Menubars

In this section, we'll look at menubars: how to create them, what goes in them, how they're used, etc.

Properly designing a menubar and its set of menus is beyond the scope of this tutorial. However, if you're creating an application for someone other than yourself, here is a bit of advice. First, if you find yourself with many menus, very long menus, or deeply nested menus, you may need to rethink how your user interface is organized. Second, many people use the menus to explore what the program can do, particularly when they're first learning it, so try to ensure major features are accessible by the menus. Finally, for each platform you're targeting, become familiar with how applications use menus, and consult the platform's human interface guidelines for full details about design, terminology, shortcuts, and much more. This is an area you will likely have to customize for each platform.

Menubars
Menubars.

You'll notice on some recent Linux distributions that many applications show their menus at the top of the screen when active, rather than in the window itself. Tk does not yet support this style of menus.

Menus are implemented as widgets in Tk, just like buttons and entries. Each menu widget consists of a number of different items in the menu. Items have various attributes, such as the text to display for the item, a keyboard accelerator, and a command to invoke.

Menus are arranged in a hierarchy. The menubar is itself a menu widget. It has several items ("File," "Edit," etc.), each of which is a submenu containing more items. These items can include things like the "Open..." command in a "File" menu, but also separators between other items. It can even have items that open up their own submenu (so-called cascading menus). As you'd expect from other things you've seen already in Tk, anytime you have a submenu, it must be created as a child of its parent menu.

Menus are part of the classic Tk widgets; there is not presently a menu in the themed Tk widget set.Menus are implemented as widgets in Tk, just like buttons and entries. Each menu widget consists of a number of different items in the menu. Items have various attributes, such as the text to display for the item, a keyboard accelerator, and a command to invoke.

Menus are arranged in a hierarchy. The menubar is itself a menu widget. It has several items ("File," "Edit," etc.), each of which is a submenu containing more items. These items can include things like the "Open..." command in a "File" menu, but also separators between other items. It can even have items that open up their own submenu (so-called cascading menus). As you'd expect from other things you've seen already in Tk, anytime you have a submenu, it must be created as a child of its parent menu.

Menus are part of the classic Tk widgets; there is not presently a menu in the themed Tk widget set.

Before you Start

It's essential to put the following line in your application somewhere before you start creating menus.


#![allow(unused)]
fn main() {
tk.option_add( "*tearOff", 0 )?;
}

Without it, each of your menus (on Windows and X11) will start with what looks like a dashed line and allows you to "tear off" the menu, so it appears in its own window. You should eliminate tear-off menus from your application as they're not a part of any modern user interface style.

This is a throw-back to the Motif-style X11 that Tk's original look and feel were based on. Get rid of them unless your application is designed to run only on that old box collecting dust in the basement. We'll all look forward to a future version of Tk where this misguided paean to backward compatibility is removed.

While on the topic of ancient history, the option_add bit uses the option database. On X11 systems, this provided a standardized way to customize certain elements of user interfaces through text-based configuration files. It's no longer used today. Older Tk programs may use the option command internally to separate style configuration options from widget creation code. This all pre-dated themed Tk styles, which should be used for that purpose today. However, it's somehow fitting to use the obsolete option database to automatically remove the obsolete tear-off menus.

Creating a Menubar

In Tk, menubars are associated with individual windows; each toplevel window can have at most one menubar. On Windows and many X11 window managers, this is visually obvious, as the menus are part of each window, sitting just below the title bar at the top.

On macOS, though, there is a single menubar along the top of the screen, shared by each window. As far as your Tk program is concerned, each window still does have its own menubar. As you switch between windows, Tk ensures that the correct menubar is displayed. If you don't specify a menubar for a particular window, Tk will use the menubar associated with the root window; you'll have noticed by now that this is automatically created for you when your Tk application starts.

Because all windows have a menubar on macOS, it's important to define one, either for each window or a fallback menubar for the root window. Otherwise, you'll end up with the "built-in" menubar, which contains menus that are only intended for typing commands directly into the interpreter.Because all windows have a menubar on macOS, it's important to define one, either for each window or a fallback menubar for the root window. Otherwise, you'll end up with the "built-in" menubar, which contains menus that are only intended for typing commands directly into the interpreter.

