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; for instance, we could define a button function to handle configuration details and support most of the buttons we draw.
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 per-instance state and
inherited methods. The script in Example 11-1 shows how.
Example 11-1. PP3EGuiToolsguimixin.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 tkMessageBox import * from tkFileDialog import * from ScrolledText import ScrolledText from PP3E.launchmodes import PortableLauncher, System 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) 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 == 1: 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): new = Toplevel( ) # make a new version of me myclass = self._ _class_ _ # instance's (lowest) class object myclass(new) # attach/run instance to new window def spawn(self, pycmdline, wait=0): if not wait: PortableLauncher(pycmdline, pycmdline)( ) # run Python progam else: System(pycmdline, pycmdline)( ) # wait for it to exit def browser(self, filename): new = Toplevel( ) # make new window text = ScrolledText(new, height=30, width=90) # Text with scrollbar text.config(font=('courier', 10, 'normal')) # use fixed-width font text.pack( ) new.title("Text Viewer") # set window mgr attrs new.iconname("browser") text.insert('0.0', open(filename, 'r').read( ) ) # insert file's text 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) TestMixin().mainloop( )
Although Example 11-1
is geared toward GUIs, it’s really about design concepts. The GuiMixin
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-style
Dialog
calls were replaced with the
new Tk standard dialog calls. Because this class’s interface hides
such details, its clients did not have to be changed to use the new
dialog 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.
There are a few things to notice here:
The quit
method serves
some of the same purpose as the reusable Quitter
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.
The clone
method makes a
new copy, in a new top-level window, of the most specific class
that mixes in a GuiMixin
(self
._ _class_ _
is the class object that the
instance was created from). This opens a new independent copy of
the window.
The browser
method opens
the standard library’s ScrolledText
object in a new window and
fills it with the text of a file to be viewed. We wrote our own
ScrolledText
in the previous
chapter; you might need to use it here instead, if the standard
library’s class ever becomes deprecated (please, no
wagering).
The spawn
method launches
a Python program command line as a new process and waits for it to
end or not (depending on the wait
argument). This method is simple,
though, because we wrapped launching details in the launchmodes
module presented at the end
of Chapter 5. GuiMixin
both fosters and practices good
code reuse habits.
The GuiMixin
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 a Frame
-based class to be used: quit
assumes it’s mixed with a Frame
, and clone
assumes it’s mixed with a widget
class. To satisfy such constraints, this module’s self-test code at
the bottom combines GuiMixin
with a
Frame
widget. Figure 11-1 shows the scene
created by the self-test after pressing “clone” twice, and then “help”
in one of the three copies.
We’ll see this class show up again as a mixin in later examples—that’s the whole point of code reuse, after all.
18.118.141.27