Глава 16 · Создаём классные приложения с Tkinter

tk и ttk: классические и тематизированные виджеты

Начиная с этого раздела в новых примерах мы предпочитаем ttk там, где существует тематизированный аналог — но сначала посмотрим, как выглядят виджеты вместе.

Витрина виджетов Tkinter

Прежде чем разбирать детали каждого виджета отдельно (разделы 16.13–16.22) — вот как они выглядят вместе, в одном настоящем окне:

Витрина: Label, Entry, Button, Checkbutton, Radiobutton, Combobox, Spinbox, Scale, Progressbar, Notebook в одном окне
Реальное окно с представительным набором виджетов главы. Пример внешнего вида на среде курса. На вашей ОС шрифты, размеры и тема могут немного отличаться.

Дальше в этой главе каждый из этих виджетов получит отдельный раздел с собственными состояниями и примерами кода.

tk и ttk — два набора виджетов

tkttk
Что этоклассические виджеты Tkinterтематизированный (themed) набор поверх Tk
Импортimport tkinter as tkfrom tkinter import ttk
Внешний видфиксированный классический стильможет адаптироваться под тему/платформу
Стилизацияfg=/bg=через ttk.Style()
Одна и та же форма (Label, Entry, Button) собрана дважды: классическим tk и тематизированным ttk
На этой теме курса разница едва заметна — так и должно быть: ttk не гарантирует «более современный» вид на любой платформе/теме. Пример внешнего вида на среде курса. На вашей ОС шрифты, размеры и тема могут немного отличаться.
tk_vs_ttk.py
import tkinter as tk
from tkinter import ttk

root = tk.Tk()

ttk.Label(root, text="Современный ttk.Label").pack()
ttk.Button(root, text="ttk.Button").pack()
ttk.Entry(root).pack()
Для нового кода — ttk, где есть аналог
Начиная с этого раздела в новых примерах мы предпочитаем ttk.Label, ttk.Button, ttk.Entry и другие тематизированные виджеты там, где существует тематизированный аналог. Но ttk не заменяет весь Tk целиком — виджеты вроде tk.Text, tk.Canvas и tk.Menu остаются классическими, потому что у них нет тематизированного аналога.
ttk не гарантирует «более современный» вид
Тема оформления ttk зависит от операционной системы и установленных тем Tk. На некоторых платформах/темах разница между tk и ttk почти незаметна визуально (как на скриншоте выше) — говорите «тематизированный», а не «современный» или «красивее».
Не используйте fg/bg на ttk-виджетах как на классических
Классические tk-виджеты понимают fg=/bg= напрямую. Виджеты ttk стилизуются иначе — через объект ttk.Style() (следующий раздел). Не переносите привычки классического Tk на ttk буквально.

from tkinter import * — не в продакшн-стиле

Импорт: звёздочка → явные имена
Не рекомендуется
from tkinter import *

root = Tk()
Button(root, text="Кнопка")   # откуда взялся Button — неочевидно
Рекомендуется
import tkinter as tk
from tkinter import ttk

root = tk.Tk()
ttk.Button(root, text="Кнопка")   # источник имени виден сразу
Что использовать сегодня: from tkinter import * работает, но затрудняет чтение кода: непонятно, какому модулю принадлежит каждое имя, и легко случайно перекрыть встроенные имена Python. В примерах курса, начиная с этой главы, — только явные импорты.

ttk.Style() — стилизация тематизированных виджетов

Обычная кнопка рядом с кнопкой, стилизованной через Accent.TButton
Слева — стиль по умолчанию, справа — собственный именованный стиль. Пример внешнего вида на среде курса. На вашей ОС шрифты, размеры и тема могут немного отличаться.
ttk_style.py
style = ttk.Style()
print(style.theme_use())   # активная тема, например 'clam' или 'default'

style.configure(
    "Accent.TButton",
    font=("TkDefaultFont", 11, "bold"),
    padding=8,
)

