python - solo - Validación interactiva del contenido del widget de entrada en tkinter
validar numeros en python (4)
¿Cuál es la técnica recomendada para validar interactivamente el contenido en un widget de Entry
tkinter?
He leído las publicaciones sobre el uso de validate=True
y validatecommand=command
, y parece que estas funciones están limitadas por el hecho de que se borran si el comando validatecommand
actualiza el valor del widget Entry
.
Dado este comportamiento, ¿debemos unirnos a KeyPress
, Cut
y Paste
eventos y monitorear / actualizar el valor de nuestro widget de Entry
través de estos eventos? (¿Y otros eventos relacionados que podría haber perdido?)
¿O deberíamos olvidar la validación interactiva por completo y solo validar en los eventos de FocusOut
?
Después de estudiar y experimentar con el código de Bryan, produje una versión mínima de 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 añadir que todavía estoy aprendiendo Python y con mucho gusto aceptaré todos los comentarios / sugerencias.
La respuesta correcta es usar el atributo validatecommand
del widget. Lamentablemente, 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 hacer la validación sin recurrir a enlaces o variables de seguimiento, o modificar el widget desde el procedimiento de validación.
El truco está en 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 anterior a 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. Para usar estos, sin embargo, 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 hará 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 # python 3.x
# import Tkinter as tk # python 2.x
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
# valid percent substitutions (from the Tk entry man page)
# note: you only have to register the ones you need; this
# example registers them all for illustrative purposes
#
# %d = Type of action (1=insert, 0=delete, -1 for others)
# %i = index of char string to be inserted/deleted, or -1
# %P = value of the entry if the edit is allowed
# %s = value of entry prior to editing
# %S = the text string being inserted or deleted, if any
# %v = the type of validation that is currently set
# %V = the type of validation that triggered the callback
# (key, focusin, focusout, forced)
# %W = the tk name of the widget
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)
# Disallow anything but lowercase letters
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()
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 tipo y una función de configuración para fines de validación. Vea la línea 48 para ejemplos de uso y una demostración de su simplicidad.
#! /usr/bin/env python3
# https://.com/questions/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()
Use un Tkinter.StringVar
para rastrear el valor del widget de Entrada. Puede validar el valor de StringVar
estableciendo un trace
en él.
Aquí hay un breve programa de trabajo que acepta solo 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()