Section 7.5
Menus and Menubars
ANY USER OF A GRAPHICAL USER INTERFACE is
accustomed to selecting commands from menus,
which can be found in a menu bar at the
top of a window (or sometimes at the top of a screen). In Java,
menu bars, menus, and the items in menus are JComponents,
just like all the other Swing components. Java makes it easy
to add a menu bar to a JApplet or, as we will see in
Section 7, to a JFrame.
Here is a sample applet that uses menus:
This is a much improved version of the ShapeDraw
applet from Section 5.4. You
can add shapes to the large white drawing area and drag them around.
To add a shape, select one of the commands in the "Add" menu.
The other menus allow you to control the properties of the shapes
and set the background color of the drawing area.
This applet illustrates many ideas related to menus.
There is a menu bar. A menu bar serves
as a container for menus. In this case, there are three
menus in the menu bar. The menus have
titles: "Add", "Color", and "Options". When you click on one of
these titles, the menu items in the menu
appear. Each menu has an associated mnemonic,
which is a character that is underlined in the name. Instead of
clicking on the menu, you can select it by pressing the mnemonic key
while holding down the ALT key. (This assumes that the
applet has the keyboard focus.)
Once the menu has appeared, you can select an item in the menu
by clicking on it, or by using the arrow keys to select the item
and then pressing return. It is possible to assign mnemonics to
individual items in a menu, but I haven't done that in this example.
The commands in the "Add" menu and the "Clear" command do have
accelerators. An accelerator is
a key or combination of keys that can be pressed to invoke a menu
item without ever opening the menu. The accelerator is shown
in the menu, next to the name of the item. For example, the
accelerator for the "Rectangle" command in the "Add" menu is
"Ctrl-R". This means that you can invoke the command by holding
down the Control key and pressing the R key. (Again, this assumes
that the applet has the keyboard focus. The accelerators
might not function at all in an applet. If so, you'll see
how they work in Section 7)
The commands in the "Color" menu act like a set of radio buttons.
Only one item in the menu can be selected at a given time.
The selected item in this menu determines the color of newly added shapes.
Similarly, two of the commands in the "Options" menu act just
like checkboxes. The first of these items determines whether
newly added shapes will be large or small. The second determines
whether newly added shapes will have a black border drawn around them.
The last item in the "Options" menu is actually another menu.
This is called a sub-menu. When you
select this item, the sub-menu will appear. Select an item from the
sub-menu to set the background color of the drawing area.
This applet also demonstrates a pop-up menu.
The pop-up menu appears when you click on one of the shapes in just
the right way. The exact action you have to take depends on the
look-and-feel and is called the pop-up trigger.
The pop-up trigger is probably either clicking with the right mouse
button, clicking with the middle mouse button, or clicking while
holding down the Control key. The pop-up menu in this example
contains commands for editing the shape on which you clicked.
In the rest of this section, we'll look at how all this
can be programmed. If you would like to see the complete
source code of the applet, you will find it in the file
ShapeDrawWithMenus.java.
The source code is just over 600 lines long. The menus are
created and configured in a very long init() method.
Menu Bars and Menus
A menu bar is just an object that belongs to the class JMenuBar.
Since JMenuBar is a subclass of JComponent,
a menu bar could actually be used anywhere in any container. In practice
though, a JMenuBar is generally added to a top-level container
such as a JApplet. This can be done in the init()
method of a JApplet using commands of the form
JMenuBar menubar = new JMenuBar();
setMenuBar(menubar);
The applet's setMenuBar() method does not add
the menu bar to the applet's content pane. The menu bar appears in
a separate area, above the content pane.
A menu bar is a container for menus. The type of menu that can
appear in a menu bar is an object belonging to the class JMenu.
The constructor for a JMenu specifies a title for the
menu, which appears in the menu bar. A menu is added to a menu bar
using the menu bar's add() method. For example, the
following commands will create a menu with title "Options" and
add it to the JMenuBar, menubar:
JMenu optionsMenu = new JMenu("Options");
menubar.add(optionsMenu);
A mnemonic can be added to a JMenu using the menu's
addMnemonic() method, which takes a parameter of type char.
For example:
optionsMenu.setMnemonic('O');
A mnemonic provides a keyboard shortcut for the menu.
If a mnemonic has been set for a menu, then the menu can
be opened by pressing the specified character key while holding
down the ALT key. If the mnemonic character appears in the
title of the menu, it will be underlined. The mnemonic does not
have to be the first character in the title. In fact, it doesn't
have to appear in the title at all. Uppercase and lowercase
letters are equivalent for mnemonics.
Note, by the way, that you can add a menu to a menu bar either
before or after you have added menu items to the menu. You can
add a menu to a menu bar even after it has appeared on the screen,
but I found that I had to call menubar.validate() after
adding the menu to get the menu to appear. You can remove
a menu from a menubar by calling menubar.remove(menu),
but again, I found it necessary to call menubar.validate()
after doing this if the menubar is already on the screen.
Menu Items, Sub-menus, and Separators
Each of the items in a menu is an object belonging to the
class JMenuItem. A JMenuItem can be created
with a constructor that specifies the text that appears in the menu,
and it can be added to the menu using the menu's add() method.
For example:
JMenuItem clear = new JMenuItem("Clear");
optionsMenu.add(clear); // where optionsMenu is of type JMenu
You can specify a mnemonic for the menu item as the second parameter
to the constructor: new JMenuItem("Clear",'C').
The JMenu class also has an add() method that
takes a String as parameter. This version of add()
creates a new menu item with the given string as its text, and
it adds that menu item to the menu. Furthermore, the menu item that
was created is returned as the value of the method. This means
that the two commands shown above can be abbreviated to the
single command:
JMenuItem clear = optionsMenu.add("Clear");
A JMenuItem generates an ActionEvent when it is
invoked by the user. If you want the menu item to have some effect
when it is invoked, you have to add an ActionListener
to the menu item. For example, if listener is the
object of type ActionListener that is to respond to the
"Clear" command, you can say:
clear.addActionListener(listener);
Action events from JMenuItems can be processed in the same way
as action events from JButtons:
When the actionPerformed() method of the listener is called,
the action command will be the text of the menu item, and the source
of the event will be the menu item object itself.
In many cases, the only things you want to do with a menu item are
add it to a menu and add an action listener to it. It's possible
to do both of these with one command. For example:
optionsMenu.add("Clear").addActionListener(listener);
This funny looking line does the following: optionsMenu.add("Clear")
creates creates a menu item and adds it to the menu, optionsMenu,
and it returns the menu item as the value of the method call.
Then the addActionListener() method is applied to the
return value, that is, to the menu item that was just created.
The items in a menu are often separated into logical groups by
horizontal lines drawn across the menu. The "Options" menu
in the sample applet contains two such lines. You can add a
separating line to the end of a JMenu by calling the menu's
addSeparator() method. For example:
optionsMenu.addSeparator();
The JMenu class is actually defined as a subclass of JMenuItem,
which means that you can add one menu to another. The menu that is
added appears as a sub-menu in the menu to which it is added. The title
of the sub-menu appears as an item in the main menu. When the user
selects this item, the sub-menu appears. For example, in the applet
at the top of this page, the "Background Color" sub-menu of
the "Options" menu is created with the commands:
JMenu background = new JMenu("Background Color");
optionsMenu.add(background); // Add as sub-menu.
background.add("Red").addActionListener(canvas);
background.add("Green").addActionListener(canvas);
background.add("Blue").addActionListener(canvas);
background.add("Cyan").addActionListener(canvas);
background.add("Magenta").addActionListener(canvas);
background.add("Yellow").addActionListener(canvas);
background.add("Black").addActionListener(canvas);
background.add("Gray").addActionListener(canvas);
background.add("White").addActionListener(canvas);
Checkbox and Radio Button Menu Items
The JMenuItem class has two subclasses,
JCheckBoxMenuItem and JRadioButtonMenuItem,
that can be used to create menu items that serve as
check boxes and radio buttons. Check boxes and radio buttons
were covered in Section 3 and just about
everything that was said there applies here as well.
A JCheckBoxMenuItem
can be in one of two states, either selected or unselected.
The user changes the state by selecting the menu item.
Just as with a JCheckBox, you can determine the
state of a JCheckBoxMenuItem by calling its
isSelected() method. You can set the state by
calling the item's setSelected(boolean) method.
You can register an ActionListener with a
JCheckBoxMenuItem, if you want to respond immediately
when the user changes the state. In many cases, however,
you can just check the state at the point in your program
where you need to know it. For example, one of the
JCheckBoxMenuItems in the sample applet determines
the size of the shapes that are added to the drawing area.
If the "Add Large Shapes" box is checked when a shape is
added, then the shape will be large; if not, the
shape will be small. There is no action listener in this
case because nothing happens when the user selects the
item (except that it changes state). When the user adds
a new shape, the program calls addLargeShapes.isSelected()
to determine which size to use. (addLargeShapes
is the instance variable that refers to the JCheckBoxMenuItem.)
JRadioButtonMenuItems are almost always used in
groups, where at most one of the radio buttons in the group
can be selected at any given time. As with JRadioButtons,
all the JRadioButtonMenuItems in a group are added
to a ButtonGroup, which ensures that at most one of
the items is selected. In the sample applet, for example, there
are nine JRadioButtonMenuItems in the "Color" menu.
These are represented by instance variables named red,
green, blue, and so on. The code that
creates the menu items and adds them both to the menu and to
a button group look like this:
ButtonGroup colorGroup = new ButtonGroup();
red = new JRadioButtonMenuItem("Red");
shapeColorMenu.add(red); // Add to menu.
colorGroup.add(red); // Add to button group.
green = new JRadioButtonMenuItem("Green");
shapeColorMenu.add(green);
colorGroup.add(green);
blue = new JRadioButtonMenuItem("Blue");
shapeColorMenu.add(blue);
colorGroup.add(blue);
.
.
.
Initially, the "Red" item is selected. This is accomplished
with the command red.setSelected(true). There are
no ActionListeners for the JRadioButtonMenuItems.
When a new shape is added to the drawing area, the program
checks the items in the "Color" menu to see what color is selected,
and that color is used as the color of the new shape. This is
done in an addShape() method using code that look like:
if (red.isSelected())
shape.setColor(Color.red);
else if (green.isSelected())
shape.setColor(Color.green);
else if (blue.isSelected())
shape.setColor(Color.blue);
.
.
.
Note that red, green, and the other variables
that represent the menu items in the "Color" menu must be defined
as instance variables since they are initialized in the init()
method of the applet and are also used in another method. The variable
that represents the ButtonGroup, on the other hand,
is just a local variable in the init() method, since it
is not used in any other method.
Accelerators
A menu item in a JMenu can have an accelerator. The accelerator
is a key, possibly with some modifiers such as ALT or Control,
that the user can press to invoke the
menu item without opening the menu. The menu item is processed in
exactly the same way whether it is invoked with an accelerator, with
a mnemonic, or with the mouse.
An accelerator can be described by a string that specifies the
key to be pressed and any modifiers that must be held down while
the key is pressed. Modifiers are specified by the words
shift, alt, ctrl, and meta.
These must be lower case. The key is specified by an
upper case letter or by the name of certain special keys including:
HOME, END, DELETE, INSERT,
LEFT, RIGHT, UP, DOWN, F1,
F2, .... The string that describes an
accelerator consists of as many modifiers as you want, followed by
any one key specification. You don't use the string directly
to create an accelerator. The string is passed as a parameter
to the static method KeyStroke.getKeyStroke(String),
which returns an object of type KeyStroke. The KeyStroke
object can be used to add an accelerator to a JMenuItem.
This is done by calling the JMenuItem's setAccelerator()
method, which requires a parameter of type KeyStroke.
For example, the first menu item in the "Add" menu of the sample
applet was created with the commands:
JMenuItem rect = new JMenuItem("Rectangle");
rect.setAccelerator( KeyStroke.getKeyStroke("ctrl R") );
The menu item will be invoked if the user holds down the
Control key and presses the R key.
Although it's unfortunate that you have to go through the
KeyStroke class, it's really not all that complicated.
Here are a few more examples of accelerators:
menuitem.setAccelerator( KeyStroke.getKeyStroke("shift ctrl S") );
// User must hold down both SHIFT and
// Control, while pressing the S key.
menuitem.setAccelerator( KeyStroke.getKeyStroke("HOME") );
// User can invoke the menu item just by pressing
// the HOME key.
menuitem.setAccelerator( KeyStroke.getKeyStroke("alt F4") );
// Pressing the F4 key while holding down the ALT key
// is equivalent to selecting the menu item. On a
// Macintosh, ALT refers to the Command key. Under
// Windows and Linux, this accelerator will probably
// be non-functional, since the operating system will
// intercept ALT-F4 and interpret it as a request to
// close the window.
Enabling and Disabling Menu Items
Since a JMenuItem is a JComponent,
you can call menuitem.setEnabled(false) to disable
a menu item and menuitem.setEnabled(true) to enable
a menu item. A menu item that is disabled will appear "grayed out,"
and the user will not be able to select it, either with the mouse or with
an accelerator. It's always a good idea to give the user
visual feedback about the state of a program. Disabling a
menu item when it doesn't make any sense to select it is one
good way of doing this.
Pop-up Menus
A pop-up menu is an object belonging to the class JPopupMenu.
It can be created with a constructor that has no parameters.
Menu items, sub-menus, and separating lines can be added to a
JPopupMenu in exactly the same way that they would be
added to a JMenu. Menu items in a JPopupMenu generate
ActionEvents, just as they would if they were in a JMenu.
However, a JPopupMenu is not added to a menu bar.
In fact, it is not added to any container at all. A JPopupMenu
has a show() method that you can call to make it appear on
the screen. Once it has appeared, the user can invoke an item
in the menu in the usual way. When the user makes a selection,
the menu disappears. The user can click outside the menu or hit
the Escape key to dismiss the menu without making any selection from it.
The show() method takes three parameters. The first
parameter is a Component. Since a JPopupMenu is
not contained in any component, you have to tell it which component
it will be associated with when it appears on the screen.
The next two parameters of show() are integers that specify
where the popup should appear on the screen. The integers give the
coordinates of the point where the upper left corner of the
popup menu will be located. The coordinates are specified in the
coordinate system of the component that is provided as the first
parameter to show().
You can call show() any time you like. Usually, though,
it's done in response to a mouse click. In that case, the first
parameter to show() is generally the component on which the
user clicked, and the next two parameters are the x and
y coordinates where the mouse was clicked. (Actually,
I usually use something like x-10 and y-2 so that
the mouse position will be inside the popup menu, rather than
exactly at the upper left corner. This tends to work better.)
Suppose, for example, that you want to show the menu when the
user right-clicks on a component. You need to set up a mouse listener
for the component and call the popup menu's show() method
in the mousePressed() method of the listener. Let's
say that popup is the variable of type JPopupMenu
that refers to the popup menu and that comp is the
variable of type JComponent that refers to the component.
Then the mousePressed() method could be written as:
public void mousePressed(MouseEvent evt) {
if (evt.isMetaDown()) { // This tests for a right-click
int x = evt.getX(); // X-coord of mouse click
int y = evt.getY(); // Y-coord of mouse click
popup.show( comp, x-10, y-2 );
}
}
If the component is also serving as the mouse listener, you could
replace popup.show(comp,x-10,y-2) with popup.show(this,x-10,y-2).
Note that the only thing you do in response to the user's click
is show the popup. The commands in the popup have to be handled
elsewhere, such as in the actionPerformed() method
of an ActionListener that has been registered to
receive action events from the menu items in the popup menu.
This will work, but it's not the best style for handling
popup menus. The problem is that different platforms have different
standard techniques for calling up popup menus. Under Windows,
the user expects a right-click to call up a menu. Under MacOS,
the user would expect to hold down the Control key while clicking.
If you want your program to work in a natural way on all platforms, you can
call evt.isPopupTrigger() to determine whether a given
mouse event is the proper "trigger" for calling up a popup menu
in the current look-and-feel. Unfortunately, to do things right,
you have to check for the popup trigger in both mousePressed()
and in mouseReleased(), since a given look-and-feel might
use either type of event as a trigger. So, the code for
showing the popup becomes:
public void mousePressed(MouseEvent evt) {
if (evt.isPopupTrigger()) {
int x = evt.getX(); // X-coord of mouse click
int y = evt.getY(); // Y-coord of mouse click
popup.show( comp, x-10, y-2 );
}
}
public void mouseReleased(MouseEvent evt) {
if (evt.isPopupTrigger()) {
int x = evt.getX(); // X-coord of mouse click
int y = evt.getY(); // Y-coord of mouse click
popup.show( comp, x-10, y-2 );
}
}
In a program that uses dragging in addition to popup menus,
the mouse event handling routines can become quite complicated.
This is true for the sample applet at the top of this page.
You can check the source code to see how it's done.