Lista de tareas
En este tutorial construimos una lista de tareas: la primera app clásica, y una buena, porque tiene todo lo que tiene una app real: datos, un formulario para añadirlos, una lista para mostrarlos y formas de cambiarlos.
| Objetivo | Tiempo | Nivel | Necesitas |
|---|---|---|---|
| Una lista de tareas con filtros y atajos | 30 minutos | Básico · Python | Tutorial en 5 pasos (pasos 1 y 2) |
Qué vas a aprender
Sección titulada «Qué vas a aprender»- Guardar los datos de la app en objetos de Python normales (un dataclass).
- Leer un campo de texto mientras el usuario escribe y enviarlo con Enter.
- Dibujar una lista de filas, cada una con una casilla y un botón de borrar.
- Filtrar lo que se muestra y añadir atajos de teclado.

-
El modelo. Una tarea es un dataclass. El estado de la app es la lista de tareas, el texto que se está escribiendo y el filtro activo:
@dataclassclass Task:title: strdone: bool = Falseclass Todo(pz.App):title = "Tareas"size = (520, 560)def __init__(self):self.tasks = [Task("Crear mi primera app")]self.draft = ""self.filter = "Todas" -
Añadir tareas. El campo es controlado: muestra
self.drafty cada pulsación lo actualiza. Enter (on_submit) y el botón llaman al mismo método:def add(self):if self.draft.strip():self.tasks.append(Task(self.draft.strip()))self.draft = ""return Command.focus("new-task") # devuelve el foco al campo# en view():pz.row([ui.input(self.draft, lambda text: setattr(self, "draft", text), id="new-task",placeholder="¿Qué hay que hacer?", on_submit=self.add, icon="plus"),ui.button("Añadir", self.add, variant="solid"),], spacing=8) -
Dibujar la lista. Cada tarea es un
ui.list_itemcon una casilla al inicio y un botón de borrar al final. Las lambdas capturantaskporquetask_rowrecibe la tarea como argumento:def task_row(self, task):return ui.list_item(ui.label(task.title, color="text_muted" if task.done else "text"),lambda: self.toggle(task),start=ui.checkbox(task.done, lambda _: self.toggle(task)),end=ui.icon_button("trash-2", lambda: self.remove(task), size="sm", tooltip="Eliminar"),height=34,inset=True,) -
Filtrar.
ui.segmentedcambiaself.filter, yview()solo muestra las tareas visibles. Comoview()se vuelve a llamar después de cada cambio, no hay que “refrescar” nada:ui.segmented(["Todas", "Pendientes", "Hechas"], self.filter, lambda f: setattr(self, "filter", f)) -
Atajos.
on_keyrecibe cada tecla como texto:def on_key(self, combo):if combo == "ctrl-l":return self.clear_done()if combo == "ctrl-t":self.theme = "One Light" if self.resolved_theme().is_dark else "One Dark" -
Foco inicial.
on_startse ejecuta una vez al abrir la ventana:def on_start(self):return Command.focus("new-task")
Código completo
Sección titulada «Código completo»"""A to-do list: state, text input, list items, filters and keyboard shortcuts.
python examples/todo.py
Walkthrough: https://albakore.github.io/pyzed/tutorials/basic/todo-app/"""
from dataclasses import dataclassfrom itertools import count
import pyzed as pzfrom pyzed import Command, ui
_ids = count(1)
@dataclassclass Task: title: str done: bool = False id: int = 0
def __post_init__(self): self.id = self.id or next(_ids)
class Todo(pz.App): title = "Tareas" size = (520, 560) min_size = (400, 360)
def __init__(self): self.tasks = [Task("Leer la guía de Pyzed", done=True), Task("Crear mi primera app"), Task("Probar los temas")] self.draft = "" self.filter = "Todas"
# -- actions ----------------------------------------------------------- def add(self): if self.draft.strip(): self.tasks.append(Task(self.draft.strip())) self.draft = "" return Command.focus("new-task")
def toggle(self, task): task.done = not task.done
def remove(self, task): self.tasks.remove(task)
def clear_done(self): self.tasks = [t for t in self.tasks if not t.done]
def on_start(self): return Command.focus("new-task")
def on_key(self, combo): if combo == "ctrl-l": return self.clear_done() if combo == "ctrl-t": self.theme = "One Light" if self.resolved_theme().is_dark else "One Dark"
# -- view ---------------------------------------------------------------- def visible(self): if self.filter == "Pendientes": return [t for t in self.tasks if not t.done] if self.filter == "Hechas": return [t for t in self.tasks if t.done] return self.tasks
def task_row(self, task): return ui.list_item( ui.label(task.title, color="text_muted" if task.done else "text"), lambda: self.toggle(task), start=ui.checkbox(task.done, lambda _: self.toggle(task)), end=ui.icon_button("trash-2", lambda: self.remove(task), size="sm", tooltip="Eliminar"), height=34, inset=True, )
def view(self): pending = sum(not t.done for t in self.tasks) tasks = self.visible() return pz.container( pz.column( [ pz.row([ui.heading("Tareas", level=2), pz.hspace(), ui.badge(f"{pending} pendientes")], align="center"), pz.row( [ ui.input(self.draft, lambda text: setattr(self, "draft", text), id="new-task", placeholder="¿Qué hay que hacer?", on_submit=self.add, icon="plus"), ui.button("Añadir", self.add, variant="solid"), ], spacing=8, ), pz.row( [ ui.segmented(["Todas", "Pendientes", "Hechas"], self.filter, lambda f: setattr(self, "filter", f)), pz.hspace(), ui.button("Limpiar hechas", self.clear_done, variant="ghost", size="sm", keys="ctrl-l"), ], align="center", ), ui.card( pz.scrollable(pz.column([self.task_row(t) for t in tasks], spacing=2), height="fill") if tasks else pz.center(ui.muted("No hay tareas aquí."), height=120), padding=6, ), ], spacing=14, max_width=640, ), padding=24, width="fill", height="fill", align_x="center", bg=self.resolved_theme().editor, )
if __name__ == "__main__": pz.run(Todo)uv run --with pyzed-ui python examples/todo.pypython3 -m venv .venvsource .venv/bin/activatepython -m pip install pyzed-uipython examples/todo.pypy -m venv .venv.venv\Scripts\Activate.ps1python -m pip install pyzed-uipython examples\todo.pypython3 examples/todo.pypy examples\todo.pyEventos de este tutorial
Sección titulada «Eventos de este tutorial»| Widget | Evento | Handler | Recibe |
|---|---|---|---|
ui.input |
on_input |
lambda text: setattr(self, "draft", text) |
El texto. |
ui.input, Añadir |
on_submit, on_press |
self.add |
Nada. |
ui.segmented |
on_select |
lambda f: setattr(self, "filter", f) |
El filtro. |
ui.list_item |
on_press |
lambda: self.toggle(task) |
Nada. |
ui.checkbox |
on_toggle |
lambda _: self.toggle(task) |
True/False (se ignora: _). |
ui.icon_button |
on_press |
lambda: self.remove(task) |
Nada. |
| La app | on_start, on_key |
enfoca el campo; ctrl-l, ctrl-t |
—, la combinación. |
Prueba más eventos
Sección titulada «Prueba más eventos»Renombra una tarea con doble clic y abre un menú con clic derecho:
def __init__(self): ... self.renaming = None # la tarea que se renombra self.menu_for = None # la tarea con el menú abierto
def task_row(self, task): return ui.list_item( ui.label(task.title), lambda: self.toggle(task), start=ui.checkbox(task.done, lambda _: self.toggle(task)), on_double_press=lambda: setattr(self, "renaming", task), # doble clic on_right_press=lambda: setattr(self, "menu_for", task), # clic derecho height=34, inset=True, )
def view(self): page = ... # la página del tutorial dialog = None if self.renaming: task = self.renaming dialog = ui.prompt("Renombrar", task.title, lambda t: setattr(task, "title", t), lambda: setattr(self, "renaming", None), lambda: setattr(self, "renaming", None)) return ui.dialog_layer(page, dialog)Siguiente
Sección titulada «Siguiente»- Enlace de datos y validación en el diseñador.
- Listas y tablas: buscar, ordenar y seleccionar filas.
- La app Todos del Showcase, que guarda la lista en disco.