Learn
08.2 | TKINTER: EVENTS AND APPLICATION STATE
01 | THE USER CHOOSES THE NEXT STEP
In a GUI, the user might click Add, change a filter, press Enter or close the window. Each action can produce an event. The event loop notices it and dispatches a suitable handler.
A callback is a callable you supply for the toolkit to invoke later. You register the function during setup; you do not repeatedly call it yourself in a blocking while loop.
Think of a reception desk. While a visitor’s request is being handled, other work waits. A slow callback can stop the interface processing repainting and input. Responsiveness depends on returning control promptly.
02 | STATE IS MORE THAN WHAT THE LABEL SAYS
Application state is the program’s current meaningful data, such as a completion count or selected task ID. A label is a view of that state. Do not make an English sentence the only place an important number is stored.
The CounterApp keeps self.count as an integer and self.display as the linked display text. complete changes the integer, then renders a new message. This makes the rule easy to test or reuse.
The object stays alive while its callbacks are registered. Using an instance attribute avoids making every piece of data a global variable, and keeps related operations together.
import tkinter as tk
from tkinter import ttk
class CounterApp:
def __init__(self, root):
self.count = 0
self.display = tk.StringVar(root, value="Completed: 0")
frame = ttk.Frame(root, padding=16)
frame.pack(fill="both", expand=True)
ttk.Label(frame, textvariable=self.display).pack(pady=8)
ttk.Button(frame, text="Complete one", command=self.complete).pack(pady=8)
def complete(self):
self.count += 1
self.display.set(f"Completed: {self.count}")
root = tk.Tk()
root.title("Completion counter")
app = CounterApp(root)
root.mainloop()03 | PASS THE FUNCTION, NOT ITS RESULT
command=self.complete passes a bound method. When the button is activated, Tk calls that method. command=self.complete() calls it during construction and passes whatever it returns, commonly None.
When a callback needs a fixed argument, use a small wrapper such as lambda: self.change_done(True). The lambda is a callable created now, and its body runs later.
Inside a loop, a lambda referring to a changing loop variable can use its final value when called later. Capture the intended value deliberately with a default, such as lambda value=value: choose(value), instead of assuming registration copies every variable.
04 | COMMAND CALLBACKS AND EVENT BINDINGS
A normal Button command callback receives no event argument. bind registers a handler for an event pattern and normally passes an event object. That object contains details such as the key or widget involved.
The greeting function uses event=None so both the button and the Entry’s Return binding can call it. It reads name.get(), trims outer whitespace and updates a message.
A blank name follows a rejection path instead of displaying a meaningless greeting. Validation happens before accepted state or output changes. Pressing Enter and clicking the button should produce consistent results.
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.title("A helpful greeting")
name = tk.StringVar(root)
message = tk.StringVar(root, value="Enter a first name.")
def greet(event=None):
cleaned = name.get().strip()
if not cleaned:
message.set("Please enter a non-blank name.")
return
message.set(f"Hello, {cleaned}. What will you build today?")
frame = ttk.Frame(root, padding=16)
frame.pack(fill="both", expand=True)
entry = ttk.Entry(frame, textvariable=name)
entry.pack(pady=8)
entry.bind("<Return>", greet)
ttk.Button(frame, text="Greet", command=greet).pack(pady=8)
ttk.Label(frame, textvariable=message).pack(pady=8)
entry.focus_set()
root.mainloop()05 | VALIDATE BEFORE MUTATING
An Entry returns text. Convert a minutes field with int(), handle ValueError, and then check the allowed range. Successful conversion does not mean -5 or 10000 minutes is suitable for your application.
In the planner, the storage service validates its own public method arguments too. UI checks help the user; service checks protect the application rule when another caller bypasses the interface.
On rejection, show helpful feedback and preserve previously accepted records. Do not catch every exception and pretend a save succeeded. Only clear a task field after the store has confirmed an accepted write.
06 | SCHEDULE A STEP WITH AFTER
root.after(1500, finish) schedules a callback for an opportunity no earlier than roughly 1500 milliseconds later. It returns a job identifier. It does not pause Python for exactly that period, and the callback can run later if the event loop is busy.
The example stores that identifier so another click can cancel the earlier pending job before scheduling a new one. It keeps only one intended pending finish operation. after_cancel uses the identifier, not the callback’s function name.
The scheduled callback must still do short work. after does not turn a slow network call into a background thread. The example changes a message; it does not spend its callback sleeping or running an endless computation.
import tkinter as tk
from tkinter import ttk
root = tk.Tk()
root.title("Responsive scheduled work")
message = tk.StringVar(root, value="Ready")
pending = None
def finish():
global pending
pending = None
message.set("Finished the scheduled step")
def schedule():
global pending
if pending is not None:
root.after_cancel(pending)
message.set("Scheduled; the window is still responsive")
pending = root.after(1500, finish)
def close():
if pending is not None:
root.after_cancel(pending)
root.destroy()
ttk.Label(root, textvariable=message).pack(padx=16, pady=12)
ttk.Button(root, text="Schedule / reschedule", command=schedule).pack(pady=12)
root.protocol("WM_DELETE_WINDOW", close)
root.mainloop()07 | COUNTDOWN TIME IS NOT CALLBACK COUNT
If a timer subtracts one each time after(1000, ...) runs, delays accumulate because callbacks do not occur at perfectly exact intervals. A better countdown stores a deadline using a monotonic clock, then calculates remaining time whenever the display is refreshed.
A monotonic clock measures elapsed time without ordinary wall-clock adjustments making it jump backwards. It is useful for durations, but it is not a calendar date or a saved timestamp.
The planner’s timer schedules short display refreshes about four times per second and checks its deadline. Starting replaces the deadline; cancelling clears it. The timer is session-only, unlike saved study tasks.
08 | TRACE A CALLBACK AND ITS STATE
Click the completion action and follow the order: event, callback, state change, display refresh. The model starts at zero and keeps its count only while this page is open.
09 | CLOSE CLEANLY AND AVOID RE-ENTRANCY
A window-close protocol lets the application cancel scheduled work before destroying widgets. Otherwise a later callback may try to update a widget that no longer exists. The complete planner follows this policy.
Avoid using root.update() as a general repair for a blocking loop. It can process new callbacks while the earlier operation is half finished, making state changes harder to reason about. Break work into small scheduled steps or design a deliberate background-work boundary.
Keep widget access on the GUI thread in this course. If later work uses a worker thread, communicate results back to scheduled GUI callbacks; do not casually update Tk widgets from arbitrary worker code.
10 | TEST THE CHOICES A USER CAN MAKE
Check button clicks, Enter, blank input, repeated actions and closing while scheduled work is pending. A single successful click does not show that all routes behave consistently.
For a saved task, compare the record before and after rejection. For a timer, supply a fake clock to the core model and jump time forward without waiting in a real test. Separate logic from widgets so both can be examined clearly.
OFFICIAL REFERENCE
Use the worked examples first, then consult the official documentation to check widget, input or drawing options. Match it to the version selected in your project environment.
PUT YOUR SKILLS TO WORK
Use the Section 8 challenges to practise these ideas independently. Work in your Python editor, predict results and keep a short test record.
Section 8 challenges Python course navigation