Previous | Tutorial index | Next
command option, the bind() method, and Tkinter variable classes.In traditional procedural programming, you write code that runs from top to bottom, and the program terminates when it finishes. But a GUI application is different—it waits. It waits for the user to do something: click a button, press a key, move the mouse, or close the window.
This paradigm is called event‑driven programming. Your program defines a set of event handlers (functions) and then enters an infinite event loop (mainloop()). The event loop constantly monitors the system for events and dispatches them to the appropriate handlers.
Think of a restaurant:
Tkinter provides three primary mechanisms for handling events:
command – The simplest, used specifically for button‑like widgets.bind() – The most flexible, attaches any event to any widget.StringVar, etc.) – Automatically trigger callbacks when values change via trace().We will explore each in depth.
command Option – The Simplest HandlerThe command option is available on widgets that are "clickable" or "actionable": Button, Checkbutton, Radiobutton, Menu, Scale, and Spinbox. When the user activates the widget (clicks, selects, or changes it), the function referenced by command is called with no arguments (by default).
def say_hello():
print("Hello, Tkinter!")
btn = tk.Button(root, text="Say Hello", command=say_hello)
btn.pack()
Crucial: When you assign command=say_hello, you are passing the function object (without parentheses). If you write command=say_hello(), you would call the function immediately at creation time, and the button would do nothing on click.
command| Widget | When command is triggered |
|---|---|
Button |
On mouse click (left button release). |
Checkbutton |
When the checkbox state is toggled. |
Radiobutton |
When the radio button is selected. |
Menu |
When a menu item is selected. |
Scale |
When the slider is moved (if command is set). |
Spinbox |
When the up/down arrows are clicked or Enter is pressed. |
state Option and Command ExecutionYou can disable a button using state=tk.DISABLED. When disabled, the command will not fire.
btn.config(state=tk.DISABLED) # Grayed out, cannot be clicked
btn.config(state=tk.NORMAL) # Re‑enable
invoke() Method – Programmatic ClickYou can simulate a button click programmatically:
btn.invoke() # Calls the command function immediately
bind() Method – Universal Event Handlingbind()?command is limited to a small set of widgets and only handles the "activation" event. bind() works with any widget (including root, Frame, Label) and can listen for any event—key presses, mouse movements, focus changes, window resizing, etc.
widget.bind(event_sequence, callback_function)
event_sequence – a string describing the event (e.g., "<Button-1>").callback_function – a function that receives one argument, an Event object containing details about the event.When a bound callback is called, Tkinter passes an Event object with useful attributes:
| Attribute | Description |
|---|---|
event.widget |
The widget that triggered the event. |
event.char |
The character pressed (for <Key> events). |
event.keysym |
The symbolic name of the key (e.g., "Return", "Escape"). |
event.keycode |
The numeric key code. |
event.x, event.y |
Mouse coordinates relative to the widget. |
event.x_root, event.y_root |
Mouse coordinates relative to the screen. |
event.num |
Mouse button number (1 = left, 2 = middle, 3 = right). |
event.type |
The type of event (e.g., "2" for KeyPress). |
| Event Pattern | Meaning |
|---|---|
<Button-1> |
Left mouse button click. |
<Button-2> |
Middle mouse button click. |
<Button-3> |
Right mouse button click. |
<Double-Button-1> |
Double‑click left mouse button. |
<Key> |
Any key press (returns the character). |
<KeyPress-Return> |
Pressing the Enter/Return key. |
<KeyPress-Escape> |
Pressing the Escape key. |
<Control-KeyPress-c> |
Ctrl+C (or Cmd+C on macOS). |
<FocusIn> |
Widget receives keyboard focus. |
<FocusOut> |
Widget loses keyboard focus. |
<Enter> |
Mouse pointer enters the widget area. |
<Leave> |
Mouse pointer leaves the widget area. |
<Motion> |
Mouse moves inside the widget. |
<Configure> |
Widget is resized or moved. |
<Destroy> |
Widget is about to be destroyed. |
Tkinter has three levels of binding, in order of precedence (highest to lowest):
widget.bind(event, handler) – specific to one widget.widget.bind_class(className, event, handler) – applies to all widgets of a class (e.g., all Entry widgets).root.bind_all(event, handler) – applies to all widgets in the application, globally.Crucial: Multiple bindings can fire for the same event. A key press, for example, will first trigger an instance binding, then a class binding, then an application binding.
return "break"If you want to prevent further handlers from processing the event (e.g., stop an Entry widget from inserting a character), return the string "break" from your callback:
def prevent_input(event):
print("No typing allowed!")
return "break" # Prevents the character from being inserted
entry.bind("<Key>", prevent_input)
unbind()To remove a binding, use:
widget.unbind("<Key>") # Removes the binding for that event on this widget
StringVar, IntVar, DoubleVar, BooleanVarThese are Tkinter's special "observable" variables. When you change their value using .set(value), any widget that is linked to them via the textvariable, variable, or value options automatically updates its display. This is much more efficient than manually calling .config().
name_var = tk.StringVar() # Default value is empty string
age_var = tk.IntVar(value=18) # Default value is 18
is_student = tk.BooleanVar(value=True)
# Link to Entry – changes to the Entry update the variable, and vice versa
entry = tk.Entry(root, textvariable=name_var)
entry.pack()
# Link to Label – shows whatever is in name_var
label = tk.Label(root, textvariable=name_var)
label.pack()
# Link to Checkbutton
check_var = tk.IntVar()
check = tk.Checkbutton(root, text="Agree", variable=check_var)
trace()trace() allows you to attach a callback that runs whenever the variable changes. This is incredibly powerful for validation, auto‑completion, and dynamic UI updates.
Syntax:
variable.trace(mode, callback)
mode – "w" (write, when value changes), "r" (read, when value is accessed), "u" (unset, when variable is deleted), or a combination.callback – a function that takes three arguments: (var_name, var_index, operation).Example: Auto‑uppercase
def on_name_change(var_name, var_index, operation):
current = name_var.get()
name_var.set(current.upper()) # Force uppercase
name_var = tk.StringVar()
name_var.trace("w", on_name_change)
entry = tk.Entry(root, textvariable=name_var)
entry.pack()
Caution: Recursive modification (changing the variable inside its own trace callback) can cause infinite loops. Use carefully.
The .trace() method returns a trace ID (a string). You can remove it using .trace_remove(trace_id).
trace_id = name_var.trace("w", callback)
# Later:
name_var.trace_remove(trace_id)
The command option and bind() callbacks are designed to receive no arguments (command) or one argument (the event object). But what if you need to pass custom data to your function?
A lambda function acts as a wrapper that calls your function with the required arguments.
def save_file(filename):
print(f"Saving {filename}...")
btn1 = tk.Button(root, text="Save Report", command=lambda: save_file("report.txt"))
btn2 = tk.Button(root, text="Save Data", command=lambda: save_file("data.csv"))
This is one of the most common bugs in Tkinter. If you create lambda functions inside a loop and capture a loop variable, all lambdas may end up using the last value of that variable.
Buggy Example:
buttons = []
for i in range(3):
btn = tk.Button(root, text=f"Button {i}", command=lambda: print(i))
btn.pack()
buttons.append(btn)
# Clicking any button prints "2" (the last value), not the expected 0, 1, 2!
Why? The lambda function does not evaluate i at the time of creation; it captures a reference to the variable i. By the time you click the button, the loop has finished and i == 2.
The Fix – Default Argument Binding:
for i in range(3):
btn = tk.Button(root, text=f"Button {i}", command=lambda val=i: print(val))
btn.pack()
Here, val=i evaluates i immediately and stores its value in the lambda's default argument.
bind() – Passing Extra ArgumentsSince bind() callbacks receive an event object, you can use a lambda to pass both the event and custom data:
def on_click(event, custom_arg):
print(f"Clicked {custom_arg} at x={event.x}")
widget.bind("<Button-1>", lambda event: on_click(event, "Hello"))
functools.partialAn alternative to lambda is functools.partial, which is sometimes more readable:
from functools import partial
btn = tk.Button(root, text="Save", command=partial(save_file, "report.txt"))
You can bind global keyboard shortcuts to the root window:
root.bind("<Control-s>", lambda e: save_file("document.txt"))
root.bind("<Escape>", lambda e: root.destroy())
On macOS, Command is represented by <Command> or <Meta>.
<MouseWheel><MouseWheel> also works, but you need to check event.delta for direction.Some widgets (like Listbox) require double‑click detection:
def on_double_click(event):
widget = event.widget
selection = widget.curselection()
if selection:
print(f"Selected item: {widget.get(selection[0])}")
listbox.bind("<Double-Button-1>", on_double_click)
break – Preventing Default BehaviorReturning "break" stops the event from being processed further. This is useful for custom input validation:
def numeric_only(event):
if not event.char.isdigit():
return "break" # Don't insert the character
entry.bind("<Key>", numeric_only)
command option?
ButtonLabelCheckbuttonRadiobuttonbind() receives how many arguments?
event.keyevent.charevent.valueevent.keysymWhy does the following code print "2" for all buttons when clicked?
for i in range(3):
tk.Button(root, text=str(i), command=lambda: print(i)).pack()
i by reference, and by the time you click, i == 2.i is a local variable that is garbage‑collected.event.stop()return "break"event.prevent()return Falsecommand option can be used with an Entry widget to validate text as the user types.StringVar, IntVar) automatically update their linked widgets without any extra calls.<Key> event triggers for modifier keys like Shift and Ctrl.trace() method on a StringVar can be used to run a callback every time the variable is read ("r" mode).command callback, you typically use a ________ function.<________-1>.command is .________()..________(event_sequence)."w" (write), "r" (read), and "____" (unset).Create a window with three buttons labelled "Red", "Green", and "Blue". When clicked, each button should change the background colour of a central Label to the corresponding colour. Use a single callback function that accepts a colour argument, and use lambda (with default argument binding) to pass the colours.
Create a simple dashboard with a Label that displays the text "Press a key". Bind the <Key> event to the root window. Whenever the user presses a key, update the label to show: "Key pressed: {char} (Keysym: {keysym}, Code: {keycode})". Use the event object to extract the information.
traceCreate an Entry that only accepts numeric digits (0‑9). Use a StringVar with a trace("w", callback) that checks the new value. If the new value contains any non‑digit, revert it to the previous valid value (you will need to store the previous value in a separate variable).
Draw a circle on a Canvas. Bind <Enter> and <Leave> events to the circle (or the canvas) to change its colour when the mouse hovers over it. Also bind <Button-1> to print the (x, y) coordinates of the click on the canvas.
Write a loop that creates 5 buttons numbered 1 to 5. Each button, when clicked, should print its own number. You must use a lambda and avoid the late‑binding trap. Test it thoroughly.
Demonstrate mastery of event handling by building a semi‑functional GUI application that uses command, bind, variable trace, and lambda functions appropriately.
Build a simple drawing tool on a Canvas (400x400). The app must have:
<Button-1> (mouse click) on the canvas should draw a small filled circle (radius 5) at the click position.<B1-Motion> (mouse drag) should draw circles along the mouse path (creating a continuous line effect). Hint: This is a classic drawing app feature.<Button-3> (right‑click) on the canvas should change the drawing colour to a random colour from a predefined list.c key to clear the canvas.d key to toggle between "Draw mode" (always draw circles) and "Eraser mode" (draw white circles to erase). Use a StringVar to store the current mode and display it in a Label.StringVar linked to a Label that shows the current mouse coordinates as you move the mouse over the canvas (use <Motion> event).Requirements:
command for the buttons.bind() for mouse and key events.StringVar for the mode and trace it to update the mode label (or just update the label directly, but use a trace to demonstrate understanding).The following code is supposed to create a form where typing in the first Entry automatically copies the text to a second Entry (mirroring), but it has three distinct errors. Identify each error, explain the consequence, and provide the corrected code.
import tkinter as tk
root = tk.Tk()
def copy_text():
# Error 1: Wrong way to get/set text
text = entry1.get()
entry2.config(text=text) # Error 2: Entry uses textvariable, not text
entry1 = tk.Entry(root)
entry1.pack()
entry2 = tk.Entry(root)
entry2.pack()
# Error 3: Binding to the wrong event and wrong target
entry1.bind("<Button-1>", copy_text)
root.mainloop()
Note: Entry widgets use textvariable or .insert()/.delete()/.get(), not .config(text=...).
Corrected code:
import tkinter as tk
root = tk.Tk()
def copy_text(event):
entry2.delete(0, tk.END)
entry2.insert(0, entry1.get())
entry1 = tk.Entry(root)
entry1.pack()
entry2 = tk.Entry(root)
entry2.pack()
entry1.bind("<KeyRelease>", copy_text)
root.mainloop()
(Alternative: use StringVar with trace for cleaner solution.)
Given the following code that creates a grid of buttons (3x3) representing a numeric keypad, explain why all buttons print 8 when clicked. Then rewrite the loop correctly using either lambda with default arguments or functools.partial.
buttons = []
for row in range(3):
for col in range(3):
num = row * 3 + col
btn = tk.Button(root, text=str(num), command=lambda: print(num))
btn.grid(row=row, column=col)
buttons.append(btn)
Fix using default argument:
btn = tk.Button(root, text=str(num), command=lambda val=num: print(val))
Or using functools.partial:
from functools import partial
btn = tk.Button(root, text=str(num), command=partial(print, num))
Write a complete, runnable script that:
Entry widget linked to a StringVar.Label that shows the current length of the text in the entry.trace("w") on the StringVar to update the label with the character count every time the user types."Length: 0" initially.Write a short essay (7‑10 sentences) explaining the fundamental difference between event‑driven programming and procedural programming. Use the restaurant analogy or create your own. Explain how the mainloop() acts as the "engine" and why command, bind, and trace are all necessary in different situations.
| Term | Definition |
|---|---|
| Event | An action performed by the user (click, key press, mouse movement) or the system (window resize). |
Event Loop (mainloop) |
The infinite loop that waits for events and dispatches them to handlers. |
| Callback | A function that is called when an event occurs. |
command |
The simplest callback mechanism, available on actionable widgets, called with no arguments. |
bind() |
A method that attaches an event handler to any widget for any event type. |
| Event Object | An object passed to a bind() callback, containing details about the event (coordinates, key, widget, etc.). |
break |
A special return value that stops further processing of an event. |
StringVar / IntVar / etc. |
Tkinter variable classes that synchronise with widgets and support trace(). |
trace() |
A method on Tkinter variables that calls a callback when the variable is read, written, or deleted. |
| Late Binding Trap | The phenomenon where lambda functions capture variables by reference, causing all instances to use the last value. Solved by default arguments. |
unbind() |
Removes an event binding from a widget. |
<Key>, <Button>, <Configure>, etc.).Event object.trace for model‑view‑controller (MVC) patterns in Tkinter.trace to highlight search terms as you type them.This tutorial is designed to take approximately 3 hours of study, lab work, and homework. Event handling is what breathes life into your GUI – mastering it separates a static display from a truly interactive application.