Модуль tkinter.ttk
В состав пакета tkinter
входит модуль (расширение) ttk
, содержащий классы более стилизованных и современных виджетов. По умолчанию их внешний вид зависит от операционной системы.
В коде ниже для сравнения в окне размещено несколько пар аналогичных виджетов из модулей tkinter
и tkinter.ttk
.
import tkinter as tk import tkinter.ttk as ttk root = tk.Tk() tk.Button(text="Hello").pack() ttk.Button(text="Hello").pack() tk.Checkbutton(text="Hello").pack() ttk.Checkbutton(text="Hello").pack() tk.Radiobutton(text="Hello").pack() ttk.Radiobutton(text="Hello").pack() root.mainloop()
В операционной системе Ubuntu они будут выглядеть так:
Поскольку имена классов аналогичных виджетов совпадают, мы импортируем модули так, чтобы их пространства имен не перекрывались. При этом конструкторы классов вызываются через имена (в данном случае псевдонимы) модулей.
Если же нам не нужны виджеты модуля tkinter
, подобные которым есть в tkinter.ttk
, удобнее импортировать всё пространство имен как одного, так и второго модуля.
from tkinter import * from tkinter.ttk import * root = Tk() b = Button(text="Hello") b.pack() t = Text(width=10, height=5) t.pack() print(root.__class__) print(b.__class__) print(t.__class__) root.mainloop()
В данном случае пространство имен tkinter.ttk
, который импортируется вторым, перекрывает часть имен tkinter
. Поэтому выражение Button(text="Hello")
вызывает конструктор класса Button
, находящийся в модуле tkinter.ttk
. В то же время в этом модуле нет классов Tk
и Text
. Следовательно, экземпляры этих классов создаются от базовых классов tkinter
. В консоль будет выведено:
<class 'tkinter.Tk'> <class 'tkinter.ttk.Button'> <class 'tkinter.Text'>
Как уже должно быть понятно, наборы виджетов обоих модулей не полностью перекрываются. Есть общие (такие как Label
, Button
, Entry
), есть характерные только для tkinter
(например, Listbox
и Canvas
) и только для ttk
(например, Combobox
, Notebook
).
Сложность заключается в том, что в ttk
свойства виджетов программируются не совсем так как в tkinter
. Если в приложении сочетаются элементы сразу обоих модулей, код программы становится менее ясным.
Для виджетов из tkinter.ttk
многие свойства задаются с помощью экземпляра, созданного от класса ttk.Style
. Основная идея ttk
– отделить оформление виджета от описания его поведения.
Импортируем модули, не перекрывая пространства их имен, и сравним установку свойств для двух кнопок.
from tkinter import * from tkinter import ttk root = Tk() b_tk = Button(text="Hello Tk") b_ttk = ttk.Button(text="Hello Ttk") b_tk.config(background="#b00", foreground="#fff") style = ttk.Style() style.configure("TButton", background="#0b0", foreground="#fff") b_tk.pack(padx=10, pady=10) b_ttk.pack(padx=10, pady=10) root.mainloop()
Мы задаем свойства для экземпляра Button
из модуля tkinter
с помощью метода кнопки config
. Однако то же самое могли бы сделать, передав значения в конструктор:
b_tk = Button(text="Hello Tk", background="#b00", foreground="#fff")
Настроить так кнопку из модуля ttk
нельзя. Вместо этого мы должны создать экземпляр от класса Style
и уже через него изменять свойства по умолчанию.
В коде выше используется метод configure
. Здесь первым аргументом в него передается имя стиля (TButton
), связанного с классом объектов, для которых производится настройка. В данном случае это кнопки. Для уточнения имени стиля можно воспользоваться методом winfo_class
:
print(ttk.Label().winfo_class()) print(ttk.Button().winfo_class())
Вывод:
TLabel TButton
Что делать, если в программе нужны, например, кнопки разного стиля? Можно создать свой стиль, унаследовав его от исходного, и изменять лишь отдельные свойства.
from tkinter import * from tkinter.ttk import * root = Tk() style = Style() style.configure("G.TButton", foreground="green") Button(text="First", style="G.TButton").pack() Button(text="Second").pack() root.mainloop()
В примере выше созданный стиль называется "G". После точки указывается его родитель (в данном случае это стиль TButton
. При создании первой кнопки через опцию style
указываем применяемый стиль.
По-умолчанию для второй кнопки используется стиль TButton
. Мы его не меняли, хотя могли бы изменить.
На самом деле острой или частой необходимости изменять внешний вид виджетов нет. Наоборот, в приложениях приветствуется стандартный и единообразный внешний вид и поведение. Это дает пользователю интуитивно понятный интерфейс.
Кроме того, в ttk
есть разные темы. Можно менять не отдельные виджеты, а целиком тему оформления приложения. С помощью методов theme_names
и theme_use
можно выяснить список тем (который зависит от вашей ОС) и текущую тему:
from tkinter.ttk import * style = Style() print(style.theme_names()) print(style.theme_use())
Вывод:
('clam', 'alt', 'default', 'classic') default
Если в метод theme_use
передать название темы, она будет применена. В программе ниже при нажатии клавиши Enter (не кнопки!) изменяется тема приложения:
from tkinter import * from tkinter.ttk import * root = Tk() style = Style() e = Entry(justify='center') e.pack() Button(text='Hello').pack() Label(text='Hello').pack() themes = style.theme_names() i = 0 def theme_next(event): global i style.theme_use(themes[i]) e.delete(0, END) e.insert(0, themes[i]) if i < len(themes)-1: i += 1 else: i = 0 root.bind('<Return>', theme_next) root.mainloop()
Описание свойств виджетов в ttk
не всегда совпадает с тем, как это делается в базовом tkinter
. Выше в примере с красной и зеленой кнопками с помощью опций foreground
и background
мы устанавливаем цвета текста и фона кнопки, когда она не находится под курсором мыши, то есть когда она не активна.
Чтобы переопределить цвета по умолчанию при наводе курсора, для кнопок tkinter
мы должны использовать опции activeforeground
и activebackground
. Однако эти свойства не работают для виджетов из ttk
:
b_tk.config(background="red", foreground="white", activebackground="orange", activeforeground="white") style = ttk.Style() style.configure("TButton", background="green", foreground="white", activebackground="lightgreen", activeforeground="black")
Код выше выполнится без ошибок. Однако фон зеленой кнопки при нажатии на нее останется светло-серым, он не станет светло-зеленым.
В данном случае вместо метода configure
следует использовать метод map объектов типа Style
:
from tkinter import * from tkinter.ttk import * root = Tk() Button(text='Hello World').pack( padx=40, pady=40, ipadx=20, ipady=20 ) st = Style() st.map('TButton', foreground=[('!active', 'purple'), ('pressed', 'orange'), ('active', 'red')], background=[ ('pressed', 'brown'), ('active', 'white')] ) root.mainloop()
Здесь мы описываем для кнопки состояния двух свойств: текста (foreground
) и фона (background
). При этом у каждого свойства есть три состояния: неактивно, активно и нажато.
Практическая работа
Напишите программу, состоящую из выпадающего списка (Combobox
) и холста. В списке перечисляются цвета. При выборе в списке того или иного значения цвет холста должен изменяться на соответствующий.
Для справки:
- Список значений передается в
Combobox
через опциюvalues
. - Метод
current
экземпляраCombobox
позволяет указать значение, которое будет выбрано в списке изначально. Метод принимает индекс элемента из списка значений. - Событие смены значения в выпадающем списке –
'<<ComboboxSelected>>'
- Метод
get
экземпляраCombobox
возвращает выбранный на данный момент элемент списка.
Курс с примерами решений практических работ: pdf-версия