Controlling the content of a tkinter entry on input

Pradelles -  
mamiemando Posted messages 33228 Registration date   Status Moderator Last intervention   -

Hello,

I need to create a Python program with tkinter, and I'm looking for a way to prevent certain characters from being entered in the entry bar (special characters, letters) (only numbers to avoid error messages with the calculations).

Thank you

6 answers

  1. mamiemando Posted messages 33228 Registration date   Status Moderator Last intervention   7 940
     

    Hello,

    Feedback on previous responses

    Solution #1 only controls the value of the last character entered (%S). While this approach works perfectly to validate an unsigned integer (since any non-empty substring of an unsigned integer is also an unsigned integer), it will not work for a signed integer (since "-" alone is not a signed integer) or for a float (since neither ".", nor "-" alone constitute a valid float).

    Solution #5 applies if validation needs to be done upon submission (for example, triggering check_input when clicking a button), but not at the moment of input.

    To work around the problem, it's necessary to adapt solution #1 so that we can control the content of the tk.Entry in its entirety (as suggested in #5).

    In an ideal world...

    According to this link, we can define what will be passed to the validation callback. We would like to pass the entire value entered into the Entry, and thus use %P instead of %S in solution #1.

    Unfortunately, in my case, I am only getting the last character entered. Therefore, we will need to manually reconstruct the value that the Entry is about to accept. For this, we will need the value currently stored in the Entry (%s), the position where the new character is being entered (%i), and the newly entered character.

    Preliminary solution

    In order to separate this reconstruction step and the validation step, I separate the two functionalities in the following code. Technically, the reconstruction is done by a functor, and the validation is implemented in a callback passed to this functor.

    • As a reminder, a functor is a class that implements the __call__ method and, once instantiated, behaves like a function. It is thus a way to implement what in mathematics is referred to as a parameterized function (for example: f_t(x, y) where t is the parameter of the function, and x and y are its arguments). In the case of a functor, t is a parameter of the constructor (the __init__ method); x and y are parameters of the __call__ method.
    import tkinter as tk class Validator: def __init__(self, check: callable): self.check = check def __call__(self, action :str, before: str, i: str, what: str) -> bool: i = int(i) action = int(action) print(f"before = {before} i = {i}, what = {what}") if action == 1: # Insertion text = before[:i] + what + before[i:] elif action == 0: # Deletion text = before[:i] + before[i+1:] else: # Focus change, etc. return True print(f"before = {before} i = {i}, what = {what}") return self.check(text) def is_float(s: str) -> bool: try: float(s) return True except: return False root = tk.Tk() entry = tk.Entry(root) check_float = Validator(is_float) entry.pack() entry.config(validate="key") # See https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/entry-validation.html entry.config(validatecommand=(entry.register(check_float), "%d", "%s", "%i", "%S")) root.mainloop()

    Note: the prints are only there for illustrating the program.

    Proposed solution

    The preliminary solution has some limitations:

    • If we want to enter "-1.23" and we start by entering "-", at this stage we would only have "-" and thus is_float would return False. We can therefore decide to allow the string "-" to be valid in this exceptional case.
    • Similarly, we can allow the empty string to enable the user to erase their entire input.

    To address these limitations, I relax the is_float test in the code below to make this possible (see function is_float_relaxed).

    import tkinter as tk class Validator: def __init__(self, check: callable): self.check = check def __call__(self, action :str, before: str, i: str, what: str) -> bool: i = int(i) action = int(action) if action == 1: # Insertion text = before[:i] + what + before[i:] elif action == 0: # Deletion text = before[:i] + before[i+1:] else: # Focus change, etc. return True return self.check(text) def is_float(s: str) -> bool: try: float(s) return True except: return False def is_float_relaxed(s: str) -> bool: return s in {"", "-"} or is_float(s) root = tk.Tk() entry = tk.Entry(root) check_float = Validator(is_float_relaxed) entry.pack() entry.config(validate="key") # See https://anzeljg.github.io/rin2/book2/2405/docs/tkinter/entry-validation.html entry.config(validatecommand=(entry.register(check_float), "%d", "%s", "%i", "%S")) root.mainloop()

    Good luck

    1
  2. xHaMaz Posted messages 123 Registration date   Status Member Last intervention   18
     

    Hello, you can use the register() method of the entry object like this:

    import tkinter as tk def validate_input(input_str): return input_str.isdigit() root = tk.Tk() entry = tk.Entry(root) entry.pack() entry.config(validate="key") entry.config(validatecommand=(entry.register(validate_input), '%S')) root.mainloop()
    0
  3. Pradelles
     

    Thank you, but I can't type decimal numbers, is there a way to do it?

    0
  4. Phil_1857 Posted messages 1883 Registration date   Status Member Last intervention   169
     

    Hello,

    Instead of isdigit(), use isinstance().

    0
  5. Phil_1857 Posted messages 1883 Registration date   Status Member Last intervention   169
     

    or rather this:

    def check_input(s): try: float(s) return True except: return False if check_input(entry.get()): .......
    0