What is the recommended technique for interactively validating content in a tkinter Entry
widget?
I’ve read the posts about using validate=True
and validatecommand=command
, and it appears that these features are limited by the fact that they get cleared if the validatecommand
command updates the Entry
widget’s value.
Given this behavior, should we bind on the KeyPress
, Cut
, and Paste
events and monitor/update our Entry
widget’s value through these events? (And other related events that I might have missed?)
Or should we forget interactive validation altogether and only validate on FocusOut
events?
Advertisement
Answer
The correct answer is, use the validatecommand
attribute of the widget. Unfortunately this feature is severely under-documented in the Tkinter world, though it is quite sufficiently documented in the Tk world. Even though it’s not documented well, it has everything you need to do validation without resorting to bindings or tracing variables, or modifying the widget from within the validation procedure.
The trick is to know that you can have Tkinter pass in special values to your validate command. These values give you all the information you need to know to decide on whether the data is valid or not: the value prior to the edit, the value after the edit if the edit is valid, and several other bits of information. To use these, though, you need to do a little voodoo to get this information passed to your validate command.
Note: it’s important that the validation command returns either True
or False
. Anything else will cause the validation to be turned off for the widget.
Here’s an example that only allows lowercase. It also prints the values of all of the special values for illustrative purposes. They aren’t all necessary; you rarely need more than one or two.
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()
For more information about what happens under the hood when you call the register
method, see Why is calling register() required for tkinter input validation?
For the canonical documentation see the Validation section of the Tcl/Tk Entry man page