Search code examples
pythontkinter-entry

Python: Constrain Entry to only accept integers AND have a maximum value


I'm trying to build a GUI that has many entry boxes, and these must be configurable to only accept integers AND only accept those integers that are <= a max value. I'm almost there, but my attempt exhibits odd behaviour. Any advice welcome. I posted this before, but it said this was answered; well, nothing I found did both of my requirements. Below is my attempt, but when I edit to try and make it accept only integers, it all goes wrong.

I've done this in Tcl/Tk, and it was quite easy, but I'm failing at every turn to get my Python version to do the same. :-(

This is the Tcl/Tk that works:

entry   enMINS  -textvar warm_mins -width 2 -validate all -vcmd {ValidIntRng %P 0 99}   

##-----------------------------------------------------------------
proc ValidIntRng {val min max} {
    expr {($val eq "") || ([string is integer $val] && ($val >= $min) && ($val <= $max))}
}
##-----------------------------------------------------------------

This is my Python which is not there yet: (Maybe a class is too ambitious for my current knowledge level, I just need a simple way to validate the Entry is an integer and <= to a max value ( >= min is not really needed, but be useful in future).

class EntryLen(tk.Entry):
    def __init__(self, master=None, max_len=1, max_val=1, **kwargs):
        self.var = tk.StringVar()
        self.max_len = max_len
        self.max_val = max_val
        tk.Entry.__init__(self, master, textvariable=self.var, **kwargs)
        self.old_value = ''
        self.var.trace('w', self.check)

    def check(self, *args):
        if self.get().isint() and (len(self.get()) <= self.max_len) and (int(self.get()) <= self.max_val):
            self.old_value = self.get() # accept change
        else:
            self.var.set(self.old_value) # reject change

Solution

  • You should use isdigital() to check if the string represents an integer string, not isint() as you do. Also empty string is a valid input for Tcl/Tk and you seems to not have a logic for that.

    Here's your code corrected:

    import tkinter as tk
    
    class EntryLen(tk.Entry):
        def __init__(self, master=None, max_len=1, max_val=1, **kwargs):
            self.var = tk.StringVar()
            self.max_len = max_len
            self.max_val = max_val
            tk.Entry.__init__(self, master, textvariable=self.var, **kwargs)
            self.old_value = ''
            self.var.trace('w', self.check)
    
        def check(self, *args):
            current_value = self.var.get()
            if (current_value.isdigit() and 
                len(current_value) <= self.max_len and 
                int(current_value) <= self.max_val):
                self.old_value = current_value
            elif current_value == "":
                self.old_value = current_value
            else:
                self.var.set(self.old_value)
    
    root = tk.Tk()
    entry_widget = EntryLen(root, max_len=2, max_val=99)
    entry_widget.pack()
    root.mainloop()