Home / Library / Tkinter / Introduction / Events and bindings

Events and bindings

Buttons take a command. Everything else, keys, the mouse, resizing, focus, goes through bind.

import tkinter as tk

def clicked(event):
    print("click at", event.x, event.y)

root = tk.Tk()
canvas = tk.Canvas(root, width=300, height=200, background="white")
canvas.pack()
canvas.bind("<Button-1>", clicked)
root.mainloop()

The pattern

An event pattern looks like <modifier-type-detail>. The common ones:

PatternFires when
<Button-1>The left mouse button goes down. 2 is middle, 3 is right
<ButtonRelease-1>It comes back up
<Double-Button-1>A double click
<B1-Motion>The mouse moves with the left button held, for dragging
<Motion>The mouse moves at all
<Enter>, <Leave>The pointer enters or leaves the widget. Not the Enter key
<Return>The Enter key
<Key>Any key. The letter is in event.char
<Control-s>Control and s together
<Configure>The widget is resized or moved
<FocusIn>, <FocusOut>Keyboard focus arrives or leaves

The event object

The handler is called with one argument carrying the details.

AttributeHolds
x, yPointer position inside the widget
x_root, y_rootPosition on the whole screen, for placing menus
charThe character typed, for key events
keysymThe key name, such as Return or Left
numWhich mouse button
width, heightThe new size, on a Configure event
widgetThe widget the event happened in

Who receives it

Mouse events go to the widget under the pointer. Key events go to whichever widget has focus, and a plain frame or canvas does not take focus until you ask for it:

canvas.focus_set()
canvas.bind("<Key>", pressed)

Binding on root instead catches the key wherever focus is, which is what you want for application shortcuts.

Three levels of binding

CallScope
widget.bind(...)That one widget
widget.bind_class("Entry", ...)Every widget of that class
widget.bind_all(...)The whole application

Closing the window

The window manager close button is not an ordinary event. Catch it with a protocol handler:

def closing():
    if messagebox.askokcancel("Quit", "Close without saving?"):
        root.destroy()

root.protocol("WM_DELETE_WINDOW", closing)

Timers, and not blocking

There is no sleep in an event driven program. Anything slow freezes the window, because Tk cannot process events while your function runs. Use after:

def tick(n=0):
    label.config(text=str(n))
    root.after(1000, tick, n + 1)

tick()

after is also the only safe way to update a widget from a background thread. Tk is not thread safe, so threads should post work back to the main loop rather than touching widgets directly.

Related