[
37
]
Perhaps most prominently today, the popular
Eve
Online
game uses Python for scripting and much of the
functionality—both on the server and the client. It uses the
Stackless Python implementation to boost massively parallel
multitasking responsiveness. Other notable game companies using
Python include the makers of Civilization IV, and the now-defunct
Origin Systems (at last report, its game Ultima Online II was to
use Python for scripting its animation).
And that’s a wrap for our tour of the tkinter library. You've now
seen all the core widgets and tools previewed in
Chapter 7
(flip back for a summary of
territory covered on this tour). For more details, watch for all of the
tools introduced here to appear again in the advanced GUI techniques in
Chapter 10
, the larger GUI examples in
Chapter 11
, and the remainder of the book at
large. To some extent, the last few chapters have laid the groundwork
needed to step up to the larger programs that
follow
.
I should point
out, though, that this story is still not quite complete.
Although we’ve covered the entire basic tkinter widget arsenal and
mastered GUI fundamentals along the way, we’ve skipped a handful of
newer and more advanced widgets introduced to tkinter recently:
Spinbox
AnEntry
used to
select among a set or range of values
LabelFrame
AFrame
with a
border and title around a group of items
PanedWindow
A geometry manager
widget containing multiple widgets that can be
resized by moving separator lines with the mouse
Moreover, we haven’t even mentioned any of the higher-level
widgets available in the popular Pmw, Tix, or ttk extension packages for
tkinter
(described in
Chapter 7
) or any other third-party
packages in general. For instance:
Tix and ttk both provide additional widget options outlined in
Chapter 7
, which are now part of
Python’s standard library.
The third-party domain tends to change over time, but has
hosted tree widgets, HTML viewers, font selection dialogs, tables,
and much more for tkinter, and includes the Pmw megawidget
set.
Many tkinter programs such as Python’s standard IDLE
development GUI include font dialogs, tree widgets, and more that
you may be able to use in your own applications.
Because such extensions are too complex for us to cover in a
useful fashion here, though, we’ll defer to other resources in the
interest of space. To sample richer options for your GUI scripts, be
sure to consult tkinter, Tk, Tix, ttk, and Pmw documentation for more
details on additional widgets, and visit the PyPI website at
http://python.org/
or search the Web for other
third-party tkinter extensions.
I should also mention that there are more widget configuration
options than we have met on this tour. Consult Tk and tkinter resources
for options not listed explicitly here. Although other tkinter tools are
analogous to those presented here, the space I have for illustrating
additional widgets and options in this book is limited by both my
publisher and the finite nature of trees.
This chapter continues our look at building GUIs with Python and the
tkinter library by presenting a collection of more advanced GUI
programming patterns and techniques. In the preceding three chapters, we
explored all the fundamentals of tkinter itself. Here, our goal is to put
them to work to add higher-level structures that will be useful in larger
programs. That is, our focus shifts here to writing code of our own which
implements utility above and beyond the basic tkinter toolkit—utility that
we’ll actually find useful in more complete examples later in the
book.
Some of the techniques we will be studying in this chapter are as
follows:
Providing common GUI operations in “mixin” classes
Building menus and toolbars from data structure templates
Adding GUI interfaces to command-line tools
Redirecting input and output streams to GUI widgets
Reloading GUI callback handlers on the fly
Wrapping up and automating top-level window interfaces
Using threads and queues to avoiding blocking in GUIs
Popping up GUI windows on demand from non-GUI programs
Adding GUIs as separate programs with sockets and pipes
As with other chapters in this book, this chapter has a dual
agenda—not only will we be studying GUI programming, but we’ll also be
learning more about general Python development concepts such as
object-oriented programming (OOP) and code reuse. As we’ll see, by coding
GUI tools in Python, it’s easy to apply them in a wide variety of contexts
and programs.
As a segue to the next chapter, this one also closes with a look at
the PyDemos and PyGadgets launcher toolbars—GUIs used to start larger GUI
examples. Although most of their code is external to this book, we’ll
explore enough of their structure to help you study them in the examples
distribution package.
Two notes before we begin: first, be sure to read the code listings
in this chapter for details we won’t present in the narrative. Second,
although small examples that apply in this chapter’s techniques will show
up along the way, more realistic application will have to await more
realistic programs. We’ll put these techniques to use in the larger
examples in the next chapter and throughout the rest of the book. In fact,
we’ll be reusing the modules we develop here often, as tools in other
programs in this book; reusable software wants to be reused. First,
though, let’s do what our species does best and build some tools.
If you read
the last three chapters, you probably noticed that the code
used to construct nontrivial GUIs can become long if we make each widget
by hand. Not only do we have to link up all the widgets manually, but we
also need to remember and then set dozens of options. If we stick to this
strategy, GUI programming often becomes an exercise in typing, or at least
in cut-and-paste text editor operations.
Instead of
performing each step by hand, a better idea is to wrap or
automate as much of the GUI construction process as possible. One
approach is to code functions that provide typical widget
configurations, and automate the construction process for cases to which
they apply. For instance, we could define a button function to handle
configuration and packing details and support most of the buttons we
draw.
Example 10-1
provides
a handful of such widget builder calls.
Example 10-1. PP4E\Gui\Tools\widgets.py
"""
###############################################################################
wrap up widget construction in functions for easier use, based upon some
assumptions (e.g., expansion); use **extras fkw args for width, font/color,
etc., and repack result manually later to override defaults if needed;
###############################################################################
"""
from tkinter import *
def frame(root, side=TOP, **extras):
widget = Frame(root)
widget.pack(side=side, expand=YES, fill=BOTH)
if extras: widget.config(**extras)
return widget
def label(root, side, text, **extras):
widget = Label(root, text=text, relief=RIDGE) # default config
widget.pack(side=side, expand=YES, fill=BOTH) # pack automatically
if extras: widget.config(**extras) # apply any extras
return widget
def button(root, side, text, command, **extras):
widget = Button(root, text=text, command=command)
widget.pack(side=side, expand=YES, fill=BOTH)
if extras: widget.config(**extras)
return widget
def entry(root, side, linkvar, **extras):
widget = Entry(root, relief=SUNKEN, textvariable=linkvar)
widget.pack(side=side, expand=YES, fill=BOTH)
if extras: widget.config(**extras)
return widget
if __name__ == '__main__':
app = Tk()
frm = frame(app, TOP) # much less code required here!
label(frm, LEFT, 'SPAM')
button(frm, BOTTOM, 'Press', lambda: print('Pushed'))
mainloop()
This module makes some assumptions about its clients’ use cases,
which allows it to automate typical construction chores such as packing.
The net effect is to reduce the amount of code required of its
importers. When run as a script,
Example 10-1
creates a simple
window with a ridged label on the left and a button on the right that
prints a message when pressed, both of which expand along with the
window. Run this on your own for a look; its window isn’t really
anything new for us, and its code is meant more as library than
script—as we’ll see when we make use of it later in
Chapter 19
’s PyCalc.
This function-based approach can cut down on the amount of code
required. As functions, though, its tools don’t lend themselves to
customization in the broader OOP sense. Moreover, because they are not
methods, they do not have access to the state of an object representing
the GUI.
Alternatively, we
can implement common methods in a class and inherit them
everywhere they are needed. Such classes are commonly called
mixin
classes because their methods are “mixed in”
with other classes. Mixins serve to package generally useful tools as
methods. The concept is almost like importing a module, but mixin
classes can access the subject instance,self
, to utilize both per-instance state and
inherited methods. The script in
Example 10-2
shows how.
Example 10-2. PP4E\Gui\Tools\guimixin.py
"""
###############################################################################
a "mixin" class for other frames: common methods for canned dialogs,
spawning programs, simple text viewers, etc; this class must be mixed
with a Frame (or a subclass derived from Frame) for its quit method
###############################################################################
"""
from tkinter import *
from tkinter.messagebox import *
from tkinter.filedialog import *
from PP4E.Gui.Tour.scrolledtext import ScrolledText # or tkinter.scrolledtext
from PP4E.launchmodes import PortableLauncher, System # or use multiprocessing
class GuiMixin:
def infobox(self, title, text, *args): # use standard dialogs
return showinfo(title, text) # *args for bkwd compat
def errorbox(self, text):
showerror('Error!', text)
def question(self, title, text, *args):
return askyesno(title, text) # return True or False
def notdone(self):
showerror('Not implemented', 'Option not available')
def quit(self):
ans = self.question('Verify quit', 'Are you sure you want to quit?')
if ans:
Frame.quit(self) # quit not recursive!
def help(self):
self.infobox('RTFM', 'See figure 1...') # override this better
def selectOpenFile(self, file="", dir="."): # use standard dialogs
return askopenfilename(initialdir=dir, initialfile=file)
def selectSaveFile(self, file="", dir="."):
return asksaveasfilename(initialfile=file, initialdir=dir)
def clone(self, args=()): # optional constructor args
new = Toplevel() # make new in-process version of me
myclass = self.__class__ # instance's (lowest) class object
myclass(new, *args) # attach/run instance to new window
def spawn(self, pycmdline, wait=False):
if not wait: # start new process
PortableLauncher(pycmdline, pycmdline)() # run Python progam
else:
System(pycmdline, pycmdline)() # wait for it to exit
def browser(self, filename):
new = Toplevel() # make new window
view = ScrolledText(new, file=filename) # Text with Scrollbar
view.text.config(height=30, width=85) # config Text in Frame
view.text.config(font=('courier', 10, 'normal')) # use fixed-width font
new.title("Text Viewer") # set window mgr attrs
new.iconname("browser") # file text added auto
"""
def browser(self, filename): # if tkinter.scrolledtext
new = Toplevel() # included for reference
text = ScrolledText(new, height=30, width=85)
text.config(font=('courier', 10, 'normal'))
text.pack(expand=YES, fill=BOTH)
new.title("Text Viewer")
new.iconname("browser")
text.insert('0.0', open(filename, 'r').read() )
"""
if __name__ == '__main__':
class TestMixin(GuiMixin, Frame): # standalone test
def __init__(self, parent=None):
Frame.__init__(self, parent)
self.pack()
Button(self, text='quit', command=self.quit).pack(fill=X)
Button(self, text='help', command=self.help).pack(fill=X)
Button(self, text='clone', command=self.clone).pack(fill=X)
Button(self, text='spawn', command=self.other).pack(fill=X)
def other(self):
self.spawn('guimixin.py') # spawn self as separate process
TestMixin().mainloop()
Although
Example 10-2
is geared toward GUIs, it’s really about design concepts. TheGuiMixin
class implements common operations
with standard interfaces that are immune to changes in implementation.
In fact, the implementations of some of this class’s methods did
change—between the first and second editions of this book, old-styleDialog
calls were replaced with the
new Tk standard dialog calls; in the fourth edition, the file browser
was updated to use a different scrolled text class. Because this class’s
interface hides such details, its clients did not have to be changed to
use the new
techniques
.
As is,GuiMixin
provides
methods for common dialogs, window cloning, program spawning, text file
browsing, and so on. We can add more methods to such a mixin later if we
find ourselves coding the same methods repeatedly; they will all become
available immediately everywhere this class is imported and mixed.
Moreover,GuiMixin
’s methods can be
inherited and used as is, or they can be redefined in subclasses. Such
are the natural advantages of classes over functions.
There are a few details worth highlighting here:
Thequit
method serves some
of the same purpose as the reusableQuitter
button we used in earlier
chapters. Because mixin classes can define a large library of
reusable methods, they can be a more powerful way to package
reusable components than individual classes. If the mixin is
packaged well, we can get a lot more from it than a single button’s
callback.
Theclone
method makes a
new in-process copy, in a new top-level window, of the most specific
class that mixes in aGuiMixin
(self
.__class__
is the class object that the
instance was created from). Assuming that the class requires no
constructor arguments other than a parent container, this opens a
new independent copy of the window (pass in any extra constructor
arguments required).
Thebrowser
method opens
theScrolledText
object we wrote
in
Chapter 9
in a new window
and fills it with the text of a file to be viewed. As noted in the
preceding chapter, there is also aScrolledText
widget in standard library
moduletkinter.scrolledtext
, but
its interface differs, it does not load a file automatically, and it
is prone to becoming deprecated (though it hasn’t over many years).
For reference, its alternative code is included.
Thespawn
method launches a
Python program command line as a new independent process and waits
for it to end or not (depending on the default Falsewait
argument—
GUIs usually shouldn’t wait).
This method is simple, though, because we wrapped launching details
in thelaunchmodes
module
presented at the end of
Chapter 5
.GuiMixin
both fosters and
practices good code reuse habits.
TheGuiMixin
class is meant to
be a library of reusable tool methods and is essentially useless by
itself. In fact, it must generally be mixed with aFrame
-based class to be used:quit
assumes it’s mixed with aFrame
, andclone
assumes it’s mixed with a widget class.
To satisfy such constraints, this module’s self-test code at the bottom
combinesGuiMixin
with aFrame
widget.
Figure 10-1
shows the
scene created by the module’s self-test after pressing “clone” and
“spawn” once each, and then “help” in one of the three copies. Because
they are separate processes, windows started with “spawn” keep running
after other windows are closed and do not impact other windows when
closed themselves; a “clone” window is in-process instead—it is closed
with others, but its “X” destroys just itself. Make sure your PYTHONPATH
includes the
PP4E
directory’s
container for the cross-directory package imports in this example and
later examples which use it.
Figure 10-1. GuiMixin self-test code in action
We’ll seeGuiMixin
show up
again as a mixin in later examples; that’s the whole point of code
reuse, after all. Although functions are often useful, classes support
inheritance and access to instance state, and provide an extra
organizational structure—features that are especially useful given the
coding requirements of GUIs. For instance, many ofGuiMixin
’s methods could be replaced with
simple functions, butclone
andquit
could not. The next section
carries these talents of mixin classes even
further.