Ir al contenido

Diálogos, notificaciones y validación

Una buena app le cuenta al usuario qué pasa: qué está mal en el formulario, qué está a punto de pasar, qué acaba de pasar. Pyzed tiene una pieza lista para cada cosa: errores de campo, diálogos y toasts (pequeñas notificaciones en una esquina).

Objetivo Tiempo Nivel Necesitas
Un formulario de registro con errores, una confirmación y notificaciones 30 minutos Intermedio · Python Listas y tablas
  • Calcular los errores de validación a partir de tu estado y mostrarlos con ui.field(..., error=...).
  • Mostrarlos solo después del primer intento, no mientras el usuario todavía escribe.
  • Abrir un diálogo de confirmación sobre la página con ui.dialog_layer y ui.confirm.
  • Mostrar toasts con ui.toasts y quitarlos a los pocos segundos sin congelar la ventana.
El formulario de registro con un error bajo cada campo y un toast de error en la esquina
Después de pulsar el botón con datos que faltan: un error bajo cada campo y un toast.
  1. Los errores son una función del estado. Un método devuelve {campo: mensaje} con lo que esté mal ahora mismo:

    def errors(self):
    found = {}
    if not self.name.strip():
    found["name"] = "Tell us your name."
    if "@" not in self.email:
    found["email"] = "Enter an email such as ada@example.com."
    if len(self.password) < 8:
    found["password"] = "At least 8 characters."
    return found
  2. Muéstralos bajo cada campo, pero solo cuando el usuario ya intentó enviar (self.tried). A nadie le gusta ver texto rojo antes de tener la oportunidad:

    errors = self.errors() if self.tried else {}
    ui.field("Email", ui.input(self.email, lambda v: setattr(self, "email", v)), error=errors.get("email"))
  3. Un diálogo de confirmación. ui.dialog_layer(page, dialog) dibuja dialog centrado sobre la página oscurecida; con dialog=None se ve solo la página. Así que “¿está abierto el diálogo?” es un atributo:

    dialog = None
    if self.confirming:
    dialog = ui.confirm(f"Create an account for {self.email}?", self.create,
    lambda: setattr(self, "confirming", False), title="Almost there",
    confirm_label="Create")
    page = ui.dialog_layer(page, dialog, on_dismiss=lambda: setattr(self, "confirming", False))
    Un diálogo de confirmación sobre el formulario oscurecido
    ui.confirm sobre la página; hacer clic fuera (on_dismiss) cancela.
  4. Toasts. Guarda una lista de avisos, dibújalos con ui.toasts y quita cada uno a los 4 segundos. Command.perform espera en un hilo en segundo plano, así que la ventana sigue respondiendo:

    def notify(self, kind, title, text=""):
    note_id = next(_ids)
    self.notes.append((note_id, kind, title, text))
    return pz.Command.perform(lambda: time.sleep(4), lambda _: self.dismiss(note_id))
    # en view():
    return ui.toasts(page, [ui.toast(title, text, kind=kind, on_dismiss=lambda i=i: self.dismiss(i))
    for i, kind, title, text in self.notes])
  5. Júntalo todo. El botón marca tried, muestra un toast de error si algo está mal o abre el diálogo; confirmar vacía el formulario y muestra un toast de éxito.

