We met thebind
widget
method in the prior chapter, when we used it to catch button
presses in the tutorial. Becausebind
is commonly used in conjunction with other widgets (e.g., to catch return
key presses for input boxes), we’re going to make a stop early in the tour
here as well.
Example 8-15
illustrates morebind
event
protocols.
Example 8-15. PP4E\Gui\Tour\bind.py
from tkinter import *
def showPosEvent(event):
print('Widget=%s X=%s Y=%s' % (event.widget, event.x, event.y))
def showAllEvent(event):
print(event)
for attr in dir(event):
if not attr.startswith('__'):
print(attr, '=>', getattr(event, attr))
def onKeyPress(event):
print('Got key press:', event.char)
def onArrowKey(event):
print('Got up arrow key press')
def onReturnKey(event):
print('Got return key press')
def onLeftClick(event):
print('Got left mouse button click:', end=' ')
showPosEvent(event)
def onRightClick(event):
print('Got right mouse button click:', end=' ')
showPosEvent(event)
def onMiddleClick(event):
print('Got middle mouse button click:', end=' ')
showPosEvent(event)
showAllEvent(event)
def onLeftDrag(event):
print('Got left mouse button drag:', end=' ')
showPosEvent(event)
def onDoubleLeftClick(event):
print('Got double left mouse click', end=' ')
showPosEvent(event)
tkroot.quit()
tkroot = Tk()
labelfont = ('courier', 20, 'bold') # family, size, style
widget = Label(tkroot, text='Hello bind world')
widget.config(bg='red', font=labelfont) # red background, large font
widget.config(height=5, width=20) # initial size: lines,chars
widget.pack(expand=YES, fill=BOTH)
widget.bind('', onLeftClick) # mouse button clicks
widget.bind('', onRightClick)
widget.bind('', onMiddleClick) # middle=both on some mice
widget.bind('', onDoubleLeftClick) # click left twice
widget.bind('', onLeftDrag) # click left and move
widget.bind('', onKeyPress) # all keyboard presses
widget.bind('', onArrowKey) # arrow button pressed
widget.bind('', onReturnKey) # return/enter key pressed
widget.focus() # or bind keypress to tkroot
tkroot.title('Click Me')
tkroot.mainloop()
Most of this file consists of
callback handler functions triggered when bound events
occur. As we learned in
Chapter 7
, this
type of callback receives an event object argument that gives details
about the event that fired. Technically, this argument is an instance of
the tkinterEvent
class, and its
details are attributes; most of the callbacks simply trace events by
displaying relevant event attributes.
When run, this script makes the window shown in
Figure 8-20
; it’s mostly intended just as
a surface for clicking and pressing event triggers.
Figure 8-20. A bind window for the clicking
The black-and-white medium of the book you’re holding won’t really
do justice to this script. When run live, it uses the configuration
options shown earlier to make the
window
show up as black on red, with a large
Courier font. You’ll have to take my word for it (or run this on your
own).
But the main point of this example is to demonstrate other kinds of
event binding protocols at work. We saw a script that intercepted left and
double-left mouse clicks with the widgetbind
method in
Chapter 7
, using event names
and
; the
script here demonstrates other kinds of events that are
commonly caught withbind
:
To catch the press of a single key on the keyboard, register a
handler for the
event identifier; this is a lower-level way to input data in GUI
programs than theEntry
widget
covered in the next section. The key pressed is returned in ASCII
string form in the event object passed to the callback handler
(event.char
). Other attributes in
the event structure identify the key pressed in lower-level detail.
Key presses can be intercepted by the top-level root window widget
or by a widget that has been assigned keyboard focus with thefocus
method used by this
script.
This script also catches mouse motion while a button is held
down: the registered
event handler is called
every time the mouse is moved while the left button is pressed and
receives the current X/Y coordinates of the mouse pointer in its
event argument (event.x
,event.y
). Such information can be used to
implement object moves, drag-and-drop, pixel-level painting, and so
on (e.g., see the PyDraw examples in
Chapter 11
).
,
This script also catches right and middle mouse button clicks
(known as buttons 3 and 2). To make the middle button 2 click work
on a two-button mouse, try clicking both buttons at the same time;
if that doesn’t work, check your mouse setting in your properties
interface (the Control Panel on Windows).
,
To catch more specific kinds of key presses, this script
registers for the Return/Enter and up-arrow key press events; these
events would otherwise be routed to the general
handler and require event
analysis.
Here is what shows up in thestdout
output stream after a left click, right
click, left click and drag, a few key presses, a Return and up-arrow
press, and a final double-left click to exit. When you press the left
mouse button and drag it around on the display, you’ll get lots of drag
event messages; one is printed for every move during the drag (and one
Python callback is run for each):
C:\...\PP4E\Gui\Tour>python bind.py
Got left mouse button click: Widget=.25763696 X=376 Y=53
Got right mouse button click: Widget=.25763696 X=36 Y=60
Got left mouse button click: Widget=.25763696 X=144 Y=43
Got left mouse button drag: Widget=.25763696 X=144 Y=45
Got left mouse button drag: Widget=.25763696 X=144 Y=47
Got left mouse button drag: Widget=.25763696 X=145 Y=50
Got left mouse button drag: Widget=.25763696 X=146 Y=51
Got left mouse button drag: Widget=.25763696 X=149 Y=53
Got key press: s
Got key press: p
Got key press: a
Got key press: m
Got key press: 1
Got key press: -
Got key press: 2
Got key press: .
Got return key press
Got up arrow key press
Got left mouse button click: Widget=.25763696 X=300 Y=68
Got double left mouse click Widget=.25763696 X=300 Y=68
For mouse-related events, callbacks print the X and Y coordinates of
the mouse pointer, in the event object passed in. Coordinates are usually
measured in pixels from the upper-left corner (0,0), but are relative to
the widget being clicked. Here’s what is printed for a left, middle, and
double-left click. Notice that the middle-click callback dumps the entire
argument—all of theEvent
object’s
attributes (less internal names that begin with “__” which includes the__doc__
string, and default operator
overloading methods inherited from the impliedobject
superclass in Python 3.X). Different
event types set different event attributes; most key presses put something
inchar
, for instance:
C:\...\PP4E\Gui\Tour>python bind.py
Got left mouse button click: Widget=.25632624 X=6 Y=6
Got middle mouse button click: Widget=.25632624 X=212 Y=95
char => ??
delta => 0
height => ??
keycode => ??
keysym => ??
keysym_num => ??
num => 2
send_event => False
serial => 17
state => 0
time => 549707945
type => 4
widget => .25632624
width => ??
x => 212
x_root => 311
y => 95
y_root => 221
Got left mouse button click: Widget=.25632624 X=400 Y=183
Got double left mouse click Widget=.25632624 X=400 Y=183
Besides those illustrated in this example, a tkinter script can
register to catch additional kinds of bindable events. For
example:
fires
when a button is released (
is run when the button
first goes down).
is triggered
when a mouse pointer is moved.
and
handlers intercept mouse
entry and exit in a window’s display area (useful for automatically
highlighting a widget).
is
invoked when the window is resized, repositioned, and so on (e.g.,
the event object’swidth
andheight
give the new window size).
We’ll make use of this to resize the display on window resizes in
the PyClock example of
Chapter 11
.
is invoked
when the window widget is destroyed (and differs from theprotocol
mechanism for window manager
close button presses). Since this interacts with widgetquit
anddestroy
methods, I’ll say more about the
event later in this section.
and
are run as the
widget gains and loses focus.
and
are run when a window is
opened and iconified.
,
, and
catch other special key
presses.
,
, and
catch other arrow key
presses.
This is not a complete list, and event names can be written with a
somewhat sophisticated syntax of their own. For instance:
Modifiers
can be added to event
identifiers to make them even more specific; for instance,
means moving the mouse
with the left button pressed, and
refers to pressing the
“a” key only.
Synonyms
can be used for some common
event names; for instance,
,
, and<1>
mean a left mouse button press,
and
and
mean the “a” key.
All forms are case sensitive: use
, not
.
Virtual
event identifiers can be defined
within double bracket pairs (e.g.,<
) to refer to a
selection of one or more event sequences.
In the interest of space, though, we’ll defer to other Tk and
tkinter reference sources for an exhaustive list of details on this
front. Alternatively, changing some of the settings in the example
script and rerunning can help clarify some event behavior, too; this is
Python, after all.
Before we move on, one event merits a few extra words: the
event (whose name
is case significant) is run when a widget is being destroyed, as a
result of both script method calls and window closures in general,
including those at program exit. If you bind this on a window, it will
be triggered once for each widget in the window; the callback’s event
argumentwidget
attribute gives the
widget being destroyed, and you can check this to detect a particular
widget’s destruction. If you bind this on a specific widget instead,
it will be triggered once for that widget’s destruction only.
It’s important to know that a widget is in a “half dead” state
(Tk’s terminology) when this event is triggered—it still exists, but
most operations on it fail. Because of that, the
event is not intended for
GUI activity in general; for instance, checking a text widget’s
changed state or fetching its content in a
handler can both fail with
exceptions. In addition, this event’s handler cannot cancel the
destruction in general and resume the GUI; if you wish to intercept
and verify or suppress window closes when a user clicks on a window’sX
button, useWM_DELETE_WINDOW
in top-level windows’protocol
methods as described
earlier in this chapter.
You should also know that running a tkinter widget’squit
method does not trigger any
events on exit, and even
leads to a fatal Python error on program exit in 3.X if any
event handlers are
registered. Because of this, programs that bind this event for non-GUI
window exit actions should usually calldestroy
instead ofquit
to close, and rely on the fact that a
program exits when the last remaining or onlyTk
root window (default or explicit) is
destroyed as described earlier. This precludes usingquit
for immediate shutdowns, though you can
still runsys.exit
for brute-force
exits.
A script can also perform program exit actions in code run after
themainloop
call returns, but the
GUI is gone completely at this point, and this code is not associated
with any particular widget. Watch for more on this event when we study
the PyEdit example program in
Chapter 11
; at the risk of spoiling the end of
this story, we’ll find it unusable for verifying changed text
saves.