Download the lesson code from GitLab: https://gitlab.com/PythonRu/tkinter-uroki
Table of Contents
Selecting numeric values
The previous material was about working with text input, but sometimes you need to restrict the field to work exclusively with numeric values. This is necessary for the Spinbox
and Scale
classes, which allow users to select a numeric value from a range or list of valid choices. However, there are differences in how they are displayed and configured. This program has Spinbox
and Scale
for selecting an integer from 0 to 5:
import tkinter as tk class App(tk.Tk): def __init__(self): super().__init__() self.spinbox = tk.Spinbox(self, from_=0, to=5) self.scale = tk.Scale(self, from_=0, to=5, orient=tk.HORIZONTAL) self.btn = tk.Button(self, text="output values", command=self.print_values) self.spinbox.pack() self.scale.pack() self.btn.pack() def print_values(self): print("Spinbox: {}".format(self.spinbox.get())) print("Scale: {}".format(self.scale.get())) if __name__ == "__main__": app = App() app.mainloop()
For debugging purposes, a button has also been added that outputs a value when pressed:
How value selection works
Both classes accept from_
and to
parameters, which denote a range of matching values – the underscore at the end is mandatory because the from
parameter is originally defined in Tck/Tk, although it is a reserved keyword in Python. A handy feature of the Scale
class is the resolution
parameter, which adjusts the rounding accuracy. For example, a value of “0.2” will allow you to choose such: 0.0, 0.2, 0.4, and so on. The default value is 1, so the widget rounds all entered numbers to the nearest integer. You can also get the value of each widget using the get()
method. The important difference is that Spinbox
returns the number as a string, while Scale
returns an integer or a floating-point number if the rounding takes decimal values. The Spinbox
class has settings that are similar to those of Entry
: textvariable
and validate
parameters. The only difference is that the rules will be limited to numeric values.
Creating radio buttons fields (radio buttons)
With the Radiobutton
widget you can let the user choose among several options. This works for a relatively small number of mutually exclusive choices. Multiple Radiobutton
instances can be connected using the Tkinter variable. In this way, if an option is selected that has not previously been selected, it will override the selection of the previous one. The following example creates three buttons for the parameters Red
, Green
and Blue
. Each press displays the name of the corresponding color in lower case:
import tkinter as tk COLORS = [(("Red", "red"), ("Green", "green"), ("Blue", "blue")] class ChoiceApp(tk.Tk): def __init__(self): super().__init__() self.var = tk.StringVar() self.var.set("red") self.buttons = [self.create_radio(c) for c in COLORS] for buttons in self.buttons: button.pack(anchor=tk.W, padx=10, pady=5) def create_radio(self, option): text, value = option return tk.Radiobutton(self, text=text, value=value, command=self.print_option, variable=self.var) def print_option(self): print(self.var.get()) if __name__ == "__main__": app = ChoiceApp() app.mainloop()
If you run the script, it will show the application where Red is already selected.
How Radiobuttons Work
To avoid repeating code to initialize Radiobuttons
, you need to define a service method that is called from the generated list. Thus, the values of each tuple in the COLORS
list are unpacked and the local variables are passed as options to the Radiobutton
. It is very important to avoid repetition if possible. Since StringVar
is common to all Radiobuttons
, they are automatically concatenated, and the user can choose only one option. The default value in the program is “red. But what happens if this line is omitted, and the StringVar
value doesn’t match any of the buttons? In this case it will coincide with the default value of option tristatevalue
, i.e., the empty string. Because of this, the widget displays in undefined “tri-state” mode. You can change this with the config()
method, but even better, set the correct default value so that the variable is initialized in a valid state.
Implementing checkboxes
Choosing from two options is usually implemented using checkboxes with an enumeration of options, where each option is independent of the others. In the following example, you can see how this concept is implemented using Checkbuttons
. The following application demonstrates how to create checkboxes that must be associated with an IntVar
variable to keep track of the state of the button:
import tkinter as tk class SwitchApp(tk.Tk): def __init__(self): super().__init__() self.var = tk.IntVar() self.cb = tk.Checkbutton(self, text="Active? variable=self.var, command=self.print_value) self.cb.pack() def print_value(self): print(self.var.get()) if __name__ == "__main__": app = SwitchApp() app.mainloop()
In this example, the value of the widget is simply displayed each time it is clicked.
How Checkboxes Work
Similar to Button
Checkbuttons
take Command
and text
parameters. You can use the onvalue
and offvalue
options to define values for the checked and empty checkboxes. An integer variable is used because the default values are 1 and 0. But it can be any other integer. You can even use other types of variables with checkbuttons
:
var = tk.StringVar() var.set("OFF") checkbutton_active = tk.Checkbutton(master, text="Active?", variable=self.var, onvalue="ON", offvalue="OFF", command=update_value)
The only restriction is that onvalue
and offvalue
must match the Tkinter variable type. In that case, since “ON” and “OFF” are strings, the variable must also be StringVar
. Otherwise, the Tcl interpreter will return an error when trying to set a corresponding value to a different type.
Displaying a list of items
The Listbox
widget contains text items that the user can select using the mouse or keyboard. It is possible to configure whether one or more items will be available for selection. The following program creates such a selector, where the options are days of the week. There is a button to display the current selection, as well as a number of buttons to change the way the selection is made:
import tkinter as tk DAYS = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"] MODES = [tk.SINGLE, tk.BROWSE, tk.MULTIPLE, tk.EXTENDED] class ListApp(tk.Tk): def __init__(self): super().__init__() self.list = tk.Listbox(self) self.list.insert(0, *DAYS) self.print_btn = tk.Button(self, text="Print Selection", command=self.print_selection) self.btns = [self.create_btn(m) for m in MODES] self.list.pack() self.print_btn.pack(fill=tk.BOTH) for btn in self.btns: btn.pack(side=tk.LEFT) def create_btn(self, mode): cmd = lambda: self.list.config(selectmode=mode) return tk.Button(self, command=cmd, text=mode.capitalize()) def print_selection(self): selection = self.list.curseselection() print([self.list.get(i) for i in selection]) if __name__ == "__main__": app = ListApp() app.mainloop()
Try changing modes and look at the output:
How selecting items from a list works
You can create an empty Listbox
object and add all elements using the insert()
method. The index 0 indicates that the elements should be added at the beginning of the list. On the next line, the DAYS
list is unpacked, but individual elements can be added to the end with the END
constant: self.list.insert(tk.END, "New item")
The current selection is extracted using the
curselection()
method. It returns the indexes of the selected items. And to further transform them into corresponding text items, the get()
method is called for each item in the list. As a result, the list is displayed in STDOUT
for debugging. In this example, the selectmode
parameter can be changed to get different behavior:
SINGLE
– one option;BROWSE
– one option, which can be moved with the arrow keys;MULTIPLE
– several variants;EXTENDED
– several variants with ranges, which are selected with theShift
andCtrl
keys.
If there are many items, it may be necessary to add a vertical scrollbar. To do this, the yscrollcommand
option must be used. In this example, both widgets are wrapped in the same window. You just have to remember to specify the fill
parameter, so that the scrollbar takes up all the space on the y-axis.
def __init__(self): self.frame = tk.Frame(self) self.scroll = tk.Scrollbar(self.frame, orient=tk.VERTICAL) self.list = tk.Listbox(self.frame, yscrollcommand=self.scroll.set) self.scroll.config(command=self.list.yview) # ... self.frame.pack() self.list.pack(side=tk.LEFT) self.scroll.pack(side=tk.LEFT, fill=tk.Y)
There is also an xscrollcommand
parameter for the horizontal axis.