To create a menubar for a window, first, create a menu widget. Then, use the window's menu configuration option to attach the menu widget to the window.


#![allow(unused)]
fn main() {
let win = root.add_toplevel(())?;
let menubar = win.add_menu(())?;
win.configure( -menu(menubar) )?;
}

You can use the same menubar for more than one window. In other words, you can specify the same menubar as the menu configuration option for several toplevel windows. This is particularly useful on Windows and X11, where you may want a window to include a menu, but don't necessarily need to juggle different menus in your application. However, if the contents or state of menu items depends on what's going on in the active window, you'll have to manage this yourself.

This is truly ancient history, but menubars used to be implemented by creating a frame widget containing the menu items and packing it into the top of the window like any other widget. Hopefully, you don't have any code or documentation that still does this.

Adding Menus

We now have a menubar, but that's pretty useless without some menus to go in it. So again, we'll create a menu widget for each menu, each one a child of the menubar. We'll then add them all to the menubar.


#![allow(unused)]
fn main() {
let menu_file = menubar.add_menu(())?;
let menu_edit = menubar.add_menu(())?;
menubar.add_cascade( -menu(menu_file) -label("File") )?;
menubar.add_cascade( -menu(menu_edit) -label("Edit") )?;
}

The add_cascade method adds a menu item, which itself is a menu (a submenu).

Adding Menu Items

Now that we have a couple of menus in our menubar, we can add a few items to each menu.

Command Items

Regular menu items are called command items in Tk. We'll see some other types of menu items shortly. Notice that menu items are part of the menu itself; we don't have to create a separate menu widget for each one (submenus being the exception).


#![allow(unused)]
fn main() {
menu_file.add_command( -label("New")     -command("newFile")   )?;
menu_file.add_command( -label("Open...") -command("openFile")  )?;
menu_file.add_command( -label("Close")   -command("closeFile") )?;
}

On macOS, the ellipsis ("...") is actually a special character, more tightly spaced than three periods in a row. Tk takes care of substituting this character for you automatically.

Each menu item has associated with it several configuration options, analogous to widget configuration options. Each type of menu item has a different set of available options. Cascade menu items have a menu option used to specify the submenu, command menu items have a command option to specify the command to invoke when the item is chosen. Both have a label option to specify the text to display for the item.

We've already seen cascade menu items used to add a menu to a menubar. Not surprisingly, if you want to add a submenu to an existing menu, you also use a cascade menu item in exactly the same way. You might use this to build a "recent files" submenu, for example.


#![allow(unused)]
fn main() {
let menu_recent = menu_file.add_menu(())?;
menu_file.add_cascade( -menu(menu_recent) -label("Open Recent") )?;
for f in recent_files {
    let f = PathBuf::from(f);
    if let Some( file_name ) = f.file_name() {
        menu_recent.add_command(
            -label( file_name.to_str() )
            -command(( "openFile", f ))
        )?;
    }
}
}

Separators

A third type of menu item is the separator, which produces the dividing line you often see between different menu items.


#![allow(unused)]
fn main() {
menu_file.add_separator(())?;
}

Checkbutton and Radiobutton Items

Finally, there are also checkbutton and radiobutton menu items that behave analogously to checkbutton and radiobutton widgets. These menu items have a variable associated with them. Depending on its value, an indicator (i.e., checkmark or selected radiobutton) may be shown next to its label.


#![allow(unused)]
fn main() {
menu_file.add_checkbutton( -label("Check") -variable("check") -onvalue(1) -offvalue(0) )?;
menu_file.add_radiobutton( -label("One")   -variable("radio") -value(1) )?;
menu_file.add_radiobutton( -label("Two")   -variable("radio") -value(2) )?;
}

When a user selects a checkbutton item that is not already checked, it sets the associated variable to the value in onvalue. Selecting an item that is already checked sets it to the value in offvalue. Selecting a radiobutton item sets the associated variable to the value in value. Both types of items also react to any changes you make to the associated variable.

