Events#
Every tkinter app is event-driven: your code runs in callbacks that fire in
response to clicks, keystrokes, and higher-level notifications. The
Events & callbacks
foundations page covers the essentials — command, bind, and after.
This guide goes deeper into the binding system: how an event finds its
callbacks, how to share and stop bindings, and how to define and dispatch your
own events.
Binding scope#
widget.bind attaches a callback to one widget. But every event actually
consults four levels of bindings in turn, and understanding them is what lets
you write shortcuts and shared behavior instead of wiring every widget by hand:
Instance —
widget.bind(pattern, callback)— this one widget only.Class —
widget.bind_class("TEntry", pattern, callback)— every widget of that Tk class at once. This is the level a widget’s default behavior lives at (what makes anEntryinsert the character you type).Toplevel — bindings on the containing window, seen by any widget in it.
Application —
widget.bind_all(pattern, callback)— every widget in the program. The right tool for a global shortcut like<F1>or<Control-q>.
A widget’s class name for bind_class is widget.winfo_class() —
"TEntry", "TButton", "Text", and so on.
The order events travel#
When an event fires, tkinter walks those four levels in order and runs every matching binding it finds. That order is the widget’s bindtags:
entry.bindtags()
# ('.!entry', 'TEntry', '.', 'all')
# instance class toplevel application
Two consequences worth internalizing:
Your instance binding runs before the class binding — which is why returning
"break"from it can pre-empt the widget’s default behavior (see Stopping an event).A matching binding at any level fires, so one
bind_all("<F1>", …)covers the whole application.
You can reorder or insert tags with widget.bindtags((...)) for advanced
cases, but the default order serves almost everything.
Adding vs replacing#
Within a single tag, binding a pattern replaces whatever callback was already bound to that same pattern — a real footgun when two parts of your code each assume they own the event:
widget.bind("<Button-1>", first)
widget.bind("<Button-1>", second) # first is silently gone
widget.bind("<Button-1>", third, add="+") # third runs alongside second
Pass add="+" to add a callback instead of replacing it; every bound
callback then fires, in the order added. Reach for it whenever a handler should
coexist with others — especially the app-wide events under Generating events,
where each consumer must add its own handler without clobbering the rest.
bind returns an id that unbind uses to remove a single callback again:
handler_id = widget.bind("<Motion>", track)
widget.unbind("<Motion>", handler_id)
Stopping an event#
A widget’s class binding implements its default behavior — a Text widget
inserting the character you typed, for instance. Because the instance binding
runs first, returning the string "break" from your callback halts the chain,
so the class binding never runs and the default is suppressed:
def tab_moves_focus(event):
event.widget.tk_focusNext().focus()
return "break" # stop Text from inserting a literal tab
text.bind("<Tab>", tab_moves_focus)
Virtual events#
Virtual events are named, higher-level notifications written in double angle
brackets, <<Like this>>. They fire when something meaningful happens rather
than for a raw input, and you bind them exactly like physical events.
ttkbootstrap emits two you will use:
<<ThemeChanged>>— fires on every widget after the theme is switched. Bind it to recolor anything you drew yourself (aCanvas, a custom asset):canvas.bind("<<ThemeChanged>>", lambda e: repaint(canvas))
<<LocaleChanged>>— fires when the app’s locale changes; this is what drivesLocaleVar(see Variables & reactivity).
Prefer the callback helpers for theme changes
For rebuilding a custom style after a theme switch, ttkbootstrap offers
on_theme_change() and the @theme_aware decorator,
which are easier than a raw <<ThemeChanged>> binding. See
Custom styles.
Generating events#
You can fire an event yourself with event_generate — most usefully your own
virtual events. A virtual event lets one part of an app announce that
something happened without knowing who cares: a producer generates it, and any
number of consumers bind to it. That decouples components cleanly.
# a producer announces on the root — it calls no one directly
def load_data():
... # do the work
app.event_generate("<<DataLoaded>>")
# consumers register on the SAME widget, each with add="+" so they stack
app.bind("<<DataLoaded>>", lambda e: status.configure(text="Loaded"), add="+")
app.bind("<<DataLoaded>>", lambda e: table.refresh(), add="+")
The name in double brackets is all you need — a <<virtual>> event works the
moment you bind and generate it.
Important
event_generate dispatches to one widget (and its bindtags); it
does not broadcast. Bind consumers on the same widget you generate on — the
root makes a natural app-wide bus — or use bind_all for a truly global
signal. Use add="+" so each consumer adds a handler rather than replacing
the previous one.
event_generate also synthesizes physical events, though it’s less common
— a bare <KeyRelease> needs the widget focused and the relevant fields for
that event type supplied (keysym for keys, x/y for pointer events) to
behave like real input.
To make a virtual event fire from the keyboard too, alias it to physical key
sequences with event_add:
app.event_add("<<Save>>", "<Control-s>", "<Control-S>")
app.bind("<<Save>>", lambda e: save()) # Ctrl-S now fires <<Save>>
Note
event_generate accepts data="…", but tkinter does not surface it on
the event the callback receives, so it cannot be read back. To pass
information with a virtual event, store it where both sides can reach it. See
the event object.
See also
Events & callbacks — the basics.
Event reference — the complete catalog: every event type, modifier, key symbol, and event attribute.