Rozdział 16 · Tworzenie fajnych aplikacji z Tkinter

Mini-Projekt: Odliczający Timer

after() w akcji: odliczanie bez żadnej akcji blokującej.

Countdown Timer – Wzorowe Demo after() i Wniosek stwierdza.

format_time.py
def format_time(sekundy):
    minuty, ostatok = divmod(sekundy, 60)
    return f"{minuty:02d}:{ostatok:02d}"

print(format_time(65))    # 01:05
print(format_time(600))   # 10:00
Okno timera: 00:42 oraz przyciski Start, Stop, Reset
Wyświetlanie startowe — do pierwszego tykania. Przykład wyglądu w środowisku kursu. Na waszym systemie operacyjnym czcionki, rozmiary i motyw mogą się nieco różnić.

Trzy stany timera

STOPPED
running = False
after_id = None
Start
RUNNING
running = True
after_id - Aktywny
remaining osiągnęło 0
FINISHED
running = False
after_id = None
remaining = 0
STOPPED → RUNNING → FINISHED — i z powrotem do STOPPED przez Reset (nie pokazane osobną strzałką).
tajmer_gui.py
class TimerApp:
    def __init__(self, root, start_seconds=60):
        self.root = root
        self.start_seconds = start_seconds
        self.remaining = start_seconds
        self.running = False
        self.after_id = None
        self.label_var = tk.StringVar(value=format_time(self.remaining))
        ttk.Label(root, textvariable=self.label_var).pack()
        ttk.Button(root, text=„Start”, command=self.start).pack()
        ttk.Button(root, text=„Stop”, command=self.stop).pack()
        ttk.Button(root, text=„Reset”, command=self.reset).pack()

    def start(self):
        if self.running or self.remaining <= 0:
            return   # już idzie, lub już FINISHED — najpierw Reset
        self.running = True
        self._schedule_tick()

    def _schedule_tick(self):
        self.after_id = self.root.after(1000, self.tick)

    def tick(self):
        self.remaining -= 1
        self.label_var.set(format_time(self.remaining))
        if self.remaining <= 0:
            self.running = False
            self.after_id = None   #FINISHED – Nie ma już nic do planowania
            return
        self._schedule_tick()

    def stop(self):
        self.running = False
        if self.after_id is not None:
            self.root.after_cancel(self.after_id)
            self.after_id = None

    def reset(self):
        self.stop()
        self.remaining = self.start_seconds
        self.label_var.set(format_time(self.remaining))
after(1000, ...) — „w ciągu około sekundy”, nie chronometr
after(1000, callback) oznacza „zrób callback gotowym do wykonania po około 1000 ms, gdy cykl zdarzeń będzie mógł go obsłużyć” — nie gwarantuje wykonania dokładnie w wskazaną milisekundę. Jeśli cykl w tym momencie jest zajęty innym zdarzeniem, wywołanie nastąpi nieco później.
Debug Lab 6: Timer ciągle tyka po „Stop”
tajmer_bez_proverki.py
def tick(self):
    self.remaining -= 1
    self.label_var.set(format_time(self.remaining))
    self.after_id = self.root.after(1000, self.tick)   # planuj ZAWSZE

def stop(self):
    self.running = False   # status został zmieniony, ale tick() go nie sprawdza
# После нажатия «Стоп» таймер продолжает уменьшать remaining и тикать —
# флаг running изменился, но ничто не проверяет его внутри tick().
Co widać na ekranie

stop() zmienia tylko flagę running, ale sama metoda tick() nie testuje tego przed przełożeniem — łańcucha after() żyje niezależnie od flagi.

Poprawiony kod
tajmer_s_proverkoj.py
def tick(self):
    if not self.running:   # sprawdź status PRZED kontynuowaniem
        return
    self.remaining -= 1
    self.label_var.set(format_time(self.remaining))
    self.after_id = self.root.after(1000, self.tick)
Praktyka: Odliczanie
Otwórz praktykę →