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:

(Applet "ShapeDrawWithMenus" would be displayed here
if Java were available.)

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.


[ Next Section | Previous Section | Chapter Index | Main Index ]