Mark a window busy#
For a slow operation you want to both show a wait cursor and stop the user
clicking meanwhile. busy() does both: it covers the widget with a
transparent overlay that swallows mouse-pointer events and shows a busy cursor,
until you release it with busy_forget():
app.busy(cursor="watch")
app.update() # paint the overlay + cursor before blocking
do_slow_work()
app.busy_forget()
The update call matters. The overlay is only mapped when the event loop next
runs, so without it the user sees nothing before the blocking work starts —
and Tk’s own docs recommend a full update here, to guarantee the hold takes
effect before any queued clicks are dispatched.
Always release it#
Pair the calls with try/finally so an exception can’t strand the app
looking busy forever:
app.busy(cursor="watch")
app.update()
try:
do_slow_work()
finally:
app.busy_forget()
busy_status() reports whether a widget is currently held busy — useful to
keep a slow action from being started twice:
if not app.busy_status():
start_work()
Warning
The busy overlay is not supported on macOS. The calls succeed and
busy_status() still reports True, but nothing is drawn and nothing is
blocked — so it fails quietly rather than raising.
On macOS, get the same effect by disabling the controls that start the work and setting the cursor yourself:
def busy(is_busy):
app.configure(cursor="watch" if is_busy else "")
run.configure(state="disabled" if is_busy else "normal")
A cursor set on the window covers everything inside it — a child with no cursor of its own inherits its parent’s. This works on every platform.
Note
busy() works on every Python ttkbootstrap supports. tkinter only grew
these methods in 3.13; on 3.10–3.12 ttkbootstrap supplies them, issuing the
same Tk command underneath. They are also spelled tk_busy_hold /
tk_busy_forget / tk_busy_status / tk_busy_configure — the same
calls under their longer names.
Note
busy() differs from simply setting cursor="watch": besides the cursor,
it blocks pointer events to the widget and everything inside it. It does
not block the keyboard — a widget that already had focus keeps receiving
keystrokes, so move focus off the busy area if that matters. Set cursor=
alone (see Cursors) when you only want to change
the pointer.
Long work still needs a thread#
The busy state only helps for work short enough to block on briefly — while the
main loop is blocked, the overlay is the only thing the window can do, and it
cannot repaint or animate. For genuinely long work, run it on a background thread
and marshal results back with after, so the UI stays responsive while the
busy state is up.
See also
Run background work — the thread-and-
afterpattern for work too long to block on.Ring the system bell — the other half of telling the user what is going on.
Windows guide — window-level behavior.
Reference#
Cursors — the pointer names you can set.