Title: Validate spinbox values with tkinter and Python
The tkinter Spinbox widget displays a text box and an up/down arrow that you can use to scroll through a list of choices. Unfortunately it doesn't control what you can enter into the text box. This example shows how you can use a Spinbox widget and validate the text entered by the user.
Creating the Widget
The following code creates a Spinbox.
self.spin_min = 1
self.spin_max = 100
self.value_var = tk.IntVar(value=50)
self.spinbox = tk.Spinbox(frame, from_=self.spin_min,
to=self.spin_max, increment=1, width=4,
textvariable=self.value_var, justify=tk.RIGHT,
command=self.show_value)
self.spinbox.pack(side=tk.RIGHT, padx=5, pady=(5,0))
self.value_var.trace_add('write', lambda *_: self.show_value())
This is pretty standard except for the final trace_add statement. That method adds the show_value method as a callback that should be called whenever the value_var variable's value is modified. In this example, it allows the program to display the value's new value when it changes.
The following code adds the validation method.
cmd = (self.spinbox.register(self.validate_spinbox), '%P')
self.spinbox.configure(validate='key', validatecommand=cmd)
Tkinter is a Python wrapper for the Tk widget library, which was written in Tcl, so sometimes calling Tk methods is awkward. In this case, the code calls self.spinbox.register to create a Tcl method that the Python code can use.
The %P parameter tells the method that it should return the new value that the Spinbox would have if the change is allowed. The following table shows other values that you can use to give the validation method other information. (See this Stack Overflow answer.)
Parameter | Meaning |
%d | Type of action (1=insert, 0=delete, -1 for others) |
%i | Index of char string to be inserted/deleted, or -1 |
%P | New value if the edit is allowed |
%s | Value before to editing |
%S | The text 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 |
The following configure statement does two things. First, it sets the widget's validate value to key. That makes it call its validation method whenever a key is pressed. Second, it sets the widget's validation method to cmd, the Tcl code needed to call the validate_spinbox method described shortly. (The Spinbox widget's validate value can be one of none, key, focusin, focusout, all, and forced.)
Validating Input
The following code shows the validate_spinbox method.
def validate_spinbox(self, new_value):
# Returning True allows the edit, False prevents it.
print(f'{new_value=}')
try:
int_value = int(new_value)
return self.spin_min <= int_value <= self.spin_max
except:
return False
This method is called whenever the widget's value changes, whether because you clicked the up/down arrow or used the keyboard. First, it display the value that the widget will take if the change is allowed.
Next the code tries to convert the new value into an integer. It then returns True if the value lies between the bounds saved in self.spin_min and self.spin_max. (Look at the earlier code to see where those values are set.)
If any of that fails, either because the new value isn't an integer or it lies out of bounds, the method returns False to tell the widget to disallow the change.
Showing New Values
The following show_value method displays the Spinbox widget's new value.
def show_value(self):
'''Display the spinbox's current value.'''
value = int(self.value_var.get())
self.result_var.set(value)
This code gets the Spinbox value from value_var, an IntVar variable to the widget. It converts the value into an integer and sets result_var (another IntVar) equal to it. That variable is attached to a label that displays the result.
Conclusion
This method isn't too hard, but it does have one fairly big drawback. When the user clicks and drags to select part of the text and then presses a key, the widget tries to delete the selected text before adding the new text. For example, suppose you select the 5 in 50 and then press 8. The widget first tries to change the value to 0, but in this example that value is out of bounds so the validation method prevents that change. Next, the widget tries to add the digit 8, but that would make the new value 580 so the validation method prevents that change, too.
Similarly if you highlight all of the text and try to replace it, the widget will first remove all of the text giving an empty string, which the validation method will prohibit. For example, if the widget contains 1, you highlight it, and press 5, the validation initially sees a blank string and says, "Nope!" It then adds the 5 to give you 51.
You could use isdigit to verify that the text contains only digits, but that won't allow a blank string either.
Overall, it is often better to let the user type whatever they want and validate the entry at the end when the program actually needs to use its value.
Download the example to experiment with it and to see additional details.
|