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.[*]
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.
18.188.211.106