Rozdział 16 · Tworzenie fajnych aplikacji z Tkinter

after(): timery bez blokowania

Czekanie nie oznacza zatrzymania całej aplikacji.

after() - Odroczone połączenie bez blokowania

after_prosto.py
after_id = root.after(1000, lambda: print(„Minęła sekunda”))
# program nadal przetwarza inne wydarzenia, czekając na tę sekundę
Pętla Zdarzeń
planować after()
inne zdarzenia są nadal przetwarzane
callback nazywa się
nadszedł czas
after() ustawia callback w kolejce na przyszłość — i nie blokuje reszty oczekując.
„Za sekundę” — nie jest to stoper z dokładnością do milisekundy
after(1000, callback) oznacza „przygotuj callback do wykonania mniej więcej za 1000 ms, kiedy cykl zdarzeń będzie mógł go obsłużyć” — nie gwarantuje wykonania dokładnie w podanej milisekundzie. Jeśli w tym momencie cykl jest zajęty innym zdarzeniem, wywołanie nastąpi trochę później.

Powtarzający się timer (self-rescheduling)

tik.py
after_id = None

def tik():
    global after_id
    label.config(text=f"Sekundy: {schet.get()}")
    schet.set(schet.get() + 1)
    after_id = root.after(1000, tik)   # planuje się na nowo – to jest powtarzalność
Nie tylko for/while jest powtarzalną
Z rozdziału 10 znamy cykle for/while. Powtarzające się połączenia przez self-scheduling after() inny sposób organizacji powtarzania na podstawie zdarzeń, a nie sekwencyjnego bloku kodu.
Każdy nowy after() musi zaktualizować zapisane id
U tik() nowe wyzwanie after() przy każdym zaznaczeniu, a więc nowy identyfikator. Jeśli nie przydzielisz ponownie after_id na każdym połączeniu (jak wyżej) zapisany zmienna szybko staje się przestarzały — będzie wskazywał już wywołane połączenie, a nie faktycznie oczekujące, a próba zatrzymania timera na tym id niczego nie zatrzyma.

Zatrzymanie timera: after_cancel

after_cancel.py
def stop():
    global after_id
    if after_id is not None:
        root.after_cancel(after_id)
        after_id = None
Nie zgub dowodu tożsamości
Aby zatrzymać zaplanowaną rozmowę, musisz zapisać istotne identyfikator, który oddzwonił na ostatnie połączenie after(). Bez niego nie będziesz mógł anulować tego konkretnego zaplanowanego połączenia. Kompletny model trzech stanów (zatrzymany/w trakcie/zakończony) z ważnym after_id zobacz mini-projekt „Timer”
Debug Lab 3: Ponowne uruchomienie tworzy zdublowane timery
duplicate_after.py
def start():
    tik()   # każde kliknięcie na „Start”

ttk.Button(root, text=„Start”, command=start).pack()
# После нескольких нажатий «Старт» счётчик начинает прыгать сразу на 2, 3, 4 за секунду —
# работает уже несколько параллельных цепочек after().
Co widać na ekranie

Każde kliknięcie uruchamia nowy niezależny łańcuch samoplanowanych wywołań tik(), i one się nawzajem nie zastępują, lecz działają jednocześnie. Należy zablokować ponowne uruchomienie przez wspólny znacznik running — i ta sama flaga musi być sprawdzona samodzielnie tik(), w przeciwnym razie łańcuch nie zatrzyma się nawet po stop().

Poprawiony kod
duplicate_after_fixed.py
running = False
after_id = None

def tik():
    global after_id, running
    if not running:
        return   # stop() już opuścił flagę – nie planujemy nowej sieci
    label.config(text=f"Sekundy: {schet.get()}")
    schet.set(schet.get() + 1)
    after_id = root.after(1000, tik)

def start():
    global running
    if running:
        return   # już działa, zignoruj drugie kliknięcie
    running = True
    tik()

def stop():
    global running, after_id
    running = False
    if after_id is not None:
        root.after_cancel(after_id)
        after_id = None
Nigdy nie time.sleep() w callback
time.sleep(...) w obsłudze zatrzymuje całą pętlę zdarzeń na cały czas trwania snu, a interfejs przestaje odpowiadać. Szczegółowy podział można znaleźć w sekcji 16.32.
Praktyka: after() odliczanie
Moduł tkinter otwiera natywne okno Python — wykonaj lokalnie w VS Code, PyCharm lub Jupyter
Praktyka kursuje lokalnie
Otwórz praktykę →