Wrapping Up Top-Level Window Interfaces

Top-level window interfaces were introduced in Chapter 9. This section picks up where that introduction left off and wraps up those interfaces in classes that automate some of the work of building top-level windows—setting titles, finding and displaying window icons, issuing proper close actions based on a window’s role, intercepting window manager close button clicks, and so on.

Example 11-13 provides wrapper classes for the most common window types—a main application window, a transient pop-up window, and an embedded GUI component window. These window types vary slightly in terms of their close operations, but most inherit common functionality related to window borders: icons, titles, and close buttons. By creating, mixing in, or subclassing the class for the type of window you wish to make, you’ll get all the setup logic for free.

Example 11-13. PP3EGuiToolswindows.py

###############################################################################
# classes that encapsulate top-level interfaces;
# allows same GUI to be main, pop-up, or attached;  content classes may inherit
# from these directly, or be mixed together with them per usage mode;  may also
# be called directly without a subclass;  designed to be mixed in after (further
# to the right than) app-specific classes: else, subclass gets methods here
# (destroy, okayToQuit), instead of from app-specific classes--can't redefine.
###############################################################################

import os, glob
from Tkinter      import Tk, Toplevel, Frame, YES, BOTH, RIDGE
from tkMessageBox import showinfo, askyesno


class _window:
    """
    mixin shared by main and pop-up windows
    """
    foundicon = None                                       # shared by all inst
    iconpatt  = '*.ico'                                    # may be reset
    iconmine  = 'py.ico'

    def configBorders(self, app, kind, iconfile):
        if not iconfile:                                    # no icon passed?
            iconfile = self.findIcon( )                     # try curr,tool dirs
        title = app
        if kind: title += ' - ' + kind
        self.title(title)                                  # on window border
        self.iconname(app)                                 # when minimized
        if iconfile:
            try:
                self.iconbitmap(iconfile)                  # window icon image
            except:                                        # bad py or platform
                pass
        self.protocol('WM_DELETE_WINDOW', self.quit)       # don't close silent

    def findIcon(self):
        if _window.foundicon:                              # already found one?
            return _window.foundicon
        iconfile  = None                                   # try curr dir auto
        iconshere = glob.glob(self.iconpatt)               # assume just one
        if iconshere:                                      # del icon for red Tk
            iconfile = iconshere[0]
        else:                                              # try tools dir icon
            mymod  = _ _import_ _(_ _name_ _)                    # import self for dir
            path   = _ _name_ _.split('.')                   # poss a package path
            for mod in path[1:]:                           # follow path to end
                mymod = getattr(mymod, mod)
            mydir  = os.path.dirname(mymod._ _file_ _)
            myicon = os.path.join(mydir, self.iconmine)    # use myicon, not tk
            if os.path.exists(myicon): iconfile = myicon
        _window.foundicon = iconfile                       # don't search again
        return iconfile


class MainWindow(Tk, _window):
    """
    when run in main top-level window
    """
    def _ _init_ _(self, app, kind='', iconfile=None):
        Tk._ _init_ _(self)
        self._ _app = app
        self.configBorders(app, kind, iconfile)

    def quit(self):
        if self.okayToQuit( ):                                # threads running?
            if askyesno(self._ _app, 'Verify Quit Program?'):
                self.destroy( )                               # quit whole app
        else:
            showinfo(self._ _app, 'Quit not allowed')         # or in okayToQuit?

    def destroy(self):                                       # exit app silently
        Tk.quit(self)                                        # redef if exit ops

    def okayToQuit(self):                                    # redef me if used
        return True                                          # e.g., thread busy


class PopupWindow(Toplevel, _window):
    """
    when run in secondary pop-up window
    """
    def _ _init_ _(self, app, kind='', iconfile=None):
        Toplevel._ _init_ _(self)
        self._ _app = app
        self.configBorders(app, kind, iconfile)

    def quit(self):                                          # redef me to change
        if askyesno(self._ _app, 'Verify Quit Window?'):    # or call destroy
            self.destroy( )                                 # quit this window

    def destroy(self):                                     # close win silently
        Toplevel.destroy(self)                             # redef for close ops


class QuietPopupWindow(PopupWindow):
    def quit(self):
        self.destroy( )                                     # don't verify close

