9.8. Running GUI Code Three Ways
Now that we've built a handful of similar demo launcher programs, let's write a few top-level scripts to combine them. Because the demos were coded as both reusable classes and scripts, they can be deployed as attached frame components, run in their own top-level windows, and launched as standalone programs. All three options illustrate code reuse in action.
9.8.1. Attaching Frames
To illustrate hierarchical GUI composition on a grander scale than we've seen so far, Example 9-32 arranges to show all four of the dialog launcher bar scripts of this chapter in a single frame. It reuses Examples 9-9, 9-22, 9-25, and 9-30.
Example 9-32. PP3E\Gui\Tour\demoAll-frm.py
Because all four demo launcher bars are coded to attach themselves to parent container widgets, this is easier than you might think: simply pass the same parent widget (here, the root window) to all four demo constructor calls, and pack and configure the demo objects as desired. Figure 9-32 shows this script's graphical resulta single window embedding instances of all four of the dialog demo launcher demos we saw earlier.
Figure 9-32. demoAll_frm: nested subframes
Naturally, this example is artificial, but it illustrates the power of composition when applied to building larger GUI displays. If you pretend that each of the four attached demo objects was something more useful, like a text editor, calculator, or clock, you'll better appreciate the point of this example.
Besides demo object frames, this composite window also contains no fewer than five instances of the Quitter button we wrote earlier (any one of which can end the GUI), and a States button to dump the current values of all the embedded demo objects at once (it calls each object's report method, if it has one). Here is a sample of the sort of output that shows up in the stdout stream after interacting with widgets on this display; States output is in bold:
C:\...\PP3E\Gui\Tour>python demoAll_frm.py in onMove 0 in onMove 0 demoDlg: none demoCheck: 0 0 0 0 0 demoRadio: demoScale: 0 you pressed Input result: 1.234 demoDlg: none demoCheck: 1 0 1 1 0 demoRadio: Input demoScale: 0 you pressed Query result: yes in onMove 1 in onMove 2 You picked 2 C:/PP2ndEd/examples/PP3E/Gui/Tour/demoAll_frm.py demoDlg: none demoCheck: 1 0 1 1 0 demoRadio: Query demoScale: 2
The only substantially tricky part of this script is its use of Python's built-in _ _import_ _ function to import a module by a name string. Look at the following two lines from the script's addComponents function:
module = _ _import_ _(demo) # import module by name string part = module.Demo(root) # attach an instance of its Demo
This is equivalent to saying something like this:
import 'demoDlg' part = 'demoDlg'.Demo(root)
However, the preceding code is not legal Python syntax; the module name in import statements must be a Python variable, not a string. To be generic, addComponents steps through a list of name strings and relies on _ _import_ _ to import and return the module identified by each string. It's as though all of these statements were run:
import demoDlg, demoRadio, demoCheck, demoScale part = demoDlg.Demo(root) part = demoRadio.Demo(root) part = demoCheck.Demo(root) part = demoScale.Demo(root)
But because the script uses a list of name strings, it's easier to change the set of demos embeddedsimply change the list, not the lines of executable code. Moreover, such data-driven code tends to be more compact, less redundant, and easier to debug and maintain. Incidentally, modules can also be imported from name strings by dynamically constructing and running import statements, like this:
for demo in demoModules: exec 'from %s import Demo' % demo # make and run a from part = Demo(root) # or eval('Demo')(window)
The exec statement compiles and runs a Python statement string (here, a from to load a module's Demo class); it works here as if the statement string were pasted into the source code where the exec statement appears. Because it supports any sort of Python statement, this technique is more general than the _ _import_ _ call, but it can also be slower, since it must parse code strings before running them.[*] However, that slowness may not matter in a GUI; users tend to be slower than parsers.
As we saw in Chapter 8, attaching nested frames like this is really just one way to reuse GUI code structured as classes. It's just as easy to customize such interfaces by subclassing rather than embedding. Here, though, we're more interested in deploying an existing widget package than changing it; the next two sections show two other ways to present such packages to users.
9.8.2. Independent Windows
Once you have a set of component classes, any parent will workboth frames, and brand-new, top-level windows. Example 9-33 attaches instances of all four demo bar objects to their own Toplevel windows, not to the same Frame.
Example 9-33. PP3E\Gui\Tour\demoAll-win.py
We met the Toplevel class earlier; every instance generates a new window on your screen. The net result is captured in Figure 9-33. Each demo runs in an independent window of its own instead of being packed together in a single display.
Figure 9-33. demoAll_win: new Toplevel windows
The main root window of this program appears in the lower left of this screenshot; it provides a States button that runs the report method of each demo object, producing this sort of stdout text:
C:\...\PP3E\Gui\Tour>python demoAll_win.py in onMove 0 in onMove 0 in onMove 1 you pressed Open result: C:/PP2ndEd/examples/PP3E/Gui/Tour/demoAll_win.txt demoRadio Open demoCheck 1 1 0 0 0 demoScale 1
9.8.3. Running Programs
Finally, as we learned earlier in this chapter, Toplevel windows function independently, but they are not really independent programs. Quitting any of the windows created in Example 9-33 quits them all, because all run in the same program process. That's OK in some applications, but not all.
To go truly independent, Example 9-34 spawns each of the four demo launchers as independent programs, using the launchmodes module we wrote at the end of Chapter 5. This works only because the demos were written as both importable classes and runnable scripts. Launching them here makes all their names _ _main_ _ when run.
Example 9-34. PP3E\Gui\Tour\demoAll-prg.py
As Figure 9-34 shows, the display generated by this script is similar to the prior one; all four demos come up in windows of their own. This time, though, these are truly independent programs: if any one of the five windows here is quit, the others live on.
Figure 9-34. demoAll_prg: independent programs
220.127.116.11. Cross-program communication
Spawning GUIs as programs is the ultimate in code independence, but it makes the lines of communication between components more complex. For instance, because the demos run as programs here, there is no easy way to run all their report methods from the launching script's window pictured in the middle of Figure 9-34. In fact, the States button is gone this time, and we only get PortableLauncher messages in stdout as the demos start up:
C:\...\PP3E\Gui\Tour>python demoAll_prg.py demoDlg demoRadio demoCheck demoScale
On some platforms, messages printed by the demo programs (including their own State buttons) may show up in the original console window where this script is launched; on Windows, the os.spawnv call used to start programs in launchmodes completely disconnects the child program's stdout stream from its parent. Regardless, there is no way to call all demos' report methods at once; they are spawned programs in distinct address spaces, not imported modules.
Of course, we could trigger report methods in the spawned programs with some of the Inter-Process Communication (IPC) mechanisms we met in Chapter 5. For instance:
Given their event-driven nature, GUI-based programs may need to be augmented with threads, timer-event callbacks, nonblocking input calls, or some combination of such techniques to periodically check for such incoming messages on pipes, fifos, or sockets, and to avoid becoming stuck in wait states (e.g., see the after method call described near the end of the next chapter). We'll explore some of these options in Chapter 11. But since this is well beyond the scope of the current chapter's simple demo programs, I'll leave such cross-program extensions up to more parallel-minded readers for now.
18.104.22.168. Coding for reusability
A postscript: I coded all the demo launcher bars deployed by the last three examples to demonstrate all the different ways that their widgets can be used. They were not developed with general-purpose reusability in mind; in fact, they're not really useful outside the context of introducing widgets in this book.
That was by design; most Tkinter widgets are easy to use once you learn their interfaces, and Tkinter already provides lots of configuration flexibility by itself. But if I had it in mind to code checkbutton and radiobutton classes to be reused as general library components, they would have to be structured differently:
Example 9-35 shows one way to code check button and radio button bars as library components. It encapsulates the notion of associating Tkinter variables and imposes a common usage mode on callersstate fetches rather than press callbacksto keep the interface simple.
Example 9-35. PP3E\Gui\Tour\buttonbars.py
To reuse these classes in your scripts, import and call them with a list of the options that you want to appear in a bar of check buttons or radio buttons. This module's self-test code at the bottom of the file gives further usage details. It generates Figure 9-35a top-level window that embeds two Checkbars, one Radiobar, a Quitter button to exit, and a Peek button to show bar stateswhen this file is run as a program instead of being imported.
Figure 9-35. buttonbars self-test window
Here's the stdout text you get after pressing Peekthe results of these classes' state methods:
x11 [1, 0, 1, 1]  win [1, 0, 0, 1] 
The two classes in this module demonstrate how easy it is to wrap Tkinter interfaces to make them easier to use; they completely abstract away many of the tricky parts of radio button and check button bars. For instance, you can forget about linked variable details completely if you use such higher-level classes instead; simply make objects with option lists and call their state methods later. If you follow this path to its conclusion, you might just wind up with a higher-level widget library on the order of the Pmw package mentioned in Chapter 8.
On the other hand, these classes are still not universally applicable; if you need to run actions when these buttons are pressed, for instance, you'll need to use other high-level interfaces. Luckily, Python/Tkinter already provides plenty. Later in this book, we'll again use the widget combination and reuse techniques introduced in this section to construct larger GUIs. For now, this first chapter in the widget tour is about to make one last stopthe photo shop.