Разберем создание собственных виджетов.
Отображение панелей со вкладками с использованием Notebook
В модуле ttk есть несколько дополнительных виджетов, которых нет в стандартном модуле. Типичный пример – класс ttk.Notebook, который дает возможность добавлять панель с вкладками к окну. Это эффективный способ группировки. Давайте для примера создадим программу, которая бы содержала перечень список дел, относящихся к категориям «Дом», «Работа», «Отпуск». Выглядит она так.
В этом примере с данными невозможно выполнять любые операции, кроме чтения. Это делается для более простого понимания.
Итак, чтобы написать такую программу, необходимо сначала создать импортировать библиотеку tkinter.ttk, а потом создать объект класса ttk.Notebook с заданными размерами. Затем программа будет проходиться по словарю с заблаговременно определенными данными, который и будет использоваться в качестве источника данных с вкладками и названиями для каждой из них.
import tkinter as tk import tkinter.ttk as ttk class App(tk.Tk): def __init__(self): super().__init__() self.title("Ttk Notebook") todos = { "Дом": ["Постирать", "Сходить за продуктами"], "Работа": ["Установить Python", "Учить Tkinter", "Разобрать почту"], "Отпуск": ["Отдых!"] } self.notebook = ttk.Notebook(self, width=250, height=100, padding=10) for key, value in todos.items(): frame = ttk.Frame(self.notebook) self.notebook.add(frame, text=key, underline=0, sticky=tk.NE + tk.SW) for text in value: ttk.Label(frame, text=text).pack(anchor=tk.W) self.label = ttk.Label(self) self.notebook.pack() self.label.pack(anchor=tk.W) self.notebook.enable_traversal() self.notebook.bind("<<NotebookTabChanged>>", self.select_tab) def select_tab(self, event): tab_id = self.notebook.select() tab_name = self.notebook.tab(tab_id, "text") text = "Ваш текущий выбор: {}".format(tab_name) self.label.config(text=text) if __name__ == "__main__": app = App() app.mainloop()
После того, как пользователь нажмет по вкладке, содержимое окна обновляется в соответствии с тем, какая информация хранится там. А в нижней части приложения стоит надпись, которая изменяется в зависимости от того, какая вкладка выбрана.
Принцип использования виджета
В нашем случае виджет ttk.Notebook создавался с заранее заданными параметрами ширины, высоты и внешними отступами. Каждый ключ, находящийся в словаре todos, используется для получения названия вкладки, а список значений добавляется в виде меток в ttk.Frame.
self.notebook = ttk.Notebook(self, width=250, height=100, padding=10) for key, value in todos.items(): frame = ttk.Frame(self.notebook) self.notebook.add(frame, text=key, underline=0, sticky=tk.NE+tk.SW) for text in value: ttk.Label(frame, text=text).pack(anchor=tk.W)
После этого, у виджета ttk.Notebook необходимо вызвать функцию enable_traversal(), что дает возможность пользователям использовать комбинации горячих клавиш, чтобы переключаться между вкладками:
- Ctrl + Shift + Tab.
- Ctrl + Tab.
- Alt + подчеркнутый символ для того, чтобы открывать соответствующую вкладку. Например, если она называется «Work» и содержит подчеркнутый символ W, то если нажать CTRL + W, откроется эта вкладка. То же касается и других.
С помощью виртуального события <<NotebookTabChanged>> задается изменение вкладки. Оно генерируется автоматически после этого и связывается с методом select_tab().
Важно: после добавления вкладки в ttk.Notebook это событие срабатывает автоматически.
self.notebook.pack() self.label.pack(anchor=tk.W) self.notebook.enable_traversal() self.notebook.bind("<<NotebookTabChanged>>", self.select_tab)
Если надо получить текущий дочерний элемент ttk.Notebook, то не надо применять дополнительные структуры данных, чтобы обозначать индекс вкладки и окно виджета.
С помощью метода nametowidget() мы получаем объект виджета, который соответствует конкретному имени.
def select_tab(self, event): tab_id = self.notebook.select() frame = self.nametowidget(tab_id) # ...
Применение стилей Ttk
Все параметры тематических виджетов задаются в классе ttk.Style. Этот объект предоставляет такие методы:
- configure(style, opts) – изменяет внешний вид opts для style конкретного виджета. Здесь можно указать фон, отступы или анимации.
- map(style, query). Изменяет динамический вид виджета. Первый аргумент – это непосредственно то, что надо задать. А аргумент query являет собой ключевое слово, где каждый ключ отвечает за параметр стиля, а значение – список кортежей вида (state, value). Это указывает на то, что значение каждого параметра зависит от того, какое его текущее состояние.
Например, давайте отметим такие примеры для двух случаев.
import tkinter as tk import tkinter.ttk as tk class App(tk.Tk): def __init__(self): super().__init__() self.title("Tk themed widgets") style = ttk.Style(self) style.configure("TLabel", padding=10) style.map("TButton", foreground=[("pressed", "grey"), ("active", "white")], background=[("pressed", "white"), ("active", "grey")] ) # ...
Теперь метки отображаются с отступом 10, а кнопки имеют имеют серую заливку с белым фоном, если они нажаты, а белую заливку с серым фоном – когда active.
Особенности работы стилей
Создавать стили несложно. Необходимо только генерировать соответствующий объект, в котором в качестве первого параметра будет использоваться родительский виджет. Затем возможно задание особенностей стиля для виджетов с использованием символа T в верхнем регистре и названия виджета (все это записывается одним словом). Например, Tbutton для ttk.Button, Tlabel для ttk.Label. Правда, есть и исключения. Поэтому лучше вызывать winfo_class для подходящего экземпляра виджета, чтобы сверяться с названиями.
Кроме этого, возможно добавление префикса с целью указать, что этот стиль должен быть не по умолчанию, а задаваться для конкретных виджетов.
style.configure("My.TLabel", padding=10) # ... label = ttk.Label(master, text="Какой-то текст", style="My.TLabel")
Как создать виджет выбора даты?
Если необходимо разрешить пользователям выбирать дату в программе, то можно попробовать оставить подсказку, которая бы их побудила к тому, чтобы выбрать дату. Для этого можно добавить несколько числовых полей для ввода дня, месяца, года. Правда, это требует того, чтобы четко соблюдались правила валидации.
В отличие от других фреймворков для создания интерфейсов, Tkinter не предусматривает отдельного класса для этих целей. Тем не менее, можно воспользоваться знаниями тематических виджетов, чтобы создать собственный виджет календаря.
Предположим, необходимо создать такую программу.
Кроме модулей Tkinter необходимо также подключить из стандартной библиотеки модули calendar и datetime. Это позволяет работать с датой и временем.
Посмотрите на рисунок выше. В заголовке виджета нарисованы стрелки, которые позволяют перемещаться между месяцами. То, как они выглядят, зависит от выбранного стиля Tk. В тело виджета входит таблица ttk.Treeview с экземпляром Canvas, подсвечивающий ячейку конкретной даты, выбранной пользователем.
import calendar import datetime import tkinter as tk import tkinter.ttk as ttk import tkinter.font as tkfont from itertools import zip_longest class TtkCalendar(ttk.Frame): def __init__(self, master=None, **kw): now = datetime.datetime.now() fwday = kw.pop('firstweekday', calendar.MONDAY) year = kw.pop('year', now.year) month = kw.pop('month', now.month) sel_bg = kw.pop('selectbackground', '#ecffc4') sel_fg = kw.pop('selectforeground', '#05640e') super().__init__(master, **kw) self.selected = None self.date = datetime.date(year, month, 1) self.cal = calendar.TextCalendar(fwday) self.font = tkfont.Font(self) self.header = self.create_header() self.table = self.create_table() self.canvas = self.create_canvas(sel_bg, sel_fg) self.build_calendar() # ... def main(): root = tk.Tk() root.title('Календарь Tkinter') ttkcal = TtkCalendar(firstweekday=calendar.SUNDAY) ttkcal.pack(expand=True, fill=tk.BOTH) root.mainloop() if __name__ == '__main__': main()
Создание виджета
Возможна кастомизация этого класса путем передачи ключевых слов в виде аргументов. Во время инициализации надо указать какие угодно значения, которые будут использоваться по умолчанию. Например, месяц и год.
После этого задаются атрибуты, в которых будет храниться значение даты, собственно, сама дата, календарь григорианского типа.
Создание визуальных элементов виджета осуществляется посредством методов create_header() и create_table().
А, чтобы определить размер шрифта, используется экземпляр tkfont.Font.
После того, как эти атрибуты будут инициализированы, будет выполнено визуальное выравнивание элементов календаря с помощью метода build_calendar().
С помощью колбека move_month() мы скрываем текущий выбор, выделенный с помощью поля полотна. Кроме этого, он добавляет параметр offset текущему месяцу с целью задать атрибут date с определенным месяцем. Затем снова осуществляется перерисовка календаря с учетом новых дней.
Для создания тела календаря используется виджет ttk.Treeview (который также осуществляет перебор недель и позиций элементов таблицы), а полотно, подсвечивающее выбор, создается с использованием create_canvas.
Функция zip_longest оставляет пустые строки там, где дней недостает.
Метод show_selection осуществляет размещение полотна в рамках выбранного элемента, чтобы не допустить выход за его пределы.
А параметр selection позволяет получить дату, выбранную пользователем.