button = ttk.Button(
    root,
    text="Сохранить",
    style="Accent.TButton",
)
ttk.Style() — не CSS
style.configure(...) принимает только опции, которые поддерживает конкретный виджет и активная тема Tk — это не универсальный CSS-подобный язык стилей, где можно менять произвольное свойство произвольного элемента. Именованный стиль ("Accent.TButton") применяется через параметр style= у самого виджета.
Cartesian Purple
#5B24F9
Cartesian Pink
#DB2777
Success Green
#059669
Warning Amber
#B45309
Цвет — сначала увиденный, потом числовой
Прежде чем писать #5B24F9 в коде, взгляните на реальный цветовой образец выше — так проще запомнить и различать похожие оттенки, чем по одним шестнадцатеричным кодам.

Canvas — рисование произвольных фигур

Canvas с линией, прямоугольником, овалом и подписью координат
Результат кода ниже. Пример внешнего вида на среде курса. На вашей ОС шрифты, размеры и тема могут немного отличаться.
canvas_basics.py
canvas = tk.Canvas(root, width=260, height=170, background="white")
canvas.pack(padx=10, pady=10)

canvas.create_line(10, 10, 240, 10, fill="#5B24F9", width=2)
rect_id = canvas.create_rectangle(10, 30, 90, 90, outline="#DB2777", width=2)
canvas.create_oval(130, 30, 210, 90, outline="#059669", width=2)
canvas.create_text(130, 130, text="(0,0) — верхний левый угол")
Ось Y растёт вниз, а не вверх
Начало координат (0, 0) — верхний левый угол Canvas. x растёт вправо, а y растёт вниз — это противоположно привычной математической системе координат, где y растёт вверх. Так устроена экранная система координат в большинстве графических библиотек, не только Tkinter.
create_* возвращает идентификатор элемента
rect_id = canvas.create_rectangle(...)Canvas хранит нарисованные элементы и возвращает идентификатор, по которому потом можно изменить или удалить именно этот элемент (canvas.itemconfig(rect_id, ...), canvas.delete(rect_id)) — пригодится для будущих игровых интерфейсов.
Canvas — не второй Turtle
Canvas — обычный виджет Tkinter для рисования произвольных фигур внутри окна приложения, а не отдельный модуль с собственным миром координат, как turtle (главы 6–7). Это краткое знакомство, а не полноценный курс по Canvas.

PhotoImage — изображения в виджетах

Label с маленьким сгенерированным изображением-шахматкой
Изображение сгенерировано прямо в Python, без внешнего файла. Пример внешнего вида на среде курса. На вашей ОС шрифты, размеры и тема могут немного отличаться.
photoimage.py
image = tk.PhotoImage(file="icon.png")
label = ttk.Label(root, image=image)
label.pack()
Debug Lab 2: Изображение исчезает, хотя код «правильный»
photoimage_propadaet.py
def build_ui():
    image = tk.PhotoImage(file="icon.png")   # локальная переменная функции
    label = ttk.Label(root, image=image)
    label.pack()

build_ui()
# Окно открывается, но картинка не отображается —
# на месте изображения пусто.
Что видно на экране

После завершения build_ui() локальное имя image исчезает. Если на объект PhotoImage больше не остаётся Python-ссылок, объект может быть освобождён — виджет хранит ссылку на него только внутри Tcl, а не в самом Python. Поэтому приложение должно хранить ссылку на PhotoImage столько времени, сколько изображение должно отображаться.

Исправленный код
photoimage_fixed.py
class App:
    def __init__(self, root):
        self.logo_image = tk.PhotoImage(file="icon.png")   # хранится в self —
        self.logo_label = ttk.Label(root, image=self.logo_image)   # живёт, пока жив self
        self.logo_label.pack()
Практика: собираем форму из tk и ttk
Модуль tkinter открывает нативное окно Python — выполните локально в VS Code, PyCharm или Jupyter
Практика выполняется локально
Открыть практику →