Dialogs are windows popped up by a script to provide or request additional information. They come in two flavors, modal and nonmodal:
These dialogs block the rest of the interface until the dialog window is dismissed; users must reply to the dialog before the program continues.
These dialogs can remain on-screen indefinitely without interfering with other windows in the interface; they can usually accept inputs at any time.
Regardless of their modality, dialogs are generally implemented
with the Toplevel
window object we
met in the prior section, whether you make the Toplevel
or not. There are essentially three
ways to present pop-up dialogs to users with Tkinter: by using common
dialog calls, by using the now-dated Dialog
object, and by creating custom dialog
windows with Toplevel
s and other
kinds of widgets. Let’s explore the basics of all three
schemes.
Because standard dialog calls are simpler, let’s start here first. Tkinter comes with a collection of precoded dialog windows that implement many of the most common pop ups programs generate—file selection dialogs, error and warning pop ups, and question and answer prompts. They are called standard dialogs (and sometimes common dialogs) because they are part of the Tkinter library, and they use platform-specific library calls to look like they should on each platform. A Tkinter file open dialog, for instance, looks like any other on Windows.
All standard dialog calls are modal (they don’t return until the dialog box is dismissed by the user), and they block the program’s main window while they are displayed. Scripts can customize these dialogs’ windows by passing message text, titles, and the like. Since they are so simple to use, let’s jump right into Example 9-6.
Example 9-6. PP3EGuiTourdlg1.pyw
from Tkinter import * from tkMessageBox import * def callback( ): if askyesno('Verify', 'Do you really want to quit?'): showwarning('Yes', 'Quit not yet implemented') else: showinfo('No', 'Quit has been cancelled') errmsg = 'Sorry, no Spam allowed!' Button(text='Quit', command=callback).pack(fill=X) Button(text='Spam', command=(lambda: showerror('Spam', errmsg))).pack(fill=X) mainloop( )
A lambda anonymous function is used here to wrap the call to
showerror
so that it is passed
two hardcoded arguments (remember, button-press callbacks get no
arguments from Tkinter itself). When run, this script creates the
main window in Figure
9-5.
When you press this window’s Quit button, the dialog in Figure 9-6 pops up by calling
the standard askyesno
function in
the tkmessagebox
module. This
looks different on Unix and Macintosh systems, but it looks like
you’d expect when run on Windows. This dialog blocks the program
until the user clicks one of its buttons; if the dialog’s Yes button
is clicked (or the Enter key is pressed), the dialog call returns
with a true value and the script pops up the standard dialog in
Figure 9-7 by calling
showwarning
.
There is nothing the user can do with Figure 9-7’s dialog but press
OK. If No is clicked in Figure
9-6’s quit verification dialog, a showinfo
call creates the pop up in Figure 9-8 instead. Finally, if
the Spam button is clicked in the main window, the standard dialog
captured in Figure 9-9 is
generated with the standard showerror
call.
All of this makes for a lot of window pop ups, of course, and you need to be careful not to rely on these dialogs too much (it’s generally better to use input fields in long-lived windows than to distract the user with pop ups). But where appropriate, such pop ups save coding time and provide a nice, native look-and-feel.
Let’s put some of these canned dialogs to better use. Example 9-7 implements an attachable Quit button that uses standard dialogs to verify the quit request. Because it’s a class, it can be attached and reused in any application that needs a verifying Quit button. Because it uses standard dialogs, it looks as it should on each GUI platform.
Example 9-7. PP3EGuiTourxd5 uitter.py
############################################# # a Quit button that verifies exit requests; # to reuse, attach an instance to other GUIs ############################################# from Tkinter import * # get widget classes from tkMessageBox import askokcancel # get canned std dialog class Quitter(Frame): # subclass our GUI def _ _init_ _(self, parent=None): # constructor method Frame._ _init_ _(self, parent) self.pack( ) widget = Button(self, text='Quit', command=self.quit) widget.pack(side=LEFT) def quit(self): ans = askokcancel('Verify exit', "Really quit?") if ans: Frame.quit(self) if _ _name_ _ == '_ _main_ _': Quitter().mainloop( )
This module is mostly meant to be used elsewhere, but it
puts up the button it implements when run standalone. Figure 9-10 shows the Quit
button itself in the upper left, and the askokcancel
verification dialog that
pops up when Quit is pressed.
If you press OK here, Quitter
runs the Frame
quit method to end the GUI to
which this button is attached (really, the mainloop
call). But to really understand
how such a spring-loaded button can be useful, we need to move on
and study a client GUI in the next section.
So far, we’ve seen a handful of standard dialogs, but there are quite a few more. Instead of just throwing these up in dull screenshots, though, let’s write a Python demo script to generate them on demand. Here’s one way to do it. First of all, in Example 9-8 we write a module to define a table that maps a demo name to a standard dialog call (and we use lambda to wrap the call if we need to pass extra arguments to the dialog function).
Example 9-8. PP3EGuiTourdialogTable.py
# define a name:callback demos table from tkFileDialog import askopenfilename # get standard dialogs from tkColorChooser import askcolor # they live in Lib/lib-tk from tkMessageBox import askquestion, showerror from tkSimpleDialog import askfloat demos = { 'Open': askopenfilename, 'Color': askcolor, 'Query': lambda: askquestion('Warning', 'You typed "rm *" Confirm?'), 'Error': lambda: showerror('Error!', "He's dead, Jim"), 'Input': lambda: askfloat('Entry', 'Enter credit card number') }
I put this table in a module so that it might be reused as
the basis of other demo scripts later (dialogs are more fun than
printing to stdout
). Next,
we’ll write a Python script, shown in Example 9-9, which simply
generates buttons for all of this table’s entries—use its keys as
button labels and its values as button callback handlers.
Example 9-9. PP3EGuiTourdemoDlg.py
from Tkinter import * # get base widget set from dialogTable import demos # button callback handlers from quitter import Quitter # attach a quit object to me class Demo(Frame): def _ _init_ _(self, parent=None): Frame._ _init_ _(self, parent) self.pack( ) Label(self, text="Basic demos").pack( ) for (key, value) in demos.items( ): Button(self, text=key, command=value).pack(side=TOP, fill=BOTH) Quitter(self).pack(side=TOP, fill=BOTH) if _ _name_ _ == '_ _main_ _': Demo().mainloop( )
This script creates the window shown in Figure 9-11 when run as a
standalone program; it’s a bar of demo buttons that simply route
control back to the values of the table in the module dialogTable
when pressed.
Notice that because this script is driven by the contents of
the dialogTable
module’s
dictionary, we can change the set of demo buttons displayed by
changing just dialogTable
(we
don’t need to change any executable code in demoDlg
). Also note that the Quit button
here is an attached instance of the Quitter
class of the prior section—it’s
at least one bit of code that you never have to write
again.
We’ve already seen some of the dialogs triggered by this demo bar window’s other buttons, so I’ll just step through the new ones here. Pressing the main window’s Query button, for example, generates the standard pop up in Figure 9-12.
This askquestion
dialog
looks like the askyesno
we saw
earlier, but actually it returns either string "yes"
or "no"
(askyesno
and askokcancel
return 1
or 0
, true
or false
). Pressing the demo bar’s Input
button generates the standard askfloat
dialog box shown in Figure 9-13.
This dialog automatically checks the input for valid
floating-point syntax before it returns, and is representative of
a collection of single-value input dialogs (askinteger
and askstring
prompt for integer and string
inputs too). It returns the input as a floating-point number
object (not as a string) when the OK button or Enter key is
pressed, or the Python None
object if the user clicks Cancel. Its two relatives return the
input as integer and string objects instead.
When the demo bar’s Open button is pressed, we get the
standard file open dialog made by calling askopenfilename
and captured in Figure 9-14. This is
Windows’ look-and-feel; it looks radically different on Linux, but
appropriately so.
A similar dialog for selecting a save-as filename is
produced by calling asksaveasfilename
(see the Text
widget section in Chapter 10 for an example). Both
file dialogs let the user navigate through the filesystem to
select a subject filename, which is returned with its full
directory pathname when Open is pressed; an empty string comes
back if Cancel is pressed instead. Both also have additional
protocols not demonstrated by this example:
They can be passed a filetypes
keyword argument—a set of
name patterns used to select files, which appear in the “Files
of type” pull down at the bottom of the dialog.
They can be passed an initialdir
(start directory),
initialfile
(for “File
name”), title
(for the
dialog window), defaultextension
(appended if the
selection has none), and parent
(to appear as an embedded
child instead of a pop-up dialog).
They can be made to remember the last directory selected by using exported objects instead of these function calls.
Another common dialog call in the tkFileDialog
module, askdirectory
, can be used to pop up a
dialog that allows users to choose a directory rather than a file.
It presents a tree view that users can navigate to pick the
desired directory, and it accepts keyword arguments including
initialdir
and title
. The corresponding Directory
object remembers the last
directory selected and starts there the next time the dialog is
shown.
We’ll use most of these interfaces later in the book, especially for the file dialogs in the PyEdit example in Chapter 12, but feel free to flip ahead for more details now. The directory selection dialog will show up in the PyPhoto example in Chapter 12 and the PyMailGUI example in Chapter 15; again, skip ahead for code and screenshots.
Finally, the demo bar’s Color button triggers a standard
askcolor
call, which generates
the standard color selection dialog shown in Figure 9-15.
If you press its OK button, it returns a data structure that
identifies the selected color, which can be used in all color
contexts in Tkinter. It includes RGB values and a hexadecimal
color string (e.g., ((160, 160, 160),
'#a0a0a0')
). More on how this tuple can be useful in a
moment. If you press Cancel, the script gets back a tuple
containing two nones (None
s of
the Python variety, that is).
The dialog demo launcher bar displays standard
dialogs and can be made to display others by simply changing the
dialogTable
module it imports.
As coded, though, it really shows only dialogs; it would also be
nice to see their return values so that we know how to use them in
scripts. Example 9-10
adds printing of standard dialog results to the stdout
standard output stream.
Example 9-10. PP3EGuiTourdemoDlg-print.py
########################################################################## # same, but show return values of dialog calls; the lambda saves # data from the local scope to be passed to the handler (button press # handlers normally get no arguments) and works just like a nested def # statement of this form: def func(key=key): self.printit(key) ########################################################################## from Tkinter import * # get base widget set from dialogTable import demos # button callback handlers from quitter import Quitter # attach a quit object to me class Demo(Frame): def _ _init_ _(self, parent=None): Frame._ _init_ _(self, parent) self.pack( ) Label(self, text="Basic demos").pack( ) for (key, value) in demos.items( ): func = (lambda key=key: self.printit(key)) Button(self, text=key, command=func).pack(side=TOP, fill=BOTH) Quitter(self).pack(side=TOP, fill=BOTH) def printit(self, name): print name, 'returns =>', demos[name]( ) # fetch, call, print if _ _name_ _ == '_ _main_ _': Demo().mainloop( )
This script builds the same main button-bar window, but
notice that the callback handler is an anonymous function made
with a lambda now, not a direct reference to dialog calls in the
imported dialogTable
dictionary:
# use enclosing scope lookup func = (lambda key=key: self.printit(key))
We talked about this in the prior chapter’s tutorial, but
this is the first time we’ve actually used lambda like this, so
let’s get the facts straight. Because button-press callbacks are
run with no arguments, if we need to pass extra data to the
handler, it must be wrapped in an object that remembers that extra
data and passes it along. Here, a button press runs the function
generated by the lambda, an indirect call layer that retains
information from the enclosing scope. The net effect is that the
real handler, printit
, receives
an extra required name
argument
giving the demo associated with the button pressed, even though
this argument wasn’t passed back from Tkinter itself. The lambda
remembers and passes on state information.
Notice, though, that this lambda function’s body references
both self
and key
in the enclosing method’s local
scope. In recent Pythons, the reference to self
just works because of the enclosing
function scope lookup rules, but we need to pass key
in explicitly with a default
argument or else it will be the same in all the generated lambda
functions—the value it has after the last loop iteration. As we
learned in Chapter 8,
enclosing scope references are resolved when the nested function
is called, but defaults are resolved when the nested function is
created. Because self
won’t
change after the function is made, we can rely on the scope lookup
rules for that name, but not for key
.
In earlier Pythons, default arguments were required to pass all values in from enclosing scopes explicitly, using either of these two techniques:
# use simple defaults func = (lambda self=self, name=key: self.printit(name)) # use a bound method default func = (lambda handler=self.printit, name=key: handler(name))
Today, we can get away with the simpler technique, though we still need a default for the loop variable, and you may still see the default forms in older Python code.
Note that the parentheses around the lambdas are not
required here; I add them as a personal style preference just to
set the lambda off from its surrounding code (your mileage can
vary). Also notice that the lambda does the same work as a nested
def
statement here; in
practice, though, the lambda could appear within the call to
Button
itself because it is an
expression and it need not be assigned to a name. The following
two forms are equivalent:
for (key, value) in demos.items( ): func = (lambda key=key: self.printit(key)) for (key, value) in demos.items( ): def func(key=key): self.printit(key)
You can also use a callable class object here that retains
state as instance attributes (see the tutorial’s _ _call_ _
example in Chapter 8 for hints). But as a rule
of thumb, if you want a lambda’s result to use any names from the
enclosing scope when later called, either simply name them and let
Python save their values for future use, or pass them in with
defaults to save the values they have at lambda function creation
time. The latter scheme is required only if the required variable
may change before the callback occurs.
When run, this script prints dialog return values; here is the output after clicking all the demo buttons in the main window and picking both Cancel/No and OK/Yes buttons in each dialog:
C:...PP3EGuiTour>python demoDlg-print.py
Error returns => ok
Input returns => None
Input returns => 3.14159
Open returns =>
Open returns => C:/PP2ndEd/examples/PP3E/Gui/Tour/demoDlg-print.py
Query returns => no
Query returns => yes
Color returns => (None, None)
Color returns => ((160, 160, 160), '#a0a0a0')
Now that I’ve shown you these dialog results, I want to next show you how one of them can actually be useful.
The standard color selection dialog isn’t just
another pretty face—scripts can pass the hexadecimal color string
it returns to the bg and fg
widget color configuration options we met earlier. That is,
bg
and fg
accept both a color name (e.g.,
blue
) and an askcolor
result string that starts with
a #
(e.g., the #a0a0a0
in the last output line of the
prior section).
This adds another dimension of customization to Tkinter
GUIs: instead of hardcoding colors in your GUI products, you can
provide a button that pops up color selectors that let users
choose color preferences on the fly. Simply pass the color string
to widget config
methods in
callback handlers, as in Example 9-11.
Example 9-11. PP3EGuiToursetcolor.py
from Tkinter import * from tkColorChooser import askcolor def setBgColor( ): (triple, hexstr) = askcolor( ) if hexstr: print hexstr push.config(bg=hexstr) root = Tk( ) push = Button(root, text='Set Background Color', command=setBgColor) push.config(height=3, font=('times', 20, 'bold')) push.pack(expand=YES, fill=BOTH) root.mainloop( )
This script creates the window in Figure 9-16 when launched (its button’s background is a sort of green, but you’ll have to trust me on this). Pressing the button pops up the color selection dialog shown earlier; the color you pick in that dialog becomes the background color of this button after you press OK.
Color strings are also printed to the stdout
stream (the console window); run
this on your computer to experiment with available color
settings:
C:...PP3EGuiTour>python setcolor.py
#c27cc5
#5fe28c
#69d8cd
We’ve seen most of the standard dialogs and will use these
pop ups in examples throughout the rest of this book. But for more
details on other calls and options available, either consult other
Tkinter documentation or browse the source code of the modules
used at the top of the dialogTable
module; all are simple
Python files installed in the lib-tk
subdirectory of the Python source library on your machine. And
keep this demo bar example filed away for future reference; we’ll
reuse it later in the tour when we meet other button-like
widgets.
In older Python code, you may see dialogs occasionally
coded with the standard Dialog
module. This is a bit dated now, and it uses an X Windows
look-and-feel; but just in case you run across such code in your
Python maintenance excursions, Example 9-12 gives you a feel
for the interface.
Example 9-12. PP3EGuiTourdlg-old.py
from Tkinter import * from Dialog import Dialog class OldDialogDemo(Frame): def _ _init_ _(self, master=None): Frame._ _init_ _(self, master) Pack.config(self) # same as self.pack( ) Button(self, text='Pop1', command=self.dialog1).pack( ) Button(self, text='Pop2', command=self.dialog2).pack( ) def dialog1(self): ans = Dialog(self, title = 'Popup Fun!', text = 'An example of a popup-dialog ' 'box, using older "Dialog.py".', bitmap = 'questhead', default = 0, strings = ('Yes', 'No', 'Cancel')) if ans.num == 0: self.dialog2( ) def dialog2(self): Dialog(self, title = 'HAL-9000', text = "I'm afraid I can't let you do that, Dave...", bitmap = 'hourglass', default = 0, strings = ('spam', 'SPAM')) if _ _name_ _ == '_ _main_ _': OldDialogDemo().mainloop( )
If you supply Dialog
a
tuple of button labels and a message, you get back the index of the
button pressed (the leftmost is index zero). Dialog
windows are modal: the rest of the
application’s windows are disabled until the Dialog
receives a response from the user.
When you press the Pop2 button in the main window created by this
script, the second dialog pops up, as shown in Figure 9-17.
This is running on Windows, and as you can see, it is nothing
like what you would expect on that platform for a question dialog.
In fact, this dialog generates an X Windows look-and-feel,
regardless of the underlying platform. Because of both Dialog
’s appearance and the extra
complexity required to program it, you are probably better off using
the standard dialog calls of the prior section instead.
The dialogs we’ve seen so far have a standard appearance and interaction. They are fine for many purposes, but often we need something a bit more custom. For example, forms that request multiple field inputs (e.g., name, age, shoe size) aren’t directly addressed by the common dialog library. We could pop up one single-input dialog in turn for each requested field, but that isn’t exactly user friendly.
Custom dialogs support arbitrary interfaces, but they are also
the most complicated to program. Even so, there’s not much to
it—simply create a pop-up window as a Toplevel
with attached widgets, and
arrange a callback handler to fetch user inputs entered in the
dialog (if any) and to destroy the window. To make such a custom
dialog modal, we also need to wait for a reply by giving the window
input focus, making other windows inactive, and waiting for an
event. Example 9-13
illustrates the basics.
Example 9-13. PP3EGuiTourdlg-custom.py
import sys from Tkinter import * makemodal = (len(sys.argv) > 1) def dialog( ): win = Toplevel( ) # make a new window Label(win, text='Hard drive reformatted!').pack( ) # add a few widgets Button(win, text='OK', command=win.destroy).pack( ) # set destroy callback if makemodal: win.focus_set( ) # take over input focus, win.grab_set( ) # disable other windows while I'm open, win.wait_window( ) # and wait here until win destroyed print 'dialog exit' # else returns right away root = Tk( ) Button(root, text='popup', command=dialog).pack( ) root.mainloop( )
This script is set up to create a pop-up dialog window in
either modal or nonmodal mode, depending on its makemodal
global variable. If it is run
with no command-line arguments, it picks nonmodal style, captured in
Figure 9-18.
The window in the upper right is the root window here; pressing its “popup” button creates a new pop-up dialog window. Because dialogs are nonmodal in this mode, the root window remains active after a dialog is popped up. In fact, nonmodal dialogs never block other windows, so you can keep pressing the root’s button to generate as many copies of the pop-up window as will fit on your screen. Any or all of the pop ups can be killed by pressing their OK buttons, without killing other windows in this display.
Now, when the script is run with a command-line argument
(e.g., python dlg-custom.py 1
),
it makes its pop ups modal instead. Because modal dialogs grab all
of the interface’s attention, the main window becomes inactive in
this mode until the pop up is killed; you can’t even click on it
to reactivate it while the dialog is open. Because of that, you
can never make more than one copy of the pop up on-screen at once,
as shown in Figure
9-19.
In fact, the call to the dialog
function in this script doesn’t
return until the dialog window on the left is dismissed by
pressing its OK button. The net effect is that modal dialogs
impose a function call-like model on an otherwise event-driven
programming model; user inputs can be processed right away, not in
a callback handler triggered at some arbitrary point in the
future.
Forcing such a linear control flow on a GUI takes a bit of extra work, though. The secret to locking other windows and waiting for a reply boils down to three lines of code, which are a general pattern repeated in most custom modal dialogs.
win.focus_set(
)
Makes the window take over the application’s input
focus, as if it had been clicked with the mouse to make it
the active window. This method is also known by the synonym
focus
, and it’s also
common to set the focus on an input widget within the dialog
(e.g., an Entry
) rather
than on the entire window.
win.grab_set(
)
Disables all other windows in the application until this one is destroyed. The user cannot interact with other windows in the program while a grab is set.
win.wait_window(
)
Pauses the caller until the win
widget is destroyed, but keeps
the main event-processing loop (mainloop
) active during the pause.
That means that the GUI at large remains active during the
wait; its windows redraw themselves if covered and
uncovered, for example. When the window is destroyed with
the destroy
method, it is
erased from the screen, the application grab is
automatically released, and this method call finally
returns.
Because the script waits for a window destroy event, it must
also arrange for a callback handler to destroy the window in
response to interaction with widgets in the dialog window (the
only window active). This example’s dialog is simply
informational, so its OK button calls the window’s destroy
method. In user-input dialogs,
we might instead install an Enter key-press callback handler that
fetches data typed into an Entry
widget and then calls destroy
(see later in this
chapter).
Modal dialogs are typically implemented by waiting
for a newly created pop-up window’s destroy
event, as in this example. But
other schemes are viable too. For example, it’s possible to create
dialog windows ahead of time, and show and hide them as needed
with the top-level window’s deiconify
and withdraw
methods (see the alarm scripts
near the end of Chapter 10
for details). Given that window creation speed is generally fast
enough as to appear instantaneous today, this is much less common
than making and destroying a window from scratch on each
interaction.
It’s also possible to implement a modal state by waiting for
a Tkinter variable to change its value, instead of waiting for a
window to be destroyed. See this chapter’s discussion of Tkinter
variables (which are class objects, not normal Python variables),
and the wait_variable
method
discussed near the end of Chapter
10, for more details. This scheme allows a long-lived
dialog box’s callback handler to signal a state change to a
waiting main program, without having to destroy the dialog
box.
Finally, if you call the mainloop
method recursively, the call
won’t return until the widget quit
method has been invoked. The
quit
method terminates a
mainloop
call, and so normally
ends a GUI program. But it will simply exit a recursive mainloop
level if one is active. Because
of this, modal dialogs can also be written without wait method
calls if you are careful. For instance, Example 9-14 works the same
way as dlg-custom
.
Example 9-14. PP3EGuiTourdlg-recursive.py
from Tkinter import * def dialog( ): win = Toplevel( ) # make a new window Label(win, text='Hard drive reformatted!').pack( ) # add a few widgetsButton(win, text='OK', command=win.quit).pack( )
# set quit callbackwin.protocol('WM_DELETE_WINDOW', win.quit)
# quit on wm close too! win.focus_set( ) # take over input focus, win.grab_set( ) # disable other windows while I'm open,win.mainloop( )
# and start a nested event loop to waitwin.destroy( )
print 'dialog exit' root = Tk( ) Button(root, text='popup', command=dialog).pack( ) root.mainloop( )
If you go this route, be sure to call quit
rather than destroy
in dialog callback handlers
(destroy
doesn’t terminate the
mainloop
level), and be sure to
use protocol
to make the window
border close button call quit
too (or else it won’t end the recursive mainloop
level call and will generate
odd error messages when your program finally exits). Because of
this extra complexity, you’re probably better off using wait_window
or wait_variable
, not recursive mainloop
calls.
We’ll see how to build form-like dialogs with labels and
input fields later in this chapter when we meet Entry
, and again when we study the
grid
manager in Chapter 10. For more custom dialog
examples, see ShellGui (Chapter
11), PyMailGUI (Chapter
15), PyCalc (Chapter
21), and the nonmodal form.py (Chapter 13). Here, we’re moving on
to learn more about events that will prove to be useful currency at later tour
destinations.
3.14.251.128