Lección 19 · 19 de 20
Validar lo que se escribe · tkinter
Valide campos en tkinter con validate y validatecommand: rechace teclas no válidas y marque los errores con el estado invalid y un estilo rojo.

Un Entry valida con dos opciones: validate dice cuándo comprobar ("key" en cada tecla, "focusout" al salir del campo, "all" siempre) y validatecommand dice con qué: una tupla con una función registrada y los datos que debe recibir.
La función se registra con root.register(...), que la convierte en una orden de Tcl, y los datos se piden con códigos: %P es el texto tal como quedaría si se acepta el cambio, %S lo que se inserta y %W el nombre del componente. La de la edad acepta el texto vacío o hasta tres dígitos; si devuelve False con validate="key", la tecla se rechaza y se llama a invalidcommand (aquí, un pitido).
Con "focusout" no se rechaza nada: check_email comprueba que haya una arroba y un punto detrás, marca el campo con state(["invalid"]) o lo desmarca con "!invalid", y escribe debajo qué falta.
ttk.Style().map("TEntry", foreground=[("invalid", ...)]) pinta de rojo el texto de cualquier ttk.Entry en estado inválido, sin tocar los demás. Al final, email.validate() lanza la comprobación sin esperar a que el usuario salga del campo; por eso la ventana se abre ya con el error a la vista.
En DMarlon Studio, el Campo de texto de la paleta es un Entry como estos; validate y validatecommand se le añaden desde el código de la ventana con config(...).
Código · main.py
import tkinter as tkfrom tkinter import ttkroot = tk.Tk()root.title("Sign up")root.geometry("480x220")root.configure(padx=24, pady=20)ttk.Style().map("TEntry", foreground=[("invalid", "#c0392b")]) # red when "invalid"# %P: the text as it would be after the change (up to 3 digits allowed).digits = root.register(lambda new: new == "" or (new.isdigit() and len(new) <= 3))def check_email(name, text): # %W: the widget's name; True means valid ok = "@" in text and "." in text.split("@")[-1] root.nametowidget(name).state(["!invalid" if ok else "invalid"]) errors.config(text="" if ok else "The email needs a domain, like ana@example.com") return ok# validate= says when to check; validatecommand= (registered function, codes).ttk.Label(root, text="Age:").grid(row=0, column=0, sticky="w", pady=6)age = ttk.Entry(root, width=6, validate="key", validatecommand=(digits, "%P"), invalidcommand=root.bell) # a beep when a key is rejectedage.grid(row=0, column=1, sticky="w")ttk.Label(root, text="Email:").grid(row=1, column=0, sticky="w", pady=6)email = ttk.Entry(root, width=32, validate="focusout", validatecommand=(root.register(check_email), "%W", "%P"))email.grid(row=1, column=1, sticky="w")errors = ttk.Label(root, foreground="#c0392b")errors.grid(row=2, column=1, sticky="w")age.insert(0, "42")email.insert(0, "ana@example")email.validate() # run the email check now, without leaving the fieldroot.mainloop()Para recordar
validatedice cuándo yvalidatecommand, con qué.register()y%P: el texto que quedaría.- Con
"key", devolverFalserechaza la tecla. - El estado
invalidyStyle().mapmarcan el error.
Ejercicio
Añada un campo «Postcode» que solo acepte cinco dígitos y que se marque en rojo al salir de él si tiene menos.