Controlling the content of a tkinter entry on input
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
-
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
-
Hello, you can use the
register()method of theentryobject 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()
-
-
-
Hello,
Instead of isdigit(), use isinstance().
-
or rather this:
def check_input(s): try: float(s) return True except: return False if check_input(entry.get()): .......