Like command items, checkbutton and radiobutton menu items support a command configuration option that is invoked when the menu item is chosen. The associated variable and the menu item's state are updated before the callback is invoked.

Radiobutton menu items are not part of the Windows or macOS human interface guidelines. On those platforms, the item's indicator is a checkmark, as it would be for a checkbutton item. The semantics still work. It's a good way to select between multiple items since it will show one of them selected (checked).

Manipulating Menu Items

As well as adding items to the end of menus, you can also insert them in the middle of menus via the insert_{type}( index, opts ) method; here index is the position (0..n-1) of the item you want to insert before. You can also delete one or more menu items susing the delete( index ) or delete_range( index ) methods.


#![allow(unused)]
fn main() {
menu_recent.delete_range( 0.. )?;
}

Like most everything in Tk, you can look at or change the value of an item's options at any time. Items are referred to via an index. Usually, this is a number (0..n-1) indicating the item's position in the menu. You can also specify the label of the menu item (or, in fact, a "glob-style" pattern to match against the item's label).


#![allow(unused)]
fn main() {
println!( "{}", menu_file.entrycget( 0, label )? ); // get label of top entry in menu
println!( "{}", menu_file.entryconfigure_options(0)? ); // show all options for an item
}

State

You can disable a menu item so that users cannot select it. This can be done via the state option, setting it to the value disabled. Use a value of normal to re-enable the item.

Menus should always reflect the current state of your application. If a menu item is not presently relevant (e.g., the "Copy" item is only applicable if something in your application is selected), you should disable it. When your application state changes so that the item is applicable, make sure to enable it.


#![allow(unused)]
fn main() {
menu_file.entryconfigure( menu::Index::pattern("Close") -state("disabled") )?;
}

Sometimes you may have menu items whose name changes in response to application state changes, rather than the menu item being disabled. For example, A web browser might have a menu item that changes between "Show Bookmarks" and "Hide Bookmarks" as a bookmarks pane is hidden or displayed.


#![allow(unused)]
fn main() {
bookmarks.entryconfigure( 3, -label("Hide Bookmarks") )?;
}

As your program grows complex, it's easy to miss enabling or disabling some items. One strategy is to centralize all the menu state changes in one routine. Whenever there is a state change in your application, it should call this routine. It will examine the current state and update menus accordingly. The same code can also handle toolbars, status bars, or other user interface components.

Accelerator Keys

The accelerator option is used to indicate a keyboard equivalent that corresponds to a menu item. This does not actually create the accelerator, but only displays it next to the menu item. You still need to create an event binding for the accelerator yourself.

Remember that event bindings can be set on individual widgets, all widgets of a certain type, the toplevel window containing the widget you're interested in, or the application as a whole. As menu bars are associated with individual windows, the event bindings you create will usually be on the toplevel window the menu is associated with.

Accelerators are very platform-specific, not only in terms of which keys are used for what operation, but what modifier keys are used for menu accelerators (e.g., on macOS, it is the "Command" key, on Windows and X11, it is usually the "Control" key). Examples of valid accelerator options are Command-N, Shift+Ctrl+X, and Command-Option-B. Commonly used modifiers include Control, Ctrl, Option, Opt, Alt, Shift, "Command, Cmd, and Meta.

On macOS, modifier names are automatically mapped to the different modifier icons that appear in menus, i.e., Shift ⇒ ⇧, Command ⇒ ⌘, Control ⇒ ⌃, and Option ⇒ ⌥.


#![allow(unused)]
fn main() {
edit.entryconfigure( menu::Index::pattern("Paste"), -accelerator("Command+V") )?;
}

Underline

All platforms support keyboard traversal of the menubar via the arrow keys. On Windows and X11, you can also use other keys to jump to particular menus or menu items. The keys that trigger these jumps are indicated by an underlined letter in the menu item's label. To add one of these to a menu item, use the underline configuration option for the item. Its value should be the index of the character you'd like underlined (from 0 to the length of the string - 1). Unlike with accelerator keys, the menu will watch for the keystroke, so no separate event binding is needed.


#![allow(unused)]
fn main() {
menubar.add_command( -label("Path Browser") -underline(5) )?; // underline "B"
}

Images

It is also possible to use images in menu items, either beside the menu item's label, or replacing it altogether. To do this, use the image and compound options, which work just like in label widgets. The value for image must be a Tk image object, while compound can have the values bottom, center, left, right, top, or none.

Platform conventions for menus suggest standard menus and items that should be available in most applications. For example, most applications have an "Edit" menu, with menu items for "Copy," "Paste," etc. Tk widgets like entry or text will react appropriately when those menu items are chosen. But if you're building your own menus, how do you make that work? What command would you assign to a "Copy" menu item?

Tk handles this with virtual events. As you'll recall from the Tk Concepts chapter, these are high-level application events, as opposed to low-level operating system events. Tk's widgets will watch for specific events. When you build your menus, you can generate those events rather than directly invoking a callback function. Your application can create event bindings to watch for those events too.

Some developers create virtual events for every item in their menus. They generate those events instead of calling routines in their own code directly. It's one way of splitting off your user interface code from the rest of your application. Remember that even if you do this, you'll still need code that enables and disables menu items, adjusts their labels, etc. in response to application state changes.

Here's a minimal example showing how we'd add two items to an "Edit" menu, the standard "Paste" item, and an application-specific "Find..." item that will open a dialog to find or search for something. We'll include an entry widget so that we can check that "Paste" works.


#![allow(unused)]
fn main() {
let _e = root.add_ttk_entry(())?;
let m = root.add_menu(())?;
let m_edit = m.add_menu( "edit" )?;
m.add_cascade( -menu(m_edit) -label("Edit") )?;
m_edit.add_command( -label("Paste")
    -command( tclosure!( tk, || -> TkResult<()> {
        Ok( tk.focus()?.event_generate( event::virtual_event("Paste"), () )? )})))?;
m_edit.add_command( -label("Find...")
    -command( tclosure!( tk, || -> TkResult<()> {
        Ok( root.event_generate( event::virtual_event("OpenFindDialog"), () )? )})))?;
root.configure( -menu(m) )?;

root.bind( event::virtual_event("OpenFindDialog"), tclosure!( tk, || -> TkResult<String> {
    Ok( tk.message_box( -message("I hope you find what you're looking for!") )? )
}))?;
}

When you generate a virtual event, you need to specify the widget that the event should be sent to. We want the "Paste" event to be sent to the widget with the keyboard focus (usually indicated by a focus ring). You can determine which widget has the keyboard focus using the focus command. Try it out, choosing the Paste item when the window is first opened (when there's no focus) and after clicking on the entry (making it the focus). Notice the entry handles the event::virtual_event("Paste") itself. There's no need for us to create an event binding.

The event::virtual_event("OpenFindDialog") event is sent to the root window, which is where we create an event binding. If we had multiple toplevel windows, we'd send it to a specific window.

Tk predefines the following virtual events:

  • event::virtual_event("Clear")
  • event::virtual_event("Copy")
  • event::virtual_event("Cut")
  • event::virtual_event("Paste")
  • event::virtual_event("PasteSelection")
  • event::virtual_event("PrevWindow")
  • event::virtual_event("Redo")
  • event::virtual_event("Undo").

For additional information, see the event mod.

Construct all menus in one statement

The tk crate provides add_menus(), a convenient API to construct all menus in one place:


#![allow(unused)]
fn main() {
let win = root.add_toplevel(())?;
let created_widgets = win.add_menus( "menubar"
    -menu::cascade( "file" -label("File")
        -menu::command( -label("New")       -command("newFile") )
        -menu::command( -label("Open...")   -command("openFile") )
        -menu::command( -label("Close")     -command("closeFile") )
        -menu::cascade( "recent" -label("Open Recent") )
        -menu::separator( no_arg() )
        -menu::checkbutton( -label("Check") -variable("check") -onvalue(1) -offvalue(0) )
        -menu::radiobutton( -label("One")   -variable("radio") -value(1) )
        -menu::radiobutton( -label("Two")   -variable("radio") -value(2) )
    )
)?;
}

See add_menus example for more.

Run Example

cargo run --example menu

cargo run --example add_menus