La respuesta correcta es utilizar el validatecommand
atributo del widget. Desafortunadamente, esta característica está muy poco documentada en el mundo Tkinter, aunque está suficientemente documentada en el mundo Tk. Aunque no está bien documentado, tiene todo lo que necesita para realizar la validación sin recurrir a enlaces o variables de seguimiento, o modificar el widget desde dentro del procedimiento de validación.
El truco es saber que puede hacer que Tkinter pase valores especiales a su comando de validación. Estos valores le brindan toda la información que necesita saber para decidir si los datos son válidos o no: el valor antes de la edición, el valor después de la edición si la edición es válida y varios otros bits de información. Sin embargo, para usarlos, debe hacer un poco de vudú para que esta información pase a su comando de validación.
Nota: es importante que el comando de validación devuelva True
o False
. Cualquier otra cosa provocará que la validación se desactive para el widget.
Aquí hay un ejemplo que solo permite minúsculas (e imprime todos esos valores originales):
import tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
vcmd = (self.register(self.onValidate),
'%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W')
self.entry = tk.Entry(self, validate="key", validatecommand=vcmd)
self.text = tk.Text(self, height=10, width=40)
self.entry.pack(side="top", fill="x")
self.text.pack(side="bottom", fill="both", expand=True)
def onValidate(self, d, i, P, s, S, v, V, W):
self.text.delete("1.0", "end")
self.text.insert("end","OnValidate:\n")
self.text.insert("end","d='%s'\n" % d)
self.text.insert("end","i='%s'\n" % i)
self.text.insert("end","P='%s'\n" % P)
self.text.insert("end","s='%s'\n" % s)
self.text.insert("end","S='%s'\n" % S)
self.text.insert("end","v='%s'\n" % v)
self.text.insert("end","V='%s'\n" % V)
self.text.insert("end","W='%s'\n" % W)
if S == S.lower():
return True
else:
self.bell()
return False
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
Para obtener más información sobre lo que sucede bajo el capó cuando llama al register
método, consulte Validación de entrada tkinter
Después de estudiar y experimentar con el código de Bryan, produje una versión mínima de la validación de entrada. El siguiente código colocará un cuadro de entrada y solo aceptará dígitos numéricos.
from tkinter import * root = Tk() def testVal(inStr,acttyp): if acttyp == '1': #insert if not inStr.isdigit(): return False return True entry = Entry(root, validate="key") entry['validatecommand'] = (entry.register(testVal),'%P','%d') entry.pack() root.mainloop()
Quizás debería agregar que todavía estoy aprendiendo Python y con gusto aceptaré todos y cada uno de los comentarios / sugerencias.
fuente
entry.configure(validatecommand=...)
y escribe entest_val
lugar detestVal
, pero este es un buen ejemplo simple.Utilice a
Tkinter.StringVar
para realizar un seguimiento del valor del widget de entrada. Puede validar el valor deStringVar
estableciendo atrace
en él.Aquí hay un programa de trabajo corto que solo acepta flotantes válidos en el widget de Entrada.
from Tkinter import * root = Tk() sv = StringVar() def validate_float(var): new_value = var.get() try: new_value == '' or float(new_value) validate.old_value = new_value except: var.set(validate.old_value) validate.old_value = '' # trace wants a callback with nearly useless parameters, fixing with lambda. sv.trace('w', lambda nm, idx, mode, var=sv: validate_float(var)) ent = Entry(root, textvariable=sv) ent.pack() root.mainloop()
fuente
Mientras estudiaba la respuesta de Bryan Oakley , algo me dijo que se podría desarrollar una solución mucho más general. El siguiente ejemplo presenta una enumeración de modo, un diccionario de tipos y una función de configuración con fines de validación. Consulte la línea 48 para ver un ejemplo de uso y una demostración de su simplicidad.
#! /usr/bin/env python3 # /programming/4140437 import enum import inspect import tkinter from tkinter.constants import * Mode = enum.Enum('Mode', 'none key focus focusin focusout all') CAST = dict(d=int, i=int, P=str, s=str, S=str, v=Mode.__getitem__, V=Mode.__getitem__, W=str) def on_validate(widget, mode, validator): # http://www.tcl.tk/man/tcl/TkCmd/ttk_entry.htm#M39 if mode not in Mode: raise ValueError('mode not recognized') parameters = inspect.signature(validator).parameters if not set(parameters).issubset(CAST): raise ValueError('validator arguments not recognized') casts = tuple(map(CAST.__getitem__, parameters)) widget.configure(validate=mode.name, validatecommand=[widget.register( lambda *args: bool(validator(*(cast(arg) for cast, arg in zip( casts, args)))))]+['%' + parameter for parameter in parameters]) class Example(tkinter.Frame): @classmethod def main(cls): tkinter.NoDefaultRoot() root = tkinter.Tk() root.title('Validation Example') cls(root).grid(sticky=NSEW) root.grid_rowconfigure(0, weight=1) root.grid_columnconfigure(0, weight=1) root.mainloop() def __init__(self, master, **kw): super().__init__(master, **kw) self.entry = tkinter.Entry(self) self.text = tkinter.Text(self, height=15, width=50, wrap=WORD, state=DISABLED) self.entry.grid(row=0, column=0, sticky=NSEW) self.text.grid(row=1, column=0, sticky=NSEW) self.grid_rowconfigure(1, weight=1) self.grid_columnconfigure(0, weight=1) on_validate(self.entry, Mode.key, self.validator) def validator(self, d, i, P, s, S, v, V, W): self.text['state'] = NORMAL self.text.delete(1.0, END) self.text.insert(END, 'd = {!r}\ni = {!r}\nP = {!r}\ns = {!r}\n' 'S = {!r}\nv = {!r}\nV = {!r}\nW = {!r}' .format(d, i, P, s, S, v, V, W)) self.text['state'] = DISABLED return not S.isupper() if __name__ == '__main__': Example.main()
fuente
La respuesta de Bryan es correcta, sin embargo, nadie mencionó el atributo 'comando inválido' del widget tkinter.
Una buena explicación está aquí: http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/entry-validation.html
Texto copiado / pegado en caso de enlace roto
El widget de entrada también admite una opción de comando no válido que especifica una función de devolución de llamada que se llama siempre que el comando de validación devuelve False. Este comando puede modificar el texto en el widget usando el método .set () en la variable de texto asociada al widget. La configuración de esta opción funciona igual que la configuración del comando validate. Debe usar el método .register () para ajustar su función Python; este método devuelve el nombre de la función envuelta como una cadena. Luego, pasará como valor de la opción de comando no válido esa cadena o como el primer elemento de una tupla que contiene códigos de sustitución.
Nota: Solo hay una cosa que no puedo averiguar cómo hacer: si agrega validación a una entrada y el usuario selecciona una parte del texto y escribe un nuevo valor, no hay forma de capturar el valor original y restablecer la entrada. Aquí hay un ejemplo
fuente
A continuación, se muestra una forma sencilla de validar el valor de entrada, que permite al usuario ingresar solo dígitos:
import tkinter # imports Tkinter module root = tkinter.Tk() # creates a root window to place an entry with validation there def only_numeric_input(P): # checks if entry's value is an integer or empty and returns an appropriate boolean if P.isdigit() or P == "": # if a digit was entered or nothing was entered return True return False my_entry = tkinter.Entry(root) # creates an entry my_entry.grid(row=0, column=0) # shows it in the root window using grid geometry manager callback = root.register(only_numeric_input) # registers a Tcl to Python callback my_entry.configure(validate="key", validatecommand=(callback, "%P")) # enables validation root.mainloop() # enters to Tkinter main event loop
PD: Este ejemplo puede ser muy útil para crear una aplicación como calc.
fuente
import tkinter tk=tkinter.Tk() def only_numeric_input(e): #this is allowing all numeric input if e.isdigit(): return True #this will allow backspace to work elif e=="": return True else: return False #this will make the entry widget on root window e1=tkinter.Entry(tk) #arranging entry widget on screen e1.grid(row=0,column=0) c=tk.register(only_numeric_input) e1.configure(validate="key",validatecommand=(c,'%P')) tk.mainloop() #very usefull for making app like calci
fuente
Respondiendo al problema de Orionrobert de lidiar con la validación simple en sustituciones de texto mediante selección, en lugar de eliminaciones o inserciones separadas:
Una sustitución del texto seleccionado se procesa como una eliminación seguida de una inserción. Esto puede generar problemas, por ejemplo, cuando la eliminación debe mover el cursor hacia la izquierda, mientras que una sustitución debe mover el cursor hacia la derecha. Afortunadamente, estos dos procesos se ejecutan inmediatamente uno tras otro. Por lo tanto, podemos diferenciar entre una eliminación por sí misma y una eliminación seguida directamente por una inserción debido a una sustitución porque esta última no cambia la bandera inactiva entre eliminación e inserción.
Esto se explota usando un substitutionFlag y un
Widget.after_idle()
.after_idle()
ejecuta la función lambda al final de la cola de eventos:class ValidatedEntry(Entry): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.tclValidate = (self.register(self.validate), '%d', '%i', '%P', '%s', '%S', '%v', '%V', '%W') # attach the registered validation function to this spinbox self.config(validate = "all", validatecommand = self.tclValidate) def validate(self, type, index, result, prior, indelText, currentValidationMode, reason, widgetName): if typeOfAction == "0": # set a flag that can be checked by the insertion validation for being part of the substitution self.substitutionFlag = True # store desired data self.priorBeforeDeletion = prior self.indexBeforeDeletion = index # reset the flag after idle self.after_idle(lambda: setattr(self, "substitutionFlag", False)) # normal deletion validation pass elif typeOfAction == "1": # if this is a substitution, everything is shifted left by a deletion, so undo this by using the previous prior if self.substitutionFlag: # restore desired data to what it was during validation of the deletion prior = self.priorBeforeDeletion index = self.indexBeforeDeletion # optional (often not required) additional behavior upon substitution pass else: # normal insertion validation pass return True
Por supuesto, después de una sustitución, mientras se valida la parte de eliminación, uno aún no sabrá si seguirá una inserción. Sin embargo, afortunadamente, con:
.set()
,.icursor()
,.index(SEL_FIRST)
,.index(SEL_LAST)
,.index(INSERT)
, podemos alcanzar la mayoría comportamiento deseado de forma retrospectiva (ya que la combinación de nuestro nuevo substitutionFlag con una inserción es un nuevo evento único y definitivo.fuente