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()
An event pattern looks like <modifier-type-detail>. The common ones:
| Pattern | Fires 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 handler is called with one argument carrying the details.
| Attribute | Holds |
|---|---|
x, y | Pointer position inside the widget |
x_root, y_root | Position on the whole screen, for placing menus |
char | The character typed, for key events |
keysym | The key name, such as Return or Left |
num | Which mouse button |
width, height | The new size, on a Configure event |
widget | The widget the event happened in |
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.
| Call | Scope |
|---|---|
widget.bind(...) | That one widget |
widget.bind_class("Entry", ...) | Every widget of that class |
widget.bind_all(...) | The whole application |
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)
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.