Prawidłowa odpowiedź brzmi: użyj validatecommand
atrybutu widżetu. Niestety ta funkcja jest poważnie niedostatecznie udokumentowana w świecie Tkinter, chociaż jest wystarczająco udokumentowana w świecie Tk. Mimo że nie jest dobrze udokumentowany, zawiera wszystko, co jest potrzebne do walidacji bez uciekania się do powiązań lub zmiennych śledzenia lub modyfikowania widżetu z poziomu procedury walidacji.
Sztuczka polega na tym, aby wiedzieć, że Tkinter może przekazać specjalne wartości do polecenia walidacji. Te wartości zapewniają wszystkie informacje, które musisz znać, aby zdecydować, czy dane są prawidłowe, czy nie: wartość przed edycją, wartość po edycji, jeśli edycja jest prawidłowa, i kilka innych informacji. Aby z nich skorzystać, musisz jednak zrobić małe voodoo, aby przekazać te informacje do polecenia walidacji.
Uwaga: ważne jest, aby polecenie walidacji zwróciło albo True
lubFalse
. Cokolwiek innego spowoduje wyłączenie weryfikacji widgetu.
Oto przykład, który dopuszcza tylko małe litery (i wyświetla wszystkie te funky wartości):
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()
Aby uzyskać więcej informacji o tym, co dzieje się pod maską, gdy wywołujesz register
metodę, zobacz tkinter sprawdzania poprawności danych wejściowych
Po przestudiowaniu i eksperymentowaniu z kodem Bryana stworzyłem minimalną wersję walidacji danych wejściowych. Poniższy kod utworzy pole wejściowe i akceptuje tylko cyfry.
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()
Może powinienem dodać, że nadal uczę się Pythona i chętnie przyjmę wszelkie uwagi / sugestie.
źródło
entry.configure(validatecommand=...)
i piszątest_val
zamiast tegotestVal
, ale to jest dobry, prosty przykład.Użyj a,
Tkinter.StringVar
aby śledzić wartość widżetu Wejście. Możesz sprawdzić wartośćStringVar
, ustawiająctrace
na nim.Oto krótki działający program, który akceptuje tylko prawidłowe elementy zmiennoprzecinkowe w widgecie Wejście.
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()
źródło
Podczas studiowania odpowiedzi Bryana Oakleya coś mi powiedziało, że można opracować znacznie bardziej ogólne rozwiązanie. Poniższy przykład przedstawia wyliczenie trybu, słownik typów i funkcję konfiguracji do celów walidacji. Zobacz wiersz 48, na przykład użycie i demonstracja jego prostoty.
#! /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()
źródło
Odpowiedź Bryana jest poprawna, jednak nikt nie wspomniał o atrybucie „invalidcommand” widżetu tkinter.
Oto dobre wyjaśnienie: http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/entry-validation.html
Kopiowanie / wklejanie tekstu w przypadku zerwanego linku
Widżet Entry obsługuje również opcję invalidcommand, która określa funkcję wywołania zwrotnego, która jest wywoływana za każdym razem, gdy validatecommand zwraca wartość False. To polecenie może modyfikować tekst w widgecie za pomocą metody .set () na zmiennej tekstowej powiązanej z widżetem. Skonfigurowanie tej opcji działa tak samo, jak ustawienie validatecommand. Musisz użyć metody .register (), aby opakować swoją funkcję Pythona; ta metoda zwraca nazwę opakowanej funkcji jako ciąg. Następnie jako wartość opcji invalidcommand przekażesz ten łańcuch lub jako pierwszy element krotki zawierającej kody podstawienia.
Uwaga: Jest tylko jedna rzecz, której nie potrafię zrobić: jeśli dodasz walidację do wpisu, a użytkownik zaznaczy część tekstu i wpisze nową wartość, nie ma możliwości przechwycenia oryginalnej wartości i zresetowania wejście. Oto przykład
źródło
Oto prosty sposób sprawdzenia poprawności wartości wejściowej, który pozwala użytkownikowi wprowadzić tylko cyfry:
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
PS: Ten przykład może być bardzo przydatny do tworzenia aplikacji takiej jak calc.
źródło
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
źródło
Odpowiadając na problem orionroberta polegający na radzeniu sobie z prostą walidacją przy podstawianiu tekstu przez selekcję, zamiast oddzielnych delecji lub wstawień:
Podstawienie zaznaczonego tekstu jest przetwarzane jako usunięcie, po którym następuje wstawienie. Może to prowadzić do problemów, na przykład, gdy usunięcie powinno przesunąć kursor w lewo, podczas gdy podstawienie powinno przesunąć kursor w prawo. Na szczęście te dwa procesy są wykonywane bezpośrednio po sobie. W związku z tym możemy odróżnić samo usunięcie od usunięcia, po którym bezpośrednio następuje wstawienie z powodu podstawienia, ponieważ to ostatnie nie zmienia flagi bezczynności między usunięciem a wstawieniem.
Jest to wykorzystywane przy użyciu substitutionFlag i a
Widget.after_idle()
.after_idle()
wykonuje funkcję lambda na końcu kolejki zdarzeń: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
Oczywiście po podstawieniu, podczas sprawdzania poprawności części do usunięcia, nadal nie wiadomo, czy nastąpi wstawka. Na szczęście jednak, ze:
.set()
,.icursor()
,.index(SEL_FIRST)
,.index(SEL_LAST)
,.index(INSERT)
, możemy osiągnąć najbardziej pożądane zachowanie retrospektywnie (ponieważ połączenie naszego nowego substitutionFlag z wstawka to nowy unikalny i ostateczne wydarzenie.źródło