Search code examples
pythonpython-3.xtkintertkinter-entry

How to validate tkinter entry


I'm working on a tkinter app that has an entry widget and a method to validate it whenever the user types something in it. the problem is that the <Key> binding runs the validate method before self.entry_str is updated so it tests what was previously in the entry. what I want it to do is run the validate method after self.entry_str is updated, anyone know how I could do this? here's the simplified program:

import sys
import tkinter as tk


class GUI:
    def __init__(self, master):
        # frame
        self.frame = tk.Frame(master)
        self.frame.pack()
        # StringVars
        self.error = tk.StringVar()
        self.error.set('')
        self.entry_str = tk.StringVar()
        self.entry_str.set('')
        # widgets
        self.entry = tk.Entry(self.frame, textvariable=self.entry_str)
        self.entry.bind('<Key>', lambda _: self.validate_entry())
        self.entry.grid(row=0, column=0)
        self.error_label = tk.Label(self.frame, textvariable=self.error, fg='red')
        self.error_label.grid(row=1, column=0)
        self.bttn = tk.Button(self.frame, text='continue', command=sys.exit)
        self.bttn.grid(row=2, column=0)

    def validate_entry(self):
        try:
            _ = int(self.entry_str.get())
        except ValueError:# entry is not valid: disable button and show error
            self.error.set('entry has to be an integer')
            self.bttn.config(state='disabled')
        else:# entry is valid: enable button and hide error
            self.error.set('')
            self.bttn.config(state='normal')


root = tk.Tk()
gui = GUI(root)
root.wm_title('entry validation test')
root.mainloop()


Solution

  • 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