Listbox
widgets allow you to
display a list of items for selection, and Scrollbar
s are designed for navigating
through the contents of other widgets. Because it is common to use
these widgets together, let’s study them both at once. Example 10-9 builds both a
Listbox
and a Scrollbar
, as a packaged set.
Example 10-9. PP3EGuiTourscrolledlist.py
from Tkinter import * class ScrolledList(Frame): def _ _init_ _(self, options, parent=None): Frame._ _init_ _(self, parent) self.pack(expand=YES, fill=BOTH) # make me expandable self.makeWidgets(options) def handleList(self, event): index = self.listbox.curselection( ) # on list double-click label = self.listbox.get(index) # fetch selection text self.runCommand(label) # and call action here def makeWidgets(self, options): # or get(ACTIVE) sbar = Scrollbar(self) list = Listbox(self, relief=SUNKEN) sbar.config(command=list.yview) # xlink sbar and list list.config(yscrollcommand=sbar.set) # move one moves other sbar.pack(side=RIGHT, fill=Y) # pack first=clip last list.pack(side=LEFT, expand=YES, fill=BOTH) # list clipped first pos = 0 for label in options: # add to listbox list.insert(pos, label) # or insert(END,label) pos += 1 #list.config(selectmode=SINGLE, setgrid=1) # select,resize modes list.bind('<Double-1>', self.handleList) # set event handler self.listbox = list def runCommand(self, selection): # redefine me lower print 'You selected:', selection if _ _name_ _ == '_ _main_ _': options = map((lambda x: 'Lumberjack-' + str(x)), range(20)) #options = [('Lumberjack-%s' % x) for x in range(20)] ScrolledList(options).mainloop( )
This module can be run standalone to experiment with these
widgets, but is also designed to be useful as a library object. By
passing in different selection lists to the options
argument and redefining the runCommand
method in a subclass, the
ScrolledList
component class
defined here can be reused anytime you need to display a scrollable
list. With just a little forethought, it’s easy to extend the Tkinter
library with Python classes.
When run standalone, this script generates the window shown in
Figure 10-12. It’s a
Frame
, with a Listbox
on its left containing 20 generated
entries (the 5th has been clicked), along with an associated Scrollbar
on its right for moving through
the list. If you move the scroll, the list moves, and vice
versa.
Listboxes are straightforward to use, but they are
populated and processed in somewhat unique ways compared to the
widgets we’ve seen so far. Many listbox calls accept a passed-in
index to refer to an entry in the list. Indexes start at integer 0
and grow higher, but Tkinter also accepts special name strings in
place of integer offsets: end
to
refer to the end of the list, active
to denote the line selected, and
more. This generally yields more than one way to code listbox
calls.
For instance, this script adds items to the listbox in this
window by calling its insert
method, with successive offsets (starting at zero):
list.insert(pos, label) pos += 1
But you can also fill a list by simply adding items at the end without keeping a position counter at all, with either of these statements:
list.insert('end', label) # add at end: no need to count positions list.insert(END, label) # END is preset to 'end' inside Tkinter
The listbox widget doesn’t have anything like the command
option we use to register callback
handlers for button presses, so you either need to fetch listbox
selections while processing other widgets’ events (e.g., a button
press elsewhere in the GUI) or tap into other event protocols to
process user selections. To fetch a selected value, this script
binds the <Double-1>
left
mouse button double-click event to a callback handler method with
bind
(seen earlier on this
tour).
In the double-click handler, this script grabs the selected item out of the listbox with this pair of listbox method calls:
index = self.listbox.curselection( ) # get selection index label = self.listbox.get(index) # fetch text by its index
Here, too, you can code this differently. Either of the
following lines has the same effect; they get the contents of the
line at index 'active'
—the one
selected:
label = self.listbox.get('active') # fetch from active index label = self.listbox.get(ACTIVE) # ACTIVE='active' in Tkinter
For illustration purposes, the class’s default runCommand
method prints the value
selected each time you double-click an entry in the list—as fetched
by this script, it comes back as a string reflecting the text in the
selected entry:
C:...PP3EGuiTour>python scrolledlist.py
You selected: Lumberjack-2
You selected: Lumberjack-19
You selected: Lumberjack-4
You selected: Lumberjack-12
Listboxes can also be useful input devices even without
attached scroll bars; they accept color, font, and relief
configuration options. They also support both single and multiple
selection modes. The default mode allows only a single item to be
selected, but the selectmode
argument supports four settings: SINGLE
, BROWSE
, MULTIPLE
, and EXTENDED
(the default is BROWSE
). Of these, the first two are
single selection modes, and the last two allow multiple items to be
selected.
These modes vary is subtle ways. For instance, BROWSE
is like SINGLE
, but it also allows the selection
to be dragged. Clicking an item in MULTIPLE
mode toggles its state without
affecting other selected items. And the EXTENDED
mode allows for multiple
selections and works like the Windows file explorer GUI—you select
one item with a simple click, multiple items with a Ctrl-click
combination, and ranges of items with Shift-clicks. Multiple
selections can be programmed with code of this sort:
listbox = Listbox(window, bg='white', font=('courier', fontsz)) listbox.config(selectmode=EXTENDED) listbox.bind('<Double-1>', (lambda event: onDoubleClick( ))) # get messages selected in listbox selections = listbox.curselection( ) # tuple of digit strs, 0..N-1 selections = [int(x)+1 for x in selections] # convert to ints, make 1..N
When multiple selections are enabled, the curselection
method returns a list of
digit strings giving the relative numbers of the items selected, or
it returns an empty tuple if none is selected. Really, this method
always returns a tuple of digit strings, even in single selection
mode (we don’t care in Example
10-9, because the get
method does the right thing for a one-item tuple, when fetching a
value out of the listbox).
You can experiment with the selection alternatives on your own
by uncommenting the selectmode
setting in Example 10-9
and changing its value. You may get an error on double-clicks in
multiple selection modes, though, because the get
method will be passed a tuple of more
than one selection index (print it out to see for yourself). We’ll
see multiple selections in action in the PyMailGUI example later in
this book.
The deepest magic in the Example 10-9 script, though, boils down to two lines of code:
sbar.config(command=list.yview) # call list.yview when I move list.config(yscrollcommand=sbar.set) # call sbar.set when I move
The scroll bar and listbox are effectively cross-linked to each other through these configuration options; their values simply refer to bound widget methods of the other. By linking like this, Tkinter automatically keeps the two widgets in sync with each other as they move. Here’s how this works:
Moving a scroll bar invokes the callback handler
registered with its command
option. Here, list.yview
refers to a built-in listbox method that adjusts the listbox
display proportionally, based on arguments passed to the
handler.
Moving a listbox vertically invokes the callback handler
registered with its yscrollcommand
option. In this script,
the sbar.set
built-in method
adjusts a scroll bar proportionally.
In other words, moving one automatically moves the other. It
turns out that every scrollable object in Tkinter—Listbox
, Entry
, Text
, and Canvas
—has built-in yview
and xview
methods to process incoming vertical
and horizontal scroll callbacks, as well as yscrollcommand
and xscrollcommand
options for specifying an
associated scroll bar’s callback handler to invoke. All scroll bars
have a command
option, to name an
associated widget’s handler to be called on moves. Internally,
Tkinter passes information to all of these methods, and that
information specifies their new position (e.g., “go 10 percent down
from the top”), but your scripts need never deal with that level of
detail.
Because the scroll bar and listbox have been cross-linked in their option settings, moving the scroll bar automatically moves the list, and moving the list automatically moves the scroll bar. To move the scroll bar, either drag the solid part or click on its arrows or empty areas. To move the list, click on the list and move the mouse pointer above or below the listbox without releasing the mouse button. In both cases, the list and scroll bar move in unison. Figure 10-13 shows the scene after moving down a few entries in the list, one way or another.
Finally, remember that widgets packed last are always clipped first when a window is shrunk. Because of that, it’s important to pack scroll bars in a display as soon as possible so that they are the last to go when the window becomes too small for everything. You can generally make due with less than complete listbox text, but the scroll bar is crucial for navigating through the list. As Figure 10-14 shows, shrinking this script’s window cuts out part of the list, but retains the scroll bar.
At the same time, you don’t generally want a scroll bar to
expand with a window, so be sure to pack it with just a fill=Y
(or fill=X
for a horizontal scroll) and not an
expand=YES
. Expanding this
example’s window, for instance, makes the listbox grow along with
the window, but it keeps the scroll bar attached to the right and it
keeps it the same size.
We’ll see both scroll bars and listboxes repeatedly in later examples in this and later chapters (flip ahead to examples for PyEdit, PyForm, PyTree, and ShellGui, later in this chapter, for more). And although the example script in this section captures the fundamentals, I should point out that there is more to both scroll bars and listboxes than meets the eye here.
For example, it’s just as easy to add
horizontal scroll bars to scrollable widgets.
They are programmed almost exactly like the vertical one implemented
here, but callback handler names start with “x,” not “y” (e.g.,
xscrollcommand
), and an orient='horizontal'
configuration option
is set for the scroll bar object. For example, to add both vertical
and horizontal scrolls and to crosslink their motions, you would use
the following sort of code:
window = Frame(self) vscroll = Scrollbar(window) hscroll = Scrollbar(window, orient='horizontal') listbox = Listbox(window) # move listbox when scroll moved vscroll.config(command=listbox.yview, relief=SUNKEN) hscroll.config(command=listbox.xview, relief=SUNKEN) # move scroll when listbox moved listbox.config(yscrollcommand=vscroll.set, relief=SUNKEN) listbox.config(xscrollcommand=hscroll.set)
See the PyEdit, PyTree, and PyMailGUI programs later in this
book for examples of horizontal scroll bars at work. Scroll bars see
more kinds of GUI action too—they can be associated with other kinds
of widgets in the Tkinter library. For instance, it is common to
attach one to the Text
widget.
This brings us to the next point of interest on this tour.
18.118.24.30