Глава 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 — на модели очереди событий
Открыть практику →