Recipe 15.14 Creating Menus with Tk

15.14.1 Problem

You want to create a window that has a menu bar at the top.

15.14.2 Solution

Use the Tk Menubutton and Frame widgets:

use Tk;

$main = MainWindow->new( );

# Create a horizontal space at the top of the window for the
# menu to live in.
$menubar = $main->Frame(-relief              => "raised",
                        -borderwidth         => 2)
                ->pack (-anchor              => "nw",
                        -fill                => "x");

# Create a button labeled "File" that brings up a menu
$file_menu = $menubar->Menubutton(-text      => "File",
                                  -underline => 1)
                     ->pack      (-side      => "left" );
# Create entries in the "File" menu
$file_menu->command(-label   => "Print",
                    -command => \&Print);

This is considerably easier if you use the -menuitems shortcut:

$file_menu = $menubar->Menubutton(-text             => "File",
                                 -underline         => 1,
                                 -menuitems         => [
              [ Button => "Print",-command          => \&Print ],
               [ Button => "Save",-command          => \&Save  ] ])
                           ->pack(-side             => "left");

15.14.3 Discussion

Menus in applications can be viewed as four separate components working together: Frames, Menubuttons, Menus, and Menu Entries. The Frame is the horizontal bar at the top of the window that the menu resides in (the menubar). Inside the Frame are a set of Menubuttons, corresponding to Menus: File, Edit, Format, Buffers, and so on. When the user clicks on a Menubutton, the Menubutton brings up the corresponding Menu, a vertically arranged list of Menu Entries.

Options on a Menu are labels (Open, for example) or separators (horizontal lines dividing one set of entries from another in a single menu).

The command entry, like Print in the File menu shown earlier, has code associated with it. When the entry is selected, the command is run by invoking the callback. These are the most common:

$file_menu->command(-label   => "Quit Immediately",
                    -command => sub { exit } );

Separators don't have any action associated with them:

$file_menu->separator( );

A checkbutton menu entry has an on value, an off value, and a variable associated with it. If the variable has the on value, the checkbutton menu entry has a check beside its label. If the variable has the off value, it does not. Selecting the entry on the menu toggles the state of the variable.

$options_menu->checkbutton(-label    => "Create Debugging File",
                           -variable => \$debug,
                           -onvalue  => 1,
                           -offvalue => 0);

A group of radiobuttons is associated with a single variable. Only one radiobutton associated with that variable can be on at any time. Selecting a radiobutton gives the variable the value associated with it:

$debug_menu->radiobutton(-label    => "Level 1",
                         -variable => \$log_level,
                         -value    => 1);

$debug_menu->radiobutton(-label    => "Level 2",
                         -variable => \$log_level,
                         -value    => 2);

$debug_menu->radiobutton(-label    => "Level 3",
                         -variable => \$log_level,
                         -value    => 3);

Create nested menus with the cascade menu entry. For instance, under Netscape Navigator, the File menu button at the left has a cascade menu entry New that brings up a selection of new windows. Creating a cascading menu entry is trickier than creating the other menu entries. You must create a cascade menu entry, fetch the new menu associated with that menu entry, and create entries on that new menu.

# step 1: create the cascading menu entry
$format_menu->cascade          (-label    => "Font");

# step 2: get the new Menu we just made
$font_menu = $format_menu->cget("-menu");

# step 3: populate that Menu
$font_menu->radiobutton        (-label    => "Courier",
                                -variable => \$font_name,
                                -value    => "courier");
$font_menu->radiobutton        (-label    => "Times Roman",
                                -variable => \$font_name,
                                -value    => "times");

A tear-off menu entry lets the user move the menu that it is on. By default, all Menubuttons and cascade menu entries make Menus that have a tear-off entry at the top of them. To create Menus without that default, use the -tearoff option:

$format_menu = $menubar->Menubutton(-text      => "Format",
                                    -underline => 1
                                    -tearoff   => 0)

$font_menu  = $format_menu->cascade(-label     => "Font",
                                    -tearoff   => 0);

The -menuitems option to Menubutton is a shorthand for creating these menubuttons. Pass it an array reference representing the options on the Menubutton. Each option is itself an anonymous array. The first two elements of the option array are the button type ("command", "radiobutton", "checkbutton", "cascade", or "tearoff") and the menu name.

Here's how to use menuitems to make an Edit menu:

my $f = $menubar->Menubutton(-text => "Edit", -underline => 0,
                              -menuitems =>
     [Button => 'Copy',        -command => \&edit_copy ],
     [Button => 'Cut',         -command => \&edit_cut ],
     [Button => 'Paste',       -command => \&edit_paste  ],
     [Button => 'Delete',      -command => \&edit_delete ],
     [Separator => ''],
     [Cascade => 'Object ...', -tearoff => 0,
                               -menuitems => [
        [ Button => "Circle",  -command => \&edit_circle ],
        [ Button => "Square",  -command => \&edit_square ],
        [ Button => "Point",   -command => \&edit_point ] ] ],
    ])->grid(-row => 0, -column => 0, -sticky => 'w');

15.14.4 See Also

The documentation for the Tk module from CPAN; Mastering Perl/Tk, by Stephen Lidie and Nancy Walsh