Menus

The menu widget provides another, more versatile method to create menus. The menu widget is created similarly to the menubutton:
my $menu = $mw->Menu( [ option => value ] );
with the same basic options as for the menubutton. An example of creating a simple menu, as in the example of the menubutton of the previous section, is as follows.
#!perl
# file menu1a.pl
use Tk;
use strict;
use warnings;
my $mw = MainWindow->new;
$mw->title('Menu');
my $menubar = $mw->Menu(-type => 'menubar');
$mw->configure(-menu => $menubar);
my $mfile = $menubar->cascade(-label => '~File', -tearoff => 0);
$mfile->command(-label => '~Load',
                -accelerator => 'Control+l',
                -command => [\&print_it, 'Load']);
$mfile->command(-label => '~Save',
                -accelerator => 'Control+s',
                -command => [\&print_it, 'Save']);
my $medit = $menubar->cascade(-label => '~Edit');
$medit->command(-label => '~Copy',
                -accelerator => 'Control+c',
                -command => [\&print_it, 'Copy']);
$medit->command(-label => '~Paste',
                -accelerator => 'Control+v',
                -command => [\&print_it, 'Paste']);
my $exit = $mw->Button(-text => 'Exit',
                       -command => [$mw => 'destroy']);
$mw->bind('<Control-l>', [\&print_it, 'Load']);
$mw->bind('<Control-s>', [\&print_it, 'Save']);
$mw->bind('<Control-c>', [\&print_it, 'Copy']);
$mw->bind('<Control-v>', [\&print_it, 'Paste']);
$exit->pack;
MainLoop;
sub print_it {
  my $message = ref($_[0]) ? $_[1] : $_[0];
  print "You chose '$message' ...\n"; 
}
In this example create a Menu widget, of the menubar type, associated with the main window. We then create two cascading entries for this menubar, labelled by the $mfile and $medit variables, through the cascade method. Commands are added to these through the command method. The resulting window appears below.

Figure 3.20: Example of a menubar
Image menu1a

There are two new features present in this menu. One, invoked by the -accelerator option, associates a keystroke shortcut for the menu action - the actual binding of the keystroke is done through the bind calls on the main window. The other feature enables one to navigate through the menu by pressing the Alt-x key, where x is the underlined character appearing in the particular menu item. Which key is underlined is determined by the `` '' appearing in the label of the menu item.

Note that in the print_it routine we tested for the existence of a reference as the first parameter passed in; this is because bind passes in, as the first parameter, the object used to create the binding.

If in the previous example we had wanted instead only one menu appearing on the main window, with two submenus ``File'' and ``Edit'', we could simply modify the previous code as follows.

#!perl
# file menu1b.pl
use Tk;
use strict;
use warnings;
my $mw = MainWindow->new;
$mw->title('Menu');
my $menubar = $mw->Menu(-type => 'menubar');
$mw->configure(-menu => $menubar);
my $menu = $menubar->cascade(-label => '~Menu');
my $mfile = $menu->cascade(-label => '~File', -tearoff => 0);
$mfile->command(-label => '~Load',
                -accelerator => 'Control+l',
                -command => [\&print_it, 'Load']);
$mfile->command(-label => '~Save',
                -accelerator => 'Control+s',
                -command => [\&print_it, 'Save']);
my $medit = $menu->cascade(-label => '~Edit');
$medit->command(-label => '~Copy',
                -accelerator => 'Control+c',
                -command => [\&print_it, 'Copy']);
$medit->command(-label => '~Paste',
                -accelerator => 'Control+v',
                -command => [\&print_it, 'Paste']);
my $exit = $mw->Button(-text => 'Exit',
                       -command => [$mw => 'destroy']);
$mw->bind('<Control-l>', [\&print_it, 'Load']);
$mw->bind('<Control-s>', [\&print_it, 'Save']);
$mw->bind('<Control-c>', [\&print_it, 'Copy']);
$mw->bind('<Control-v>', [\&print_it, 'Paste']);
$exit->pack;
MainLoop;
sub print_it {
  my $message = ref($_[0]) ? $_[1] : $_[0];
  print "You chose '$message' ...\n"; 
}
This appears in the figure below.

Figure 3.21: Another example of a menubar
Image menu1b

Occasions may arise where you want more versatility in the form of displaying the menu. One common example of this occurs in many applications where a click of the right mouse button brings up a handy menu of commands appropriate to the relevant window. In these instances you could use the menu widget.

There are two basic ways of displaying the menu:

