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

Reference#

  • Cursors — the pointer names you can set.