Menus are the pull-down lists you're accustomed to seeing at the top of a window (or the entire display, if you're accustomed to seeing them on a Macintosh). Move the mouse cursor to the menu bar at the top and click on a name (e.g., File), and a list of selectable options pops up under the name you clicked (e.g., Open, Save). The options within a menu might trigger actions, much like clicking on a button; they may also open other "cascading" submenus that list more options, pop up dialog windows, and so on. In Tkinter, there are two kinds of menus you can add to your scripts: top-level window menus and frame-based menus. The former option is better suited to whole windows, but the latter also works as a nested component.
10.2.1. Top-Level Window Menus
In more recent Python releases (using Tk 8.0 and later), you can associate a horizontal menu bar with a top-level window object (e.g., a Tk or Toplevel). On Windows and Unix (X Windows), this menu bar is displayed along the top of the window; on Macintosh, this menu replaces the one shown at the top of the screen when the window is selected. In other words, window menus look like you would expect on whatever underlying platform your script runs upon.
This scheme is based on building trees of Menu widget objects. Simply associate one top-level Menu with the window, add other pull-down Menu objects as cascades of the top-level Menu, and add entries to each of the pull-down objects. Menus are cross-linked with the next higher level, by using parent widget arguments and the Menu widget's add_cascade method. It works like this:
The end result is a tree of Menu widgets with associated command callback handlers. This is probably simpler in code than in words, though. Example 10-1 makes a main menu with two pull downs, File and Edit; the Edit pull down in turn has a nested submenu of its own.
Example 10-1. PP3E\Gui\Tour\menu_win.py
A lot of code in this file is devoted to setting callbacks and such, so it might help to isolate the bits involved with the menu tree-building process. For the File menu, it's done like this:
top = Menu(win) # attach Menu to window win.config(menu=top) # cross-link window to menu file = Menu(top) # attach a Menu to top Menu top.add_cascade(label='File', menu=file) # cross-link parent to child
Apart from building up the menu object tree, this script also demonstrates some of the most common menu configuration options:
Let's see what all this translates to in the realm of the pixel. Figure 10-1 shows the window that first appears when this script is run live on Windows; it looks different, but similar, on Unix and Macintosh.
Figure 10-1. menu_win: a top-level window menu bar
Figure 10-2 shows the scene when the File pull down is selected. Notice that Menu widgets are linked, not packed (or gridded)the geometry manager doesn't really come into play here. If you run this script, you'll also notice that all of its menu entries either quit the program immediately or pop up a "Not Implemented" standard error dialog. This example is about menus, after all, but menu selection callback handlers generally do more useful work in practice.
Figure 10-2. The File menu pull down
And finally, Figure 10-3 shows what happens after clicking the File menu's tear-off line and selecting the cascading submenu in the Edit pull down. Cascades can be nested as deep as you like, but your users probably won't be happy if this gets silly.
Figure 10-3. A File tear-off and Edit cascade
In Tkinter, every top-level window can have a menu bar, including pop ups that you create with the Toplevel widget. Example 10-2 makes three pop-up windows with the same menu bar as the one we just met; when run, it constructs the scene captured in Figure 10-4.
Figure 10-4. Multiple Toplevels with menus
Example 10-2. PP3E\Gui\Tour\menu_win-multi.py
10.2.2. Frame- and Menubutton-Based Menus
Although less commonly used for top-level windows, it's also possible to create a menu bar as a horizontal Frame. Before I show you how, though, let me explain why you should care. Because this frame-based scheme doesn't depend on top-level window protocols, it can also be used to add menus as nested components of larger displays. In other words, it's not just for top-level windows. For example, Chapter 12's PyEdit text editor can be used both as a program and as an attachable component. We'll use window menus to implement PyEdit selections when PyEdit is run as a standalone program, but we'll use frame-based menus when PyEdit is embedded in the PyMail and PyView displays. Both schemes are worth knowing.
Frame-based menus require a few more lines of code, but they aren't much more complex than window menus. To make one, simply pack Menubutton widgets within a Frame container, associate Menu widgets with the Menubuttons, and associate the Frame with the top of a container window. Example 10-3 creates the same menu as Example 10-2, but using the frame-based approach.
Example 10-3. PP3E\Gui\Tour\menu_frm.py
Again, let's isolate the linkage logic here to avoid getting distracted by other details. For the File menu case, here is what this boils down to:
menubar = Frame(parent) # make a Frame for the menubar fbutton = Menubutton(menubar, text='File') # attach a Menubutton to Frame file = Menu(fbutton) # attach a Menu to Menubutton fbutton.config(menu=file) # crosslink button to menu
There is an extra Menubutton widget in this scheme, but it's not much more complex than making top-level window menus. Figures 10-5 and 10-6 show this script in action on Windows.
Figure 10-5. menu_frm: Frame and Menubutton menu bar
Figure 10-6. With the Edit menu selected
The menu widgets in this script provide a default set of event bindings that automatically pop up menus when selected with a mouse. This doesn't look or behave exactly like the top-level window menu scheme shown earlier, but it is close, can be configured in any way that frames can (i.e., with colors and borders), and will look similar on every platform (though this is probably not a feature).
The biggest advantage of frame-based menu bars, though, is that they can also be attached as nested components in larger displays. Example 10-4 and its resulting interface (Figure 10-7) show how.
Figure 10-7. Multiple Frame menus on one window
Example 10-4. PP3E\Gui\Tour\menu_frm-multi.py
Because they are not tied to the enclosing window, frame-based menus can also be used as part of another attachable component's widget package. For example, the menu-embedding behavior in Example 10-5 works even if the menu's parent is another Frame container and not the top-level window.
Example 10-5. PP3E\Gui\Tour\menu_frm-multi2.py
10.2.2.1. Using Menubuttons and Optionmenus
In fact, menus based on Menubutton are even more general than Example 10-3 impliesthey can actually show up anywhere on a display that normal buttons can, not just within a menu bar Frame. Example 10-6 makes a Menubutton pull-down list that simply shows up by itself, attached to the root window; Figure 10-8 shows the GUI it produces.
Figure 10-8. A Menubutton all by itself
Example 10-6. PP3E\Gui\Tour\mbutton.py
The related Tkinter Optionmenu widget displays an item selected from a pull-down menu. It's roughly like a Menubutton plus a display label, and it displays a menu of choices when clicked, but you must link Tkinter variables (described in Chapter 9) to fetch the choice after the fact instead of registering callbacks, and menu entries are passed as arguments in the widget constructor call after the variable.
Example 10-7 illustrates typical Optionmenu usage and builds the interface captured in Figure 10-9. Clicking on either of the first two buttons opens a pull-down menu of options; clicking on the third "state" button fetches and prints the current values displayed in the first two.
Figure 10-9. An Optionmenu at work
Example 10-7. PP3E\Gui\Tour\optionmenu.py
There are other menu-related topics that we'll skip here in the interest of space. For instance, scripts can add entries to system menus and can generate pop-up menus (posted in response to events, without an associated button). Refer to Tk and Tkinter resources for more details on this front.
In addition to simple selections and cascades, menus can also contain disabled entries, check button and radio button selections, and bitmap and photo images. The next section demonstrates how some of these special menu entries are programmed.
10.2.3. Windows with Both Menus and Toolbars
Besides showing a menu at the top, it is common for windows to display a row of buttons at the bottom. This bottom button row is usually called a toolbar, and it often contains shortcuts to items also available in the menus at the top. It's easy to add a toolbar to windows in Tkintersimply pack buttons (and other kinds of widgets) into a frame, pack the frame on the bottom of the window, and set it to expand horizontally only. This is really just hierarchical GUI layout at work again, but make sure to pack toolbars (and frame-based menu bars) early so that other widgets in the middle of the display are clipped first when the window shrinks.
Example 10-8 shows one way to go about adding a toolbar to a window. It also demonstrates how to add photo images in menu entries (set the image attribute to a PhotoImage object) and how to disable entries and give them a grayed-out appearance (call the menu enTRyconfig method with the index of the item to disable, starting from 1). Notice that PhotoImage objects are saved as a list; remember, unlike other widgets, these go away if you don't hold on to them.
Example 10-8. PP3E\Gui\Tour\menuDemo.py
When run, this script generates the scene in Figure 10-10 at first. Figure 10-11 shows this window after being stretched a bit, with its File and Edit menus torn off and its Image menu selected. That's Python creator Guido van Rossum in this script's third menu (wearing his now-deprecated eyeglasses). Run this on your own computer to get a better feel for its behavior.[*]
Figure 10-10. menuDemo: menus and toolbars
Figure 10-11. Images and tear-offs on the job
10.2.3.1. Automating menu construction
Menus are a powerful Tkinter interface device. If you're like me, though, the examples in this section probably seem like a lot of work. Menu construction can be both code intensive and error prone if done by calling Tkinter methods directly. A better approach might automatically build and link up menus from a higher-level description of their contents. In fact, in Chapter 11, we'll meet a tool called GuiMixin that automates the menu construction process, given a data structure that contains all menus desired. As an added bonus, it supports both window and frame-style menus, so it can be used by both standalone programs and nested components. Although it's important to know the underlying calls used to make menus, you don't necessarily have to remember them for long.