Hello, World! Simple Widgets Binding Events More Widgets Scrollbars Menus Canvas Make things look Beautiful More Information

We are moving to this wiki


Menus are found in most applications that turn beyond a simple display of some information. Menus exist in several flavours. Tk provides much to our convenience, but not everything.

Beware that menus in itself are complex widgets. A bar with a menu that shows options and submenus when you click on it, each of them bound to a command, possibly hooked to a key-sequence as well, is a bit more than just a button. But its the menu that's complex, not the implementation.

A small example

root = TkRoot.new() { title "Ruby/Tk Menu Example" }

bar = TkMenu.new()

sys = TkMenu.new(bar)
sys.add('command', 'label'=>"Quit", 'command'=>proc { root.destroy })
bar.add('cascade', 'menu'=>sys, 'label'=>"System")

file = TkMenu.new(bar)
file.add('command', 'label'=>"Open", 'command'=>proc { puts "Open..." })
file.add('command', 'label'=>"Close", 'command'=>proc { puts "Close..." })
bar.add('cascade', 'menu'=>file, 'label'=>"File")

What happened?
  1. A toplevel menu bar is created,
  2. Two menus sys and file are constructed; it is not necessary to pack the menus, since they aren't shown immediately,
  3. The menus receive a couple of items,
  4. The menus are put into the toplevel menu as cascading menu,
  5. The toplevel menu is added to root (so it appears in the root window for X and M$; a Mac has other ideas),
The entire menu shows when you click on "System" or "File", showing a tear-off menu. You can switch that off with the option 'tearoff'=>false of the menu.

Other types of menus exist as well, the entire list is: command, separator, cascade (submenu), checkbutton and radiobutton.

Ruby Menubar

In the Ruby/Tk bindings, an extra class has been added, which implements a short way of building a menubar and cascading menus. Unfortunately, it uses the second way of building a menu that Tk advises, which means it is a flexible widget that can be anywhere in your window (Mac users won't like this...)

I think the implementation should be changed, because the idea is good. Typically, you would write a way of building menus like this if you need many menus.

It works like this:

menu_spec = [
  [ ['File', 0],
    ['New File',  proc{new_file}],
    ['Open File', proc{open_file}],
    ['Save File', proc{save_file}],
    ['Save As',   proc{save_as}],
    ['Quit',      proc{exit}]
  [ ['Edit', 0],
    ['Cut',       proc{cut_text}],
    ['Copy',      proc{copy_text}],
    ['Paste',     proc{paste_text}]
TkMenubar.new(nil, menu_spec, 'tearoff'=>false).pack('fill'=>'x', 'side'=>'top')

Note: Tk does not know a separate class menubar, it's just a plain toplevel menu.

Tk Menu Button

The Menu button provided by Tk is the second way to hook a complete menu into a window, but now a single menu in an arbitrary place.

Option Menus

Option menus are a Tk extension that allows for easy creation of a button that shows a menu when you click on it to select one of many options. A TkOptionMenubutton takes a TkVariable as second parameter (don't pass nil, the Tk binding chokes on it), followed by a default value and a list of values. Unfortunately, Tk adds the default value to the list, even if it is in there already. So the example below may look straightforward, but it is a good-looking work-around.
root = TkRoot.new() { title "Example Menu Event" }
names = ["One", "Two", "Three"]
var = TkVariable.new()
button = TkOptionMenubutton.new(root, var, *names)

Catching the Virtual TkOptionMenubutton Event

Tk passes a virtual event <<MenuSelect>> to the menu when some activation is made. Since the Tk bindings pre-/append `<' and `>' already, we need to catch the event like this:

button.menu.bind("<MenuSelect>") { p "Menu select!!! (#{var})" }
Note that activation is not only the selection of a new value, but also each change of highlighting while you are selecting a new value.

Popup Menus

Popup menus are the third way to handle menus (where the toplevel menu(bar) and menu button are first and second) and are obviously menus that popup when you want them, typically after clicking with the third mouse button on some item. So you get:
some_widget.bind("Button-3") { |evt| menu.popup(x_root, y_root) }
where we use x_root and y_root because a popup menu is in fact a toplevel window that ignores the window manager. So we need to tell it the exact (x, y) w.r.t. the root window. menu is a TkMenu, like bar in the topmost example.
Hello, World! Simple Widgets Binding Events More Widgets Scrollbars Menus Canvas Make things look Beautiful More Information