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
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
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().
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