class ComponentWindow(Frame):
    """
    when attached to another display
    """
    def _ _init_ _(self, parent):                            # if not a frame
        Frame._ _init_ _(self, parent)                       # provide container
        self.pack(expand=YES, fill=BOTH)
        self.config(relief=RIDGE, border=2)                # reconfig to change

    def quit(self):
        showinfo('Quit', 'Not supported in attachment mode')

    # destroy from Frame: erase frame silent               # redef for close ops

So why not just set an application’s icon and title by calling protocol methods directly? For one thing, those are the sorts of details that are easy to forget (you will probably wind up cutting and pasting code much of the time). For another, these classes add higher-level functionality that we might otherwise have to code redundantly. Among other things, the classes arrange for automatic quit verification dialog pop ups and icon file searching. For instance, the window classes always search the current working directory and the directory containing this module for a window icon file, once per process.

By using classes that encapsulate—that is, hide—such details, we inherit powerful tools without even having to think about their implementation again in the future. Moreover, by using such classes, we’ll give our applications a standard look-and-feel. And if we ever need to change that appearance, we have to change code in only one place, not in every window we generate.

To test this utility module, Example 11-14 exercises its classes in a variety of modes—as mix-in classes, as superclasses, and as calls from nonclass code.

Example 11-14. PP3EGuiToolswindows-test.py

# must import windows to test,
# else _ _name_ _ is _ _main_ _ in findIcon

from Tkinter import Toplevel, Tk, Button, mainloop
from windows import MainWindow, PopupWindow, ComponentWindow

def _selftest( ):
    from Tkinter import Button, mainloop

    # mixin usage
    class content:
        "same code used as a Tk, Toplevel, and Frame"
        def _ _init_ _(self):
            Button(self, text='Larch', command=self.quit).pack( )
            Button(self, text='Sing ', command=self.destroy).pack( )

    class contentmix(MainWindow, content):
        def _ _init_ _(self):
            MainWindow._ _init_ _(self, 'mixin', 'Main')
            content._ _init_ _(self)
    contentmix( )

    class contentmix(PopupWindow, content):
        def _ _init_ _(self):
            PopupWindow._ _init_ _(self, 'mixin', 'Popup')
            content._ _init_ _(self)
    prev = contentmix( )

    class contentmix(ComponentWindow, content):
        def _ _init_ _(self):                               # nested frame
            ComponentWindow._ _init_ _(self, prev)          # on prior window
            content._ _init_ _(self)                        # Sing erases frame
    contentmix( )

    # subclass usage
    class contentsub(PopupWindow):
        def _ _init_ _(self):
            PopupWindow._ _init_ _(self, 'popup', 'subclass')
            Button(self, text='Pine', command=self.quit).pack( )
            Button(self, text='Sing', command=self.destroy).pack( )
    contentsub( )

    # non-class usage
    win = PopupWindow('popup', 'attachment')
    Button(win, text='Redwood', command=win.quit).pack( )
    Button(win, text='Sing   ', command=win.destroy).pack( )
    mainloop( )

if _ _name_ _ == '_ _main_ _':
    _selftest( )

When run, the test generates the window in Figure 11-10. All generated windows get a blue “PY” icon automatically, thanks to the search and configuration logic they inherit from the window module’s classes. Some of the buttons on the test windows close just the enclosing window, some close the entire applications, some erase an attached window, and others pop up a quit verification dialog. Run this on your own to see what the examples’ buttons do.[*]

windows-test display

Figure 11-10. windows-test display

We’ll use these window protocol wrappers in the next chapter’s PyClock example, and then again later in Chapter 15 where they’ll come in handy to reduce the complexity of the PyMailGUI program. Part of the benefit of doing OOP in Python now is that we can forget the details later.



[*] Caveat: in Python 2.4, when setting window iconbitmask images, there is a slight pause when opening the window on Windows (during which an empty window flashes briefly for a fraction of a second). No workaround could be found for this, and it may be improved in the future (window icons were first supported on Windows very recently, in Python 2.3). If this startup delay is undesirable, simply delete the .ico icon files to force the compiled-in red “Tk” icon to be used; icons can also be set in the C API; see Tk documentation for details.

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

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