examples/tutorial/feedback.py
"""Talking to the user: validation messages, a confirmation dialog and toast notifications.
python examples/tutorial/feedback.py
- ``ui.field(..., error=...)`` shows a message under a field; the errors are computed from the state.
- ``ui.dialog_layer(page, dialog)`` puts a dialog (``ui.confirm``, ``ui.alert``...) over the page;
``dialog`` is ``None`` when nothing is open.
- ``ui.toasts(page, [...])`` stacks notifications in the corner; ``Command.perform`` waits a few
seconds in the background and then removes them.
Walkthrough: https://albakore.github.io/pyzed/tutorials/intermediate/dialogs-feedback/
"""
import itertools
import time
import pyzed as pz
from pyzed import ui
_ids = itertools.count()
class Signup(pz.App):
title = "Create an account"
size = (640, 560)
def __init__(self):
self.name = ""
self.email = ""
self.password = ""
self.tried = False # show errors only after the first try
self.confirming = False
self.notes = [] # (id, kind, title, text) of the visible toasts
# -- validation -----------------------------------------------------------------------
def errors(self):
found = {}
if not self.name.strip():
found["name"] = "Tell us your name."
if "@" not in self.email or "." not in self.email.split("@")[-1]:
found["email"] = "Enter an email such as ada@example.com."
if len(self.password) < 8:
found["password"] = "At least 8 characters."
return found
# -- toasts ---------------------------------------------------------------------------
def notify(self, kind, title, text=""):
note_id = next(_ids)
self.notes.append((note_id, kind, title, text))
# wait 4 seconds in a background thread, then remove it (the window never freezes)
return pz.Command.perform(lambda: time.sleep(4), lambda _: self.dismiss(note_id))
def dismiss(self, note_id):
self.notes = [n for n in self.notes if n[0] != note_id]
# -- actions --------------------------------------------------------------------------
def submit(self):
self.tried = True
if self.errors():
return self.notify("error", "Check the form", "Some fields need your attention.")
self.confirming = True # open the dialog
def create(self):
self.confirming = False
name = self.name
self.name = self.email = self.password = ""
self.tried = False
return self.notify("success", "Account created", f"Welcome, {name}!")
# -- view -----------------------------------------------------------------------------
def view(self):
t = self.resolved_theme()
errors = self.errors() if self.tried else {}
form = pz.column([
ui.heading("Create an account", level=2),
ui.muted("Leave a field empty and press the button to see the messages."),
ui.field("Name", ui.input(self.name, lambda v: setattr(self, "name", v), placeholder="Ada Lovelace"),
error=errors.get("name")),
ui.field("Email", ui.input(self.email, lambda v: setattr(self, "email", v), icon="mail",
placeholder="ada@example.com"), error=errors.get("email")),
ui.field("Password", ui.input(self.password, lambda v: setattr(self, "password", v), secure=True,
on_submit=self.submit), error=errors.get("password"),
description="8 characters or more"),
pz.row([pz.hspace(), ui.button("Create account", self.submit, variant="solid", icon="user-plus")]),
], spacing=14, padding=28, width="fill")
page = pz.container(form, width="fill", height="fill", bg=t.background)
dialog = None
if self.confirming:
dialog = ui.confirm(f"Create an account for {self.email}?", self.create,
lambda: setattr(self, "confirming", False), title="Almost there",
confirm_label="Create")
page = ui.dialog_layer(page, dialog, on_dismiss=lambda: setattr(self, "confirming", False))
return ui.toasts(page, [ui.toast(title, text, kind=kind, on_dismiss=lambda i=i: self.dismiss(i))
for i, kind, title, text in self.notes])
if __name__ == "__main__":
pz.run(Signup)
Windows, macOS y Linux
uv run --with pyzed-ui python examples/tutorial/feedback.py
Widget Evento Handler
ui.input (tres campos) on_input lambda v: setattr(self, "name", v)…
ui.input (contraseña) on_submit self.submit
Create account on_press self.submit
ui.confirm on_confirm, on_cancel self.create, cerrar el diálogo
ui.dialog_layer on_dismiss cerrar el diálogo (un clic en el fondo)
ui.toast on_dismiss lambda i=i: self.dismiss(i)
def on_key(self, combo):
if combo == "escape" and self.confirming: # Escape cierra el diálogo
self.confirming = False
return [] # atendida: la tecla no sigue
def view(self):
...
toasts = [ui.toast(title, text, kind=kind,
on_dismiss=lambda i=i: self.dismiss(i),
on_press=lambda i=i: self.dismiss(i)) # un clic en la tarjeta también lo cierra
for i, kind, title, text in self.notes]