Скачайте код уроков с GitLab: https://gitlab.com/PythonRu/tkinter-uroki
Выбор числовых значений
В предыдущем материале речь шла о работе с вводом текста, но иногда есть необходимость ограничить поле для работы исключительно с числовыми значениями. Это нужно для классов Spinbox
и Scale
, которые позволяют пользователям выбирать числовое значение из диапазона или списка валидных вариантов. Однако есть отличия в том, как они отображаются и настраиваются.
У этой программы есть Spinbox
и Scale
для выбора целого числа от 0 до 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="Вывести значения",
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()
Для отладки также была добавлена кнопка, которая выводит значение при нажатии:
Как работает выбор значений
Оба класса принимают параметры from_
и to
, которые обозначают диапазон подходящих значений — нижнее подчеркивание в конце является обязательным, потому что параметр from
изначально определен в Tck/Tk, хотя является зарезервированным ключевым словом в Python.
Удобная особенность класса Scale
— параметр resolution
, который настраивает точность округления. Например, значение «0.2» позволит выбирать такие: 0.0, 0.2, 0.4 и так далее. По умолчанию установлено значение 1, поэтому виджет округляет все введенные числа до ближайшего целого.
Также значение каждого виджета можно получить с помощью метода get()
. Важное отличие в том, что Spinbox
возвращает число в виде строки, а Scale
— целое число или число с плавающей точкой, если округление принимает десятичные значения.
Класс Spinbox
имеет настройки, которые похожи на те, что есть у Entry
: параметры textvariable
и validate
. Разница лишь в том, что правила будут ограничены числовыми значениями.
Создание полей с радиокнопками (переключателями)
С помощью виджета Radiobutton
можно разрешить пользователю выбирать среди нескольких вариантов. Это работает для относительно небольшого количества взаимоисключающих вариантов.
Несколько экземпляров Radiobutton
можно подключить с помощью переменной Tkinter. Таким образом при выборе варианта, который до этого не был выбран, он будет отменять выбор предыдущего.
В следующем примере создаются три кнопки для параметров Red
, Green
и Blue
. При каждом нажатии выводится название соответствующего цвета в нижнем регистре:
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 button 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()
Если запустить скрипт, он покажет приложение, где вариант Red уже выбран.
Как работают радиокнопки
Чтобы не повторять код для инициализации Radiobutton
, нужно определить служебный метод, вызываемый из сгенерированного списка. Так, значения каждого кортежа в списке COLORS
распаковываются, а локальные переменные передаются в качестве опций в Radiobutton
. Очень важно при возможности избегать повторений.
Поскольку StringVar
является общим для всех Radiobutton
, они автоматически соединяются, а пользователь может выбрать лишь один вариант.
Значением по умолчанию в программе является «red». Но что произойдет, если эту строку пропустить, а значение StringVar
не будет соответствовать значению ни одной из кнопок? В таком случае оно будет совпадать со значением по умолчанию опции tristatevalue
, то есть, пустой строкой. Из-за этого виджет отображается в неопределенном режиме «tri-state». Это можно изменить с помощью метода config()
, но еще лучше — задавать правильное значение по умолчанию, чтобы переменная инициализировалась в валидном состоянии.
Реализация чекбоксов
Выбор из двух вариантов обычно реализуется с помощью чекбоксов с перечислением вариантов, где каждый из них не зависит от остальных. В следующем примере можно увидеть, как эта концепция реализуется с помощью Checkbutton
.
Следующее приложение демонстрирует, как создавать чекбоксы, которые должны быть связаны с переменной IntVar
для отслеживания состояния кнопки:
import tkinter as tk
class SwitchApp(tk.Tk):
def __init__(self):
super().__init__()
self.var = tk.IntVar()
self.cb = tk.Checkbutton(self, text="Активно?",
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()
В этом примере значение виджета просто выводится каждый раз при нажатии.
Как работают чекбоксы
По аналогии с Button
Checkbutton
принимает параметры Command
и text
.
С помощью опций onvalue
и offvalue
можно определить значения для отмеченного и пустого чекбоксов. Используется целочисленная переменная, потому что значения по умолчанию — это 1 и 0. Но это могут быть любые другие целые числа.
С Checkbuttons
можно использовать даже другие типы переменных:
var = tk.StringVar()
var.set("OFF")
checkbutton_active = tk.Checkbutton(master, text="Активно?", variable=self.var,
onvalue="ON", offvalue="OFF",
command=update_value)
Единственное ограничение в том, что onvalue
и offvalue
должны совпадать с типом переменной Tkinter. В таком случае, поскольку «ON» и «OFF» — это строки, то и переменная должна быть StringVar
. В противном случае интерпретатор Tcl вернет ошибку при попытке задать соответствующее значение другому типу.
Отображение списка элементов
Виджет Listbox
содержит текстовые элементы, которые пользователь может выбрать с помощью мыши или клавиатуры. Есть возможность настроить, будут ли для выбора доступны один или несколько элементов.
Следующая программа создает такой селектор, где вариантами являются дни недели. Есть кнопка для вывода текущего выбора, а также ряд кнопок для изменения способа выбора:
import tkinter as tk
DAYS = ["Понедельник", "Вторник", "Среда", "Четверг",
"Пятница", "Суббота", "Воскресенье"]
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="Вывести выбор",
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.curselection()
print([self.list.get(i) for i in selection])
if __name__ == "__main__":
app = ListApp()
app.mainloop()
Попробуйте менять режимы и смотреть на вывод:
Как работает выбор элементов из списка
Можно создать пустой объект Listbox
и добавить все элементы с помощью метода insert()
. Индекс 0 обозначает, что элементы должны добавляться в начале списка. В следующей строке список DAYS
распаковывается, но отдельные элементы можно добавить в конец с помощью константы END
:
self.list.insert(tk.END, "Новый пункт")
Текущая выборка извлекается с помощью метода curselection()
. Он возвращает индексы выбранных элементов. А для последующей трансформации их в соответствующие текстовые элементы для каждого элемента в списке вызывается метод get()
. В итоге список выводится в STDOUT
для отладки.
В этом примере параметр selectmode
можно изменить для получения разного поведения:
SINGLE
— один вариант;BROWSE
— один вариант, который можно перемещать с помощью клавиш со стрелками;MULTIPLE
— несколько вариантов;EXTENDED
— несколько вариантов с диапазонами, которые выбираются кнопкамиShift
иCtrl
.
Если элементов много, то может возникнуть необходимость добавить вертикальный скроллбар. Для этого нужно задействовать опцию yscrollcommand
. В этом примере оба виджета оборачиваются в одно окно. Нужно только не забыть указать параметр fill
, чтобы скроллбар занимал все место по оси y.
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)
Также существует параметр xscrollcommand
для горизонтальной оси.