GuiMixin: Common Tool Mixin Classes

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.

GuiMixin self-test code in action

Figure 11-1. GuiMixin self-test code in action

We’ll see this class show up again as a mixin in later examples—that’s the whole point of code reuse, after all.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.141.27