Interactively validating Entry widget content in tkinter
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
Interactively validating Entry widget content in tkinter (part 2 - change the properties of the entry object)
I can't pass the entry pointer to the validation function
You can pass the name of the entry widget, and use tkinter's nametowidget
method to convert that name into the instance of the widget.
Could you please explain ... why my validatecommand usage only works once?
You are improperly configuring the validate command. Consider this code:
self.entry['validatecommand'] = self.onValidate2(self.entry)
The above code is functionally identical to this:
result = self.onValidate2(self.entry)
self.entry['validatecommand'] = result
In other words, you are immediately calling your validation function and then setting the validatecommand
option to None
. the validationcommand
option must be set to a callable.
Here is a working example of what you are trying to achieve. Normally the validate command must return True
for a valid entry and False
for an invalid entry, but I'm guessing you actually want to allow invalid entries and instead just want to turn the background red.
This example creates multiple entry widgets so you can see that the single validation function works for multiple widgets, by passing the name of the widget to the validation function.
import tkinter as tk
class Example(tk.Frame):
def __init__(self, parent):
tk.Frame.__init__(self, parent)
vcmd = self.register(self.onValidate)
for i in range(4):
entry = tk.Entry(self, validate="all")
entry.configure(validatecommand=(vcmd, "%W", "%P"))
entry.pack(side="top", fill="x")
def onValidate(self, entry_name, new_value):
entry = self.nametowidget(entry_name)
entry.configure(background="white")
try:
int(new_value)
except ValueError:
entry.configure(background="red")
return True
if __name__ == "__main__":
root = tk.Tk()
Example(root).pack(fill="both", expand=True)
root.mainloop()
How to validate tkinter entry
Use <KeyRelease>
instead of <Key>
A better way would be to use .trace
self.entry_str = tk.StringVar()
self.entry_str.set('')
self.entry_str.trace('w', self.validate_entry)
...
def validate_entry(self, *event):
try:
_ = int(self.entry_str.get())
...
An alternative is to disable the user from entering anything but integer.
self.entry = tk.Entry(self.frame, textvariable=self.entry_str, validate='key', validatecommand=(master.register(self.validate), "%P"))
...
def validate(self, char): # this function must return only True or False
return char.isdigit() or char==''
Do note self.entry_str
will always be up-to date
How to validate entry widgets in python tkinter
There are 2 problems with your code.
The validation function should always return a boolean.
From this answer:
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.
Your
test_input
function doesn't do that - there's a branch in which it returnsNone
.def test_input(value, action):
valid_input = ["7", "8", "9", "+", "4", "5", "6", "-", "1", "2", "3", "*", "0", ".", "/"]
if action == "1":
if value not in valid_input:
return False
return True
# None is being returned here!This is why the validation is disabled after your program deletes text from the Entry. The fix is simple: return
True
instead ofNone
.def test_input(value, action):
valid_input = ["7", "8", "9", "+", "4", "5", "6", "-", "1", "2", "3", "*", "0", ".", "/"]
if action == "1":
if value not in valid_input:
return False
return True
# if action != 1, allow it
return TrueThe validation function needs to handle multi-character input.
You've assumed that the validation function is called for every single character that's entered. This is true when the user types a formula with their keyboard, but not when copy/pasting or setting the entry's text with
.insert(...)
. Your function needs to handle these cases.def test_input(value, action):
valid_input = ["7", "8", "9", "+", "4", "5", "6", "-", "1", "2", "3", "*", "0", ".", "/"]
if action == "1":
return all(char in valid_input for char in value)
# if action != 1, allow it
return True
validation of tkinter entry widget
This accepts valid decimal numbers not greater than 1000:
def acceptNumber(inp):
try:
return True if inp == '' else float(inp) <= 1000
except:
return False
How to validate a Tkinter entry widget to only accept string?
Here is a small snippet to actually make you understand better
from tkinter import *
from tkinter import messagebox
root = Tk()
def check():
sel = e.get()
if not sel.isalpha():
messagebox.showerror('Only letters','Only letters are allowed!')
e = Entry(root)
e.pack(pady=10)
b = Button(root,text='Click Me',command=check)
b.pack(padx=10,pady=10)
root.mainloop()
Here we are checking if sel.isalpha()
returns False
or not, if it does, then show a messagebox
saying only letters are allowed. Simple as that.
Do let me know if any errors. Happy coding
Here is more on isalpha()
method
Cheers
Tkinter Entry validatation
The proper way to do entry validation is with the validatecommand
option rather than using trace
. With the validation feature built into the widget you don't need a reference to the widget itself (though you can use it if you want).
When the validatecommand
is run, you can have it pass in what the new value will be if the input is valid. You only need to check this value and then return True
or False
, without having to know which widget it applies to.
For example:
import tkinter as tk
def validate_input(new_value):
valid = new_value .isdigit() and len(new_value) <= 5
return valid
root = tk.Tk()
validate = root.register(validate_input)
for i in range(10):
entry = tk.Entry(root, validate="key", validatecommand=(validate, "%P"))
entry.pack(side="top", fill="x")
root.mainloop()
For information about what %P
represents, and what else can be used as arguments to the command, see this question: Interactively validating Entry widget content in tkinter
Related Topics
How Does Python'S Super() Work With Multiple Inheritance
Why Does "Pip Install" Inside Python Raise a Syntaxerror
How to Make a Python Script Standalone Executable to Run Without Any Dependency
Running Bash Commands in Python
Difference Between Class and Instance Attributes
Link to Flask Static Files With Url_For
Why Does Python Use 'Else' After For and While Loops
What Does the 'B' Character Do in Front of a String Literal
Pygame Installation Issue in MAC Os
Is It Pythonic to Use List Comprehensions For Just Side Effects
How to Concatenate Two Lists in Python
Best Way to Strip Punctuation from a String
If/Else in a List Comprehension
Split a Pandas Column of Lists into Multiple Columns
Difference Between @Staticmethod and @Classmethod
Python Subprocess.Popen "Oserror: [Errno 12] Cannot Allocate Memory"
Convert Rgb Color to English Color Name, Like 'Green' With Python