We will give two examples of the use of a menu in this context. In the first, we will have a text entry box for some information to be entered, and insert a menu, brought up by clicking with the right mouse button over the entry box, which will allow the user to either clear the box or exit.
#!perl
# file menu1.pl
use Tk;
use strict;
use warnings;
my $mw = MainWindow->new;
$mw->title('Name Entry');
my $entry_value = 'not given';
my $enter = $mw->Label(-text => 'Enter your name');
my $name = $mw->Entry(-width => 30);
$name->insert('end', $entry_value);
my $menu = $mw->Menu(-tearoff => 0,
                     -menuitems => [
                                    ['command' => 'Clear',
                                     -command => \&clear],
                                    ['command' => 'Exit',
                                     -command => [$mw => 'destroy']],
                                   ]);
$name->bind('<Button-3>',[\&menu_display, Ev('X'), Ev('Y')]);
my $exit = $mw->Button(-text => 'Exit',
                       -command => [$mw => 'destroy']);
$enter->pack;
$name->pack;
$exit->pack;
MainLoop;

sub clear {
  $name->delete(0, 'end');
}

sub menu_display {
  my ($name, $x, $y) = @_;
  $menu->post($x, $y);
}
This example creates the text entry field as we encountered earlier in this chapter. A menu widget is created with two actions: clear the entry box, or exit the window. The bind method is then used to associate a click of the third mouse button over the text entry box with the display of the menu; the x and y coordinates are specified as where the user clicks, obtained through the ``Ev'' calls.

One can in this example use the Popup method to obain greater control over where to place the menu. For example, if we change the bind call in the previous program to

$name->bind('<Button-3>',[\&menu_display]);
and then changed the menu_display subroutine to
sub menu_display {
  my $name = shift;
  $menu->Popup(-popover => $enter,
               -popanchor => 'n',
              );
}
then the menu, when invoked, will pop up over the label of the main window.

Another example we shall consider is the use of a menu in making a menubar for another top-level window. We shall start off with a text widget which will be used to preview a file. The file to preview will be chosen, when a button is clicked, from a listbox appearing in another top level window. In this top level window a menu will appear, offering us the choice, in the main window, of either loading the selected file or clearing the text. The code for this is as below.

#!perl
# file menu3.pl
use Tk;
use strict;
use warnings;
my ($lb, $tl);
my $mw = MainWindow->new;
$mw->title('File preview');
my $text = $mw->Scrolled('Text');
$text->configure(-width => 40, -height => 16);
my $menu = $mw->Menu(-tearoff => 0,
                     -menuitems => [
                                    ['command' => 'View',
                                     -command => \&load],
                                    ['command' => 'Clear',
                                     -command => \&clear],
                                   ]);
my $exit = $mw->Button(-text => 'Exit',
                      -command => [$mw => 'destroy']);
my $button = $mw->Button(-text => 'File list',
                         -command => \&do_top);
$button->pack;
$text->pack;
$exit->pack;
MainLoop;

sub load {
  $text->delete('1.0', 'end');
  my $selection = $lb->curselection;
  my $file = $lb->get($selection);
  open(SELECTION, $file) or die "Cannot open $file: $!\n";
  while (<SELECTION>) {
    $text->insert('end', $_);
  }
  close SELECTION or die "Cannot close $file: $!\n";
}

sub clear {
  $text->delete('1.0', 'end');
}

sub do_top {
  if (! Exists($tl)) {
    opendir(DIR, '.') or die "Cannot open '.' for reading: $!\n";
    my @files = grep {-T } readdir DIR;
    closedir DIR or die  "Cannot close '.': $!\n";
    $tl = $mw->Toplevel(-menu => $menu);
    $tl->title('File list');
    $lb = $tl->Scrolled('Listbox');
    $lb->configure(-selectmode => 'single');
    $lb->insert('end', sort @files);
    $lb->pack;
    $tl->Button(-text => 'Close',
                -command => sub{$tl->withdraw })->pack;
  }
  else {
    $tl->deiconify;
    $tl->raise;
  }
}
A screen shot of this program in action appears below.

Figure 3.22: Example of a menubar
Image menu3

Here the menu has two options - one to load the selected file in the text box, and the other to clear the text box. This menu appears as a menu bar in the top level window created when the user clicks on the button labelled by ``File list''. The subroutine do_top, as well as having this menu bar, creates a list box out of all the text files in the current directory, out of which the user selects which file to load. Note that we have used scrollbars (via the Scrolled method for both the text area and for the list box.

Randy Kobes 2003-11-17