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

Отладка интерфейса и качество GUI

Отзывчивость интерфейса — не автоматическое следствие использования Tkinter, а результат конкретных решений в коде.

Событийный цикл нельзя занимать надолго

Debug Lab 7: time.sleep() внутри callback замораживает интерфейс
sleep_v_callback.py
import time

def on_click():
    time.sleep(5)   # "подождать 5 секунд"
    label.config(text="Готово!")
# На все 5 секунд окно перестаёт перерисовываться и реагировать на клики —
# событийный цикл заблокирован внутри time.sleep(), а не только сам callback.
Что видно на экране

time.sleep(...) останавливает весь поток, в котором работает событийный цикл — а Tkinter обычно работает в одном потоке. Пока поток спит, цикл не может обработать вообще ничего: ни перерисовку, ни клики, ни закрытие окна.

Исправленный код
after_vmesto_sleep.py
def on_click():
    label.config(text="Ждём...")
    root.after(5000, lambda: label.config(text="Готово!"))
    # событийный цикл продолжает работать все эти 5 секунд
Debug Lab 8: while True вместо событийного цикла
while_true_gui.py
while True:
    if button_nazhata():
        obrabotat_klik()
# Такого кода не должно быть в Tkinter-приложении вообще —
# у Tk уже есть собственный событийный цикл внутри mainloop().
Что видно на экране

Tkinter уже предоставляет полноценный событийный цикл. Собственный while True для «опроса» состояния виджетов — не нужная и не работающая архитектура: она либо блокирует mainloop(), либо просто избыточна.

Исправленный код
mainloop_vmesto_while.py
button.config(command=obrabotat_klik)
root.mainloop()   # cам обрабатывает события, включая клики по button
Debug Lab 9: Долгое вычисление напрямую в callback
dolgoe_vychislenie.py
def on_click():
    result = obrabotat_million_zapisej()   # секунды вычислений внутри callback
    label.config(text=str(result))
# Интерфейс "подвисает" на всё время вычисления — ровно так же,
# как и с time.sleep(), просто по другой причине.
Что видно на экране

Любая тяжёлая по времени операция внутри callback — не только time.sleep() — занимает событийный цикл на всё это время. Для по-настоящему долгих задач: разбивать работу на части, планируемые через after(), или выносить в отдельный поток/процесс с безопасной передачей результата обратно — это уже продвинутая тема, не требующая полной реализации в этом курсе.

Исправленный код
razbivka_cherez_after.py
def obrabotat_chast(indeks):
    if indeks >= len(dannye):
        label.config(text="Готово")
        return
    obrabotat_odnu_zapis(dannye[indeks])
    root.after(0, obrabotat_chast, indeks + 1)   # следующая часть — на следующем такте цикла
Обновление виджетов — из потока событийного цикла
Если превью с рабочим потоком вообще встречается в вашем коде — результат нужно безопасно передавать обратно и обновлять виджеты именно из событийного цикла Tkinter, а не напрямую из другого потока. Полная многопоточная архитектура — вне рамок этой главы.

Тестируем то, что можно протестировать без окна

test_domennoj_logiki.py
def test_calculate_tip():
    assert calculate_tip(100, 10, 2) == 5.0

test_calculate_tip()
print("Доменная логика проверена без единого виджета.")
Чек-лист перед тем, как считать GUI-проект готовым
Окно открывается
Все виджеты видны
Порядок Tab по клавиатуре разумен
Кнопка вызывает ожидаемый callback
Некорректный ввод обрабатывается понятным сообщением
Изменение размера окна не ломает раскладку
Закрытие окна завершает процесс корректно
Данные сохраняются там, где это нужно
Практика: блокирующий vs неблокирующий обработчик
Проверяется без tkinter — на модели очереди событий
Открыть практику →