Search the Catalog
Java Swing, 2nd Edition

Java Swing, 2nd Edition

By Marc Loy, Robert Eckstein, David Wood, James Elliott, Brian Cole
2nd Edition November 2002
0-596-00408-7, 4087
1280 pages, $54.95 US, $85.95 CA

Chapter 14
Menus and Toolbars

In this chapter:
Introducing Swing Menus
Menu Bar Selection Models
The JMenuBar Class
The JMenuItem Class
The JPopupMenu Class
The JMenu Class
Selectable Menu Items
Toolbars

This chapter discusses Swing menus and toolbars. Menus are the richer and more flexible of the two, so they encompass most of the chapter. They tend to be the first thing users explore in learning a new application, so it's fitting that Swing provides a great deal of freedom in laying out menu components.

Toolbars allow you to group buttons, combo boxes, and other elements together in repositionable panels; these tools can assist the user in performing many common tasks. You can add any component to a Swing toolbar, even non-Swing components. In addition, Swing allows the toolbar to be dragged from the frame and positioned inside a child window for convenience.

Introducing Swing Menus

Swing menu components are subclasses of JComponent. Consequently, they have all the benefits of a Swing component, and you can treat them as such with respect to layout managers and containers.

Here are some notable features of the Swing menu system:

Swing provides familiar menu separators, checkbox menu items, pop-up menus, and submenus for use in your applications. In addition, Swing menus support keyboard accelerators and "underline" style (mnemonic) shortcuts, and you can attach menu bars to the top of Swing frames with a single function that adjusts the frame insets accordingly. On the Macintosh, your application can be configured so that this method places the menu bar at the top of the screen, where users expect to find it. Figure 14-1 defines the various elements that make up the menu system in Swing.

Figure 14-1. The elements of the Swing menu system

 

Note that not all platforms support underline-style mnemonics. Notably, on the Macintosh (which has never provided this sort of user interface) mnemonics do not appear at all in the system menu bar, and though they are visible in the actual menus, they do not work in either place. If your application uses mnemonics, you should consider grouping the code to set them up into a separate method that is invoked only when running on a platform that supports them.

All platforms do support accelerators (shortcuts) but have different conventions about the key used to invoke them. You can take advantage of the Toolkit method getMenuShortcutKeyMask to always use the right key.

Menu Hierarchy

The class diagram for Swing menus is shown in Figure 14-2.

Figure 14-2. Swing menu diagram

 

You might be surprised to find AbstractButton in the hierarchy, but menus and menu items have many features in common with Swing buttons. For example, menu items can be highlighted (when the mouse pointer passes over them), they can be clicked to indicate that the user has made a choice, they can be disabled and grayed like buttons, and they can be assigned action commands to assist with event handling. JCheckBoxMenuItem and JRadioButtonMenuItem can even be toggled between two selection states. Since Swing menu components share much of the functionality of Swing buttons, it is appropriate and efficient that they inherit from AbstractButton.

It may also seem surprising that JMenu inherits from JMenuItem, instead of vice-versa. This is because each JMenu contains an implicit menu item that serves as the title of the menu. You'll often hear this part of the menu called the title button. When the user presses or drags the mouse cursor over the title button, the corresponding menu appears. Note, however, that menus do not have to be anchored to a menu bar. You can embed them in other menus, where they act as submenus. This means that the title button must be able to act as a menu item, which would not be possible if the hierarchy was reversed. We discuss this behavior in more detail when we cover the JMenu class later in this chapter.

Almost all of the menu classes implement the MenuElement interface. The MenuElement interface outlines standardized methods that dictate how each Swing menu component behaves when it encounters user input, such as keyboard or mouse events. Swing menu classes typically process these mouse and keyboard events and pass notifications to the component delegates, which handle any necessary redrawing of the component. These methods work in tandem with the MenuSelectionManager class. While you rarely need to implement the MenuElement interface, it helps to know how it works. We show how to implement this interface later in the chapter.

Getting Your Feet Wet

Okay, it's time to jump in. Here is a flashy program that introduces much of the basic Swing menu functionality:

// IntroExample.java
//
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
 
public class IntroExample extends JMenuBar {
 
    String[ ] fileItems = new String[ ] { "New", "Open", "Save", "Exit" };
    String[ ] editItems = new String[ ] { "Undo", "Cut", "Copy", "Paste" };
    char[ ] fileShortcuts = { 'N','O','S','X' };
    char[ ] editShortcuts = { 'Z','X','C','V' };
 
    public IntroExample(  ) {
 
        JMenu fileMenu = new JMenu("File");
        JMenu editMenu = new JMenu("Edit");
        JMenu otherMenu = new JMenu("Other");
        JMenu subMenu = new JMenu("SubMenu");
        JMenu subMenu2 = new JMenu("SubMenu2");
 
        // Assemble the File menus with mnemonics.
        ActionListener printListener = new ActionListener(  ) {
                public void actionPerformed(ActionEvent event) {
                    System.out.println("Menu item [" + event.getActionCommand(  ) +
                                       "] was pressed.");
                }
            };
        for (int i=0; i < fileItems.length; i++) {
            JMenuItem item = new JMenuItem(fileItems[i], fileShortcuts[i]);
            item.addActionListener(printListener);
            fileMenu.add(item);
        }
 
        // Assemble the File menus with keyboard accelerators.
        for (int i=0; i < editItems.length; i++) {
            JMenuItem item = new JMenuItem(editItems[i]);
            item.setAccelerator(KeyStroke.getKeyStroke(editShortcuts[i],
                Toolkit.getDefaultToolkit(  ).getMenuShortcutKeyMask(  ), false));
            item.addActionListener(printListener);
            editMenu.add(item);
        }
 
        // Insert a separator in the Edit menu in Position 1 after "Undo".
        editMenu.insertSeparator(1);
 
        // Assemble the submenus of the Other menu.
        JMenuItem item;
        subMenu2.add(item = new JMenuItem("Extra 2"));
        item.addActionListener(printListener);
        subMenu.add(item = new JMenuItem("Extra 1"));
        item.addActionListener(printListener);
        subMenu.add(subMenu2);
 
        // Assemble the Other menu itself.
        otherMenu.add(subMenu);
        otherMenu.add(item = new JCheckBoxMenuItem("Check Me"));
        item.addActionListener(printListener);
        otherMenu.addSeparator(  );
        ButtonGroup buttonGroup = new ButtonGroup(  );
        otherMenu.add(item = new JRadioButtonMenuItem("Radio 1"));
        item.addActionListener(printListener);
        buttonGroup.add(item);
        otherMenu.add(item = new JRadioButtonMenuItem("Radio 2"));
        item.addActionListener(printListener);
        buttonGroup.add(item);
        otherMenu.addSeparator(  );
        otherMenu.add(item = new JMenuItem("Potted Plant", 
                             new ImageIcon("image.gif")));
        item.addActionListener(printListener);
 
        // Finally, add all the menus to the menu bar.
        add(fileMenu);
        add(editMenu);
        add(otherMenu);
    }
 
    public static void main(String s[ ]) {
        JFrame frame = new JFrame("Simple Menu Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setJMenuBar(new IntroExample(  ));
        frame.pack(  );
        frame.setVisible(true);
    }
}

This example creates a menu bar with three simple menus, attaching mnemonics to the menu items of the File menu and keyboard accelerators to the menu items of the Edit menu. Figure 14-3 shows a mosaic of the different menus that the program produces. It also shows how the Edit menu looks on two different platforms, with the proper accelerator key (Control or Command) used on each.

Figure 14-3. A sample of Swing menu effects

 

In the third menu, we've enhanced the last item with a GIF image of a potted plant. In addition, the first menu item in the Other menu is actually a submenu that pops out to a second submenu, underscoring the recursive nature of menus. If you select any of the menus, you are rewarded with a simple text output that tells you what you clicked:

Menu item [New] was pressed.
Menu item [Radio 1] was pressed.

Don't worry if you do not understand all the classes and methods at this point. We will examine each menu component in detail shortly.

Menu Bar Selection Models

In all GUI environments, menu components allow only one selection to be made at a time. Swing is no exception. Swing provides a data model that menu bars and menus can use to emulate this behavior: the SingleSelectionModel.

The SingleSelectionModel Interface

Objects implementing the SingleSelectionModel interface do exactly what its name suggests: they maintain an array of possible selections and allow one element in the array to be chosen at a time. The model holds the index of the selected element. If a new element is chosen, the model resets the index representing the chosen element and fires a ChangeEvent to each of the registered listeners.

Properties

Objects implementing the SingleSelectionModel interface contain the properties shown in Table 14-1. The selected property is a boolean that tells if there is a selection. The selectedIndex property is an integer index that represents the currently selected item.

Table 14-1: SingleSelectionModel properties

Property

Data type

get

is

set

Default value

selected

boolean

 

·

 

 

selectedIndex

int

·

 

·

 

Events

Objects implementing the SingleSelectionModel interface must fire a ChangeEvent (not a PropertyChangeEvent) when the object modifies its selectedIndex property, i.e., when the selection has changed. The interface contains the standard addChangeListener( ) and removeChangeListener( ) methods for maintaining a list of ChangeEvent listeners.

void addChangeListener(ChangeListener listener)
void removeChangeListener(ChangeListener listener)
Add or remove the specified ChangeListener from the list of listeners receiving this model's change events.

Method

The SingleSelectionModel interface contains one other method:

public void clearSelection( )
Clear the selection value, forcing the selected property to return false.

The DefaultSingleSelectionModel Class

Swing provides a simple default implementation of the SingleSelectionModel interface in the DefaultSingleSelectionModel class.

Properties

DefaultSingleSelectionModel contains just the properties required by the SingleSelectionModel interface, as shown in Table 14-2. The selectedIndex property is an integer index that represents the currently selected item. The default value of -1 indicates that there is no selection. The selected property is a boolean that returns true if the selectedIndex is anything other than -1, and false otherwise.

Table 14-2: DefaultSingleSelectionModel properties

Property

Data type

get

is

set

Default value

selected

boolean

 

·

 

false

selectedIndex

int

·

 

·

-1

Events and methods

The DefaultSingleSelectionModel object provides all the events and methods specified by the SingleSelectionModel interface discussed earlier.

The JMenuBar Class

Swing's JMenuBar class supersedes the AWT MenuBar class. This class creates a horizontal menu bar component with zero or more menus attached to it. JMenuBar uses the DefaultSingleSelectionModel as its data model because the user can raise, or activate, only one of its menus at a given time. Once the mouse pointer leaves that menu, the class removes the menu from the screen (or cancels it, in Swing lingo), and all menus again become eligible to be raised. Figure 14-4 shows the class hierarchy for the JMenuBar component.

Figure 14-4. JMenuBar class diagram

 

You can add JMenu objects to the menu bar with the add( ) method of the JMenuBar class. JMenuBar then assigns an integer index based on the order in which the menus were added. The menu bar displays the menus from left to right on the bar according to their assigned index. In theory, there is one exception: the help menu. You are supposed to be allowed to mark one menu as the help menu; the location of the help menu is up to the L&F. In practice, trying to do this results in JMenuBar throwing an Error.

Menu Bar Placement

You can attach menu bars to Swing frames or applets in one of two ways. First, you can use the setJMenuBar( ) method of JFrame, JDialog, JApplet, or JInternalFrame:

JFrame frame = new JFrame("Menu");
JMenuBar menuBar = new JMenuBar(  );
 
// Attach the menu bar to the frame.
frame.setJMenuBar(menuBar);

The setJMenuBar( ) method is analogous to the setMenuBar( ) method of java.awt.Frame. Like its predecessor, setJMenuBar( ) allows the L&F to determine the location of the menu (typically, it anchors the menu bar to the top of a frame, adjusting the frame's internal Insets accordingly). Both JApplet and JDialog contain a setJMenuBar( ) method-- this means that you can add menu bars to both applets and dialogs. Either way, be sure not to confuse the setJMenuBar( ) method with the older setMenuBar( ) method of AWT when working with Swing menus, or the compiler complains bitterly.

If your application is running on a Macintosh, the Mac L&F can be configured to place menu bars at the top of the screen, where Mac users expect to find them. Setting the system property com.apple.macos.useScreenMenuBar to true activates this behavior. It's disabled by default because most Java programs do not expect this behavior, and they must be coded properly to deal with it. Notably, the Aqua Human Interface Guidelines require that the menu bar is always visible. If your application has any frames that lack menu bars, whenever one of these gains focus, it causes the menu bar to disappear, much to the user's consternation. The most common way of dealing with this is to write a menu factory that generates an identical menu bar for each frame your application uses. Although this is a little extra work, the familiarity and comfort it brings your Mac users is probably worth it.

The second way to add a menu bar is much less common. Recall that the JMenuBar class extends JComponent. This means it can be positioned by a Swing layout manager like other Swing components. For example, we could replace the call to setJMenuBar( ) with the following code:

menuBar.setBorder(new BevelBorder(BevelBorder.RAISED)); 
frame.getContentPane(  ).add(menuBar, BorderLayout.SOUTH);

This places the menu bar at the bottom of the frame, as shown in Figure 14-5. (Note that we set a beveled border around the menu bar to help outline its location.) It would even be possible to add two or three menu bars in different locations. Swing does not require a single menu bar to be anchored to the top of a frame. Because they extend JComponent, multiple menu bars can be positioned anywhere inside a container.

Figure 14-5. JMenuBar positioned as a Swing component

 

TIP: You have to add at least one named menu to a menu bar for it to gain any thickness. Otherwise, it appears as a thin line--similar to a separator.

Of course, you'd never actually want to do this without a very compelling reason. It robs the L&F of its opportunity to place the menu bar in the appropriate location. Moving something as fundamental as a menu bar is almost certain to cause confusion and usability challenges for your users; having multiple menu bars would be baffling.

Properties

The properties of the JMenuBar class are shown in Table 14-3. menu is an indexed property that references each JMenu attached to the menu bar. The read-only menuCount property maintains a count of these attached menus. Remember that the single selection model allows only one menu to be activated at a time. If any menu is currently activated, the selected property returns true; otherwise, the property returns false. The componentAtIndex property accesses the menu associated with the given index. It is similar to the indexed menu property, except the contents are cast to a Component. If there is no component associated with that index, the getComponentAtIndex( ) accessor returns null. The component property returns a reference to this (i.e., the menu bar itself); subElements returns an array consisting of the menus on the menu bar.

Table 14-3: JMenuBar properties

Property

Data type

get

is

set

Default value

accessibleContexto

AccessibleContext

·

 

 

JMenuBar.AccessibleJMenuBar( )

borderPaintedb

boolean

 

·

·

true

component

Component

·

 

 

this

componentAtIndexi

Component

·

 

 

true

helpMenuu

JMenu

·

 

·

Throws an Error

layouto

LayoutManager

·

 

·

BoxLayout(X_AXIS)

marginb

Insets

·

 

·

null

menuCount

int

·

 

 

0

menui

JMenu

·

 

 

null

selected

boolean

 

·

 

false

selectionModelb

SingleSelectionModel

·

 

·

DefaultSingleSelectionModel( )

subElements

MenuElement[ ]

·

 

 

 

UIb

MenuBarUI

·

 

·

From L&F

UIClassIDo

String

·

 

 

"MenuBarUI"

bbound, iindexed, ooverridden, uunimplemented
See also properties from the JComponent class (Table 3-6).

The margin property controls the amount of space between the menu bar's border and its menus while the borderPainted property can be used to suppress the painting of the menu bar's border even if the border property has a non-null value. Setting borderPainted to false prevents the normal painting of the border. For more information about Swing borders, see Chapter 13.

WARNING: The helpMenu property is supposed to allow you to designate one JMenu as the help menu (which has a special location in some operating systems), but this property has never been implemented, and using it throws an Error even in SDK 1.4. You can take advantage of the fact that the menu bar uses a BoxLayout to insert "glue" to position your (ordinary) help menu at the right edge when appropriate, but this shifts the burden of knowing when to do that (based on the current L&F) to your code, which is unfortunate.

Constructor

public JMenuBar( )
Create and initialize an empty JMenuBar object.

Menu

public JMenu add(JMenu menu)
You can use this method to attach a JMenu to the menu bar set. Because of the BoxLayout of JMenuBar, menus are displayed on the menu bar from left to right in the order that you add( ) them. The method returns a reference to the JMenu that was passed in, allowing you to string together calls--for example, menubar.add(menu).add(menuitem).

Miscellaneous

public int getComponentIndex(Component c)
Return the index associated with the component reference passed in. If there is no match to the component, the method returns a -1. The only type of component it makes sense to pass in is JMenu.

public void setSelected(Component c)
Force the menu bar (and its associated model) to select a particular menu, which fires a ChangeEvent in the menu bar's single selection model. This method, for example, is called when a mnemonic key for a particular menu is pressed. Note that this is different than the boolean selected property listed in Table 14-3.

public void updateUI( )
Force the UIManager to refresh the L&F of the component, based on the current UI delegate.

JMenuBar also implements the methods specified by the MenuElement interface, which is covered later in this chapter.

The JMenuItem Class

Before discussing menus, we should introduce the JMenuItem class. Figure 14-6 shows the class diagram for the JMenuItem component.

Figure 14-6. JMenuItem class diagram

 

A JMenuItem serves as a wrapper for strings and images to be used as elements in a menu. The JMenuItem class is essentially a specialized button and extends the AbstractButton class. Its behavior, however, is somewhat different from standalone buttons. When the mouse pointer is dragged over a menu item, Swing considers the menu item to be selected. If the user releases the mouse button while over the menu item, it is considered to be chosen and should perform its action.

There is an unfortunate conflict in terminology here. Swing considers a menu item selected when the mouse moves over it, as updated by the MenuSelectionManager and classes that implement the MenuElement interface. On the other hand, Swing considers a button selected when it remains in one of two persistent states, such as a checkbox button remaining in the checked state until clicked again. So when a menu item is selected, its button model is really armed. Conversely, when a menu item is deselected, its button model is disarmed. Finally, when the user releases the mouse button over the menu item, the button is considered to be clicked, and the AbstractButton's doClick( ) method is invoked.

Menu Item Shortcuts

Menu items can take both keyboard accelerators and (on some platforms) mnemonics. Mnemonics are an artifact of buttons; they appear as a single underline below the character that represents the shortcut. Keyboard accelerators, on the other hand, are inherited from JComponent. With menu items, they have the unique side effect of appearing in the menu item. (Their exact appearance and location is up to the L&F.) Figure 14-7 shows both mnemonics and keyboard accelerators.

Figure 14-7. Mnemonics and keyboard accelerators

 

Keyboard accelerators and mnemonics perform the same function: users can abbreviate common GUI actions with keystrokes. However, a mnemonic can be activated only when the button (or menu item) it represents is visible on the screen. Menu item keyboard accelerators can be invoked any time the application has the focus-- whether the menu item is visible or not. Also, as noted, accelerators work on all platforms and all L&Fs while mnemonics are less universal. Menus may be assigned both at once.

Let's look at programming both cases. Keyboard accelerators typically use a variety of keystrokes: function keys, command keys, or an alphanumeric key in combination with one or more modifiers (e.g., Shift, Ctrl, or Alt). All of these key combinations can be represented by the javax.swing.KeyStroke class, but only some of them are appropriate for the platform and L&F in use. Hence, you can assign a keyboard accelerator to a menu item by setting its accelerator property with a KeyStroke object configured using the default toolkit's menuShortcutKeyMask property, as follows:

JMenuItem m = new JMenuItem("Copy");
m.setAccelerator(KeyStroke.getKeyStroke('C',
    Toolkit.getDefaultToolkit(  ).getMenuShortcutKeyMask(  ), false));

Under Metal, this sets the accelerator to Ctrl-C, which is the letter C typed in combination with the Ctrl key. The accelerator appears at the right side of the menu item (though again, the position is up to the L&F). The KeyStroke class is covered in more detail in Chapter 27.

The second, less universal, way to set a shortcut is through the mnemonic property of the AbstractButton superclass:

JMenuItem mi = new JMenuItem("Copy");
mi.setMnemonic('C');

The mnemonic property underlines the character you pass into the setMnemonic( ) method. Note that mnemonic characters cannot take modifiers; they are simple letters. Be sure to use a letter that exists in the menu item's label. Otherwise, nothing is underlined, and the user will not know how to activate the keyboard shortcut. Also be sure to set up mnemonics only if you're running on a platform and L&F that support them.

As of SDK 1.4, you can use the displayedMnemonicIndex property to cope with menu items containing multiple copies of the character you're using as a mnemonic, if it makes more sense for a later instance to be underlined (for example, the common Save As menu item in which the uppercase "A" should get the underline). To achieve this, once you set up the mnemonic, call setDisplayedMnemonicIndex with a value of 5.

Images

In Swing, menu items can contain (or consist entirely of) icons. This can be a visual aid if the icon can convey the intended meaning more clearly. You can pass an Icon object to the constructor of the JMenuItem class as follows:

JMenu menu = new JMenu("Justify");
 
// The first two menu items contain text and an image. The third
// uses only the image.
menu.add(new JMenuItem("Center", new ImageIcon("center.gif")));
menu.add(new JMenuItem("Right", new ImageIcon("right.gif")));
menu.add(new JMenuItem(new ImageIcon("left.gif")));

By default, the text is placed to the left of the image. This is shown on the left in Figure 14-8. As you can see, this often misaligns the images to the right of the text, especially if there is a menu item consisting only of an image. If the menu item images are all the same width, you can improve the appearance of your menus by altering the text's position using the setHorizontalTextAlignment( ) method:

JMenu menu = new JMenu("Justify");
 
// The first two menu items contain text and an image. The third
// uses only the image. The text is now set to the right.
JMenuItem item1= new JMenuItem("Center", new ImageIcon("center.gif")));
item1.setHorizontalTextAlignment(SwingConstants.RIGHT);
JMenuItem item2= new JMenuItem("Right", new ImageIcon("right.gif")));
item2.setHorizontalTextAlignment(SwingConstants.RIGHT);
 
// Now add the menu items to the menu.
menu.add(item1);
menu.add(item2);
menu.add(new JMenuItem(new ImageIcon("left.gif")));

Figure 14-8. Image and text placement in menu items

 

This positions the text on the other side of the images, as shown on the right of Figure 14-8. You can trace the setHorizontalTextAlignment( ) method up the class hierarchy to the AbstractButton class. As we mentioned before, the JMenuItem class is a button object with respect to its text and image. AbstractButton contains a setVerticalTextAlignment( ) method as well, so if the accompanying image is taller than the menu item text, you can use this method to set the text's vertical position as well. (See the AbstractButton class in Chapter 5 and the OverlayLayout class in Chapter 11 for more information about alignment with menu items and buttons.) The image is placed to the left of the text if you construct a menu item from an Action object (more on this later in the chapter).

Java supports image transparency, so if you require some parts of an image to be transparent, you can specify a "transparent" color in the GIF file (many paint programs allow you to do this), or you can create a specialized color filter that seeks out specific pixel colors and changes their opacity before passing the resulting Image onto the menus. The former is much easier.

Event Handling

There are a number of ways to process events from menu items. Because menu items inherit ActionEvent functionality from AbstractButton, one approach is to assign an action command to each menu item (this is often done automatically with named components) and attach all of the menu items to the same ActionListener. Then, in the actionPerformed( ) method of the listener, use the event's getActionCommand( ) method to obtain the action command of the menu item generating the event. This tells the listener which menu item has been clicked, allowing it to react accordingly. This is the approach used in IntroExample.java earlier in this chapter and PopupMenuExample.java, which is discussed later.

Alternatively, you can register a separate ActionListener class with each menu item, which takes the guesswork out of determining the menu item selected. However, Swing allows you to go a step further. The most object-oriented approach is to create a specialized Action class that corresponds to each of the tasks a user might request of your application. This lets you bundle the code for each program action together with the action's name, icon, keystrokes, and other attributes in one place. You can then use this Action to create the menu item, which automatically sets the item's text, image, accelerator, and so on.

This technique is particularly powerful if you want to be able to invoke the same action in multiple ways (such as from a toolbar as well as a menu). You can use the same Action instance to create the menu item and toolbar button, and they'll both have appropriate labels and appearances. If the application needs to disable the action because it's not currently appropriate, calling setEnabled on the Action instance automatically updates all user interface elements associated with the action (thus dimming both your menu item and toolbar button). Similarly, changing other attributes of the action, such as its name or icon, automatically updates any associated user-interface components.

Although prior to SDK 1.3 it wasn't possible to construct a JMenuItem from an Action directly, adding the Action to a JMenu or JPopupMenu had the same effect: the menu would create and configure an appropriate JMenuItem for you.

Properties

The properties for the JMenuItem class are shown in Table 14-4. Most of the properties shown are superclass properties reconfigured to ensure that the menu item's "button" acts like a menu item should. The borderPainted property is always false; menu items never take a border. The focusPainted property is also false to ensure that a focus rectangle is never drawn around the menu item. horizontalTextPosition and horizontalAlignment are both initialized to JButton.LEFT. This places the text to the left of the image icon and places the text and image icon on the left side of the menu item. (See the previous example for information on how to reconfigure this.)

Table 14-4: JMenuItem properties

Property

Data type

get

is

set

Default value

acceleratorb

KeyStroke

·

 

·

null

accessibleContexto

Accessible Context

·

 

 

JMenuItem.AccessibleJMenuItem( )

armedb, o

boolean

 

·

·

false

borderPaintedo

boolean

 

·

·

false

componento

Component

·

 

 

 

enabledo

boolean

 

·

·

true

focusPaintedo

boolean

 

·

·

false

horizontalAlignmento

int

·

 

·

JButton.LEFT

horizontalTextPositiono

int

·

 

·

JButton.LEFT

menuDragMouseListeners1.4

MenuDragMouseListener[ ]

·

 

 

 

menuKeyListeners1.4

MenuKeyListener[ ]

·

 

 

 

modelo

ButtonModel

·

 

·

DefaultButtonModel( )

subElementso

MenuElement[ ]

·

 

 

 

UIb

MenuItemUI

 

 

·

From L&F

UIClassIDo

String

·

 

 

"MenuItemUI"

1.4since 1.4, bbound, ooverridden
See also properties from the AbstractButton class (Table 5-4).

The accelerator property sets the keyboard accelerator for the menu item; the accelerator is typically drawn to the right of the menu item string. The armed property simply maps a boolean down to the armed state of the component model, ButtonModel. You can use this to programmatically select the menu item, if needed. The enabled property is a boolean that indicates whether the user can select the menu item. If the menu item is disabled, JMenuItem automatically grays the text and associated image. As discussed earlier, the most powerful way to control the enabled state of a menu item is to associate it with an Action object so that it automatically tracks the action's enabled state. The subElements property provides an array of submenus contained in this menu item.

Constructors

JMenuItem( )
JMenuItem(Action action)
JMenuItem(Icon icon)
JMenuItem(String string)
JMenuItem(String string, Icon icon)
JMenuItem(String string, int mnemonic)
Create a menu item with the appropriate icon or string. You also have the option to specify a mnemonic if you initialize with a string. Since Version 1.3, you can use the properties of an Action to directly configure the properties of the JMenuItem.

Events

JMenuItems send many different kinds of events. Perhaps the most important are ActionEvents, which are fired when an item is selected. ChangeEvents are fired when button properties change. Methods for adding and removing listeners for these events are inherited from AbstractButton.

JMenuItem also uses special events for reporting mouse motions and key presses on top of the menu item. These are the MenuDragMouseEvent and MenuKeyEvent. Here are the methods for registering listeners for these events:

addMenuDragMouseListener (MenuDragMouseListener 1)
removeMenuDragMouseListener (MenuDragMouseListener 1)
These methods add or remove a specific MenuDragMouseListener interested in being notified when there is a MenuDragMouseEvent.

addMenuKeyListener (MenuKeyListener 1)
removeMenuKeyListener (MenuKeyListener 1)
These methods add or remove a specific MenuKeyListener interested in being notified when there is a MenuKeyEvent.

The following methods provide support for firing these events, though you will probably never need to call them:

public void processMenuDragMouseEvent (MenuDragMouseEvent e)
Fire a specific MenuDragMouseEvent notification based on the type of MouseEvent that was observed. If the MouseEvent listed was MOUSE_ENTERED, for example, the menu invokes the fireMenuDragMouseEntered( ) method.

public void processMenuKeyEvent (MenuKeyEvent e)
Fire a specific MenuKeyEvent notification based on the type of MenuKeyEvent that was observed. If the MenuKeyEvent listed was KEY_RELEASED, for example, the menu invokes the fireMenuKeyReleased( ) method.

Method

public void updateUI( )
Force the current UI manager to reset the current delegate for the component, thus updating the component's L&F.

Menu Element Interface

public void menuSelectionChanged(boolean isIncluded)
public MenuElement[ ] getSubElements( )
public Component getComponent( )
public void processMouseEvent(MouseEvent event, MenuElement path[ ], MenuSelectionManager manager)
public void processKeyEvent(KeyEvent event, MenuElement path[ ], MenuSelectionManager manager)
Implement the MenuElement interface, discussed later in this chapter.

The MenuDragMouseEvent Class

Swing generates a series of events while the mouse is dragging across an open menu. One event, MenuDragMouseEvent, describes the drag in relation to a particular menu item. You can listen for these events by adding an object that implements MenuDragMouseListener to the addMenuDragMouseListener( ) method of JMenuItem. The object implementing MenuDragMouseListener will have four separate methods that can be invoked in response to a mouse drag inside a menu; each one indicates exactly what happened with the drag. Table 14-5 shows the properties of the MenuDragMouseEvent.

Properties

Table 14-5: MenuDragMouseEvent properties

Property

Data type

get

is

set

Default value

clickCounto

int

·

 

 

 

ido

int

·

 

·

 

manager

MenuSelectionManager

·

 

 

 

modifierso

Object

·

 

·

 

path

MenuElement[ ]

·

 

 

 

popupTriggero

boolean

 

·

 

 

source

Object

·

 

 

 

wheno

long

·

 

 

 

xo

int

·

 

 

 

yo

int

·

 

·

 

ooverridden
See also java.awt.event.MouseEvent.

There are no defaults for the event; all properties are set in the constructor. The source property indicates the object that sent the event. The id property describes the type of event that was fired. The when property gives the event a timestamp. The modifiers property allows you to test various masks to see which mouse button is being pressed, as well as the Alt, Ctrl, Shift, and Meta keys. The x and y properties give the current location of the mouse pointer relative to the component in question. The clickCount property describes how many times a mouse button has been clicked prior to this drag. The popupTrigger property indicates whether this mouse event should cause a popup menu to appear. The path property gives an ordered array of MenuElement objects, describing the path to this specific menu. Finally, the manager property contains a reference to the current MenuSelectionManager for this menu system.

Constructor

public MenuDragMouseEvent(Component source, int id, long when, int modifiers, int x, int y, int clickCount, boolean popupTrigger, MenuElement[ ] path, MenuSelectionManager manager)
Initialize each of the properties described in Table 14-5 with the specified values.

The MenuDragMouseListener Interface

The MenuDragMouseListener interface, which is the conduit for receiving the MenuDragMouseEvent objects, contains four methods. One method is called when the mouse is dragged inside the menu item, the second when the mouse is released inside the menu item. Finally, the last two are called when the mouse is dragged into a menu item, or dragged out of a menu item.

Methods

public abstract void menuDragMouseDragged(PopupMenuEvent e)
Called when the mouse is dragged inside of a menu item.

public abstract void menuDragMouseReleased(PopupMenuEvent e)
Called when the mouse has been released inside of a menu item.

public abstract void menuDragMouseEntered(PopupMenuEvent e)
Called when the mouse is being dragged, and has entered a menu item.

public abstract void menuDragMouseExited(PopupMenuEvent e)
Called when the mouse is being dragged, and has exited a menu item.

The MenuKeyEvent Class

Swing also generates an event when a specific menu item receives a key event. Note that the key event does not have to be directed at the specific menu (i.e., an accelerator or mnemonic). Instead, the menu item responds to any key events generated while the menu pop up containing it is showing on the screen. You can listen for these events by adding an object that implements MenuKeyListener to the addMenuKeyListener( ) method of JMenuItem. The object implementing MenuKeyListener will have three separate methods that can be invoked in response to a menu key event.

Table 14-6 shows the properties of MenuKeyEvent. There are no defaults for the event; all properties are set in the constructor. The source property indicates the object that sent the event. The id property describes the type of event that was fired. The when property gives the event a timestamp. The modifiers property allows you to test various masks to see which mouse button is being pressed, as well as the Alt, Ctrl, Shift, and Meta keys. The keyCode and keyChar properties describe the key that was actually pressed. The path property gives an ordered array of MenuElement objects, describing the path to this specific menu. Finally, the manager property contains a reference to the current MenuSelectionManager.

Table 14-6: MenuKeyEvent properties

Property

Data Type

get

is

set

Default Value

ido

int

·

 

·

 

keyCharo

char

·

 

·

 

keyCodeo

int

·

 

 

 

manager

MenuSelectionManager

·

 

 

 

modifierso

Object

·

 

·

 

path

MenuElement[ ]

·

 

 

 

source

Object

·

 

 

 

wheno

long

·

 

 

 

ooverridden
See also java.awt.event.keyEvent.

Constructor

public MenuDragMouseEvent(Component source, int id, long when, int keyCode, char keyChar, MenuElement[ ] path, MenuSelectionManager manager)
This constructor takes each of the properties described in Table 14-6.

The MenuKeyListener Interface

The MenuKeyListener interface, which is the conduit for receiving the MenuKeyEvent objects, contains three methods. One method is called when a key is typed (i.e., pressed and released) while the second is called after a key is pressed. This third is called after a key is released. Note that if a key is pressed and held down for a few seconds, Swing emulates the traditional key behavior: it considers the key both "typed" and "pressed" again.

Methods

public abstract void menuKeyTyped(MenuKeyEvent e)
Called when a key intended for this menu element is both pressed and released.

public abstract void menuKeyPressed(MenuKeyEvent e)
Called when a key intended for this menu element is pressed.

public abstract void menuKeyReleased(MenuKeyEvent e)
Called when a key intended for this menu element is released.

Menu items cannot exist by themselves; they must be embedded in menus. Swing implements two closely related styles of menus: anchored menus and pop-up menus. Swing uses the JMenu and JPopupMenu classes to implement these menus.

The JPopupMenu Class

Pop-up menus are an increasingly popular user-interface feature. These menus are not attached to a menu bar; instead, they are free-floating menus that associate themselves with an underlying component. This component is called the invoker. Linked to specific interface elements, pop-up menus are nicely context-sensitive. They are brought into existence by a platform-dependent pop-up trigger event that occurs while the mouse is over the invoking component. In AWT and Swing, this trigger is typically a mouse event. Once raised, the user can interact with the menu normally. Figure 14-9 is an example of a pop-up menu in Swing.

Figure 14-9. A pop-up menu in Swing

 

You can add or insert JMenuItem, Component, or Action objects to the pop-up menu with the add( ) and insert( ) methods. The JPopupMenu class assigns an integer index to each menu item and orders them based on the layout manager of the pop-up menu. In addition, you can add separators to the menu by using the addSeparator( ) method; these separators also count as an index. Figure 14-10 shows the class diagram for the JPopupMenu component. Starting with SDK 1.4, pop-up menus use the Popup class to actually draw themselves. This class is also used for other briefly displayed interface elements like tooltips.

Figure 14-10. JPopupMenu class diagram

 

Displaying the Pop-up Menu

Pop-up menus are usually raised by invoking the show( ) method in response to a platform-specific pop-up trigger. The show( ) method sets the location and invoker properties of the menu before making it visible. Pop ups are automatically canceled by a variety of events, including clicking a menu item; resizing an invoking component; or moving, minimizing, maximizing, or closing the parent window. (You won't need to worry about canceling pop-up menus.) You raise the pop-up menu at the right time by checking all your MouseEvents to see if they're the pop-up trigger. A word to the wise: if a MouseEvent is the pop-up trigger, be sure not to pass it on to your superclass, or Swing could cancel the pop-up menu immediately after raising it! Also, be sure to check both pressed and released events because some platforms use one or the other. The easiest way to do that is to check all mouse events. Here's a processMouseEvent( ) method that raises a pop-up menu upon receiving the appropriate trigger:

public void processMouseEvent(MouseEvent e) {
    if (e.isPopupTrigger(  )) {
        popup.show(this, e.getX(  ), e.getY(  ));
    }
    else {
        super.processMouseEvent(e); 
    }
}

Note the use of isPopupTrigger( ) in java.awt.event.MouseEvent to check whether the mouse event is a trigger in a platform-independent way. Since SDK 1.3, JPopupMenu has an equivalent method you can use in the same way.

When the mouse moves outside the component, Swing no longer sends pop-up trigger events to that component, and its pop-up menu cannot be raised. This gives you the opportunity to define different pop-up menus for different underlying components, adding context sensitivity to your interface.

Properties

The properties of the JPopupMenu class are shown in Table 14-7. Pop-up menus have many properties. The visible property tells whether the pop-up menu is currently showing on the screen; you can use the setVisible( ) method to show or hide the pop up, but if it is a free-floating pop up, it is much easier to use the show( ) method. The location property provides the coordinates on the screen where the pop-up menu is or has been raised. The read-only margin property gives the amount of space between the pop-up window border and an imaginary rectangle surrounding the individual menu items.

Table 14-7: JPopupMenu properties

Property

Data type

get

is

set

Default value

accessibleContexto

AccessibleContext

·

 

 

JPopupMenu.accessibleJPopupMenu( )

borderPainted

boolean

 

·

·

true

component

Component

·

 

 

 

componentAtIndexi

Component

·

 

 

 

invoker

Component

·

 

·

 

labelb

String

·

 

·

""

layouto

LayoutManager

·

 

·

GridBagLayout( )

lightWeightPopupEnabled

boolean

 

·

·

getDefaultLightWeightPop-upEnabled( )

locationo

Point

 

 

·

 

margin

Insets

·

 

 

 

popupMenuListeners1.4

PopupMenuListener[ ]

·

 

 

 

popupSize

Dimension

 

 

·

 

selectionModel

SingleSelectionModel

·

 

 

DefaultSingleSelectionMo-del( )

subElements

MenuElement[ ]

·

 

 

 

UIb

PopupMenuUI

·

 

·

BasicPopupMenuUI( )

UIClassIDo

String

·

 

 

"PopupMenuUI"

visibleb, o

boolean

 

·

·

false

1.4since 1.4, bbound, iindexed, ooverridden
See also properties from the JMenuItem class (Table 14-4).

The invoker property is a reference to the component that is responsible for hosting the pop-up menu. The borderPainted property indicates whether the pop-up menu should paint its border. The label property gives each pop-up menu a specific label; the individual L&F is free to use or ignore this property as it sees fit. Note that label is a String and not a JLabel. componentAtIndex is an indexed property that returns the component at the specified index.

The lightWeightPopupEnabled property allows the programmer to enable or disable the potential use of lightweight components to represent the pop-up menu. If the property is set to true, Swing uses a lightweight component when the pop-up is inside the top-level component's drawing space, and a heavyweight when the pop-up extends beyond its space. If your interface uses any heavyweight components, they interfere with lightweight pop ups, so you should turn off this feature. You can set the default value of this property for all pop-up menus using the static setDefaultLightWeightPopupEnabled( ) method.

Events

JPopupMenu objects fire a PopupMenuEvent under two conditions: when the menu becomes visible or invisible, or is canceled without a menu item selection. The class contains the standard addPopupMenuListener( ) and removePopupMenuListener( ) methods for maintaining a list of PopupMenuEvent subscribers.

public void addPopupMenuListener(PopupMenuListener l)
public void removePopupMenuListener(PopupMenuListener l)
Add or remove a PopupMenuListener from the object's event queue.

The ability to be notified right before the pop-up menu becomes visible gives you the opportunity to tweak the state and contents of the menu based on the current state of your application, which can make your interface even more helpful and context-sensitive.

Note that when the pop-up menu is canceled, it also becomes invisible, so two events are potentially triggered. The cancelation event itself seems to be fired rarely in current implementations, though. If you need to know when the menu goes away, use the popupMenuWillBecomeInvisible handler.

Constructors

public JPopupMenu( )
public JPopupMenu(String title)
Create an empty pop-up menu. The second constructor accepts a String as the title of the pop-up menu.

Menu Items

public JMenuItem add(JMenuItem menuItem)
public Component add(Component c)
public JMenuItem add(Action a)
Add various elements to the pop-up menus. Objects extending either JMenuItem or JComponent can be added, but the latter functions best if it implements the MenuElement interface. If you specify an Action, its many properties are used to derive an appropriate JMenuItem, and its text is placed to the right of any image icon. The item retains its association with the action so that updates to the action (changes in name, icon, enabled state, etc.) are reflected by the item. The resulting JMenuItem is then returned, which you can use to alter its formatting.

public JMenuItem insert(Action a, int index)
public Component insert(Component component, int index)

Insert a specific menu item at a particular index. You can pass in a JComponent or an Action to these methods. If you use a JComponent, it's best if it implements the MenuElement interface. If you specify an Action, its various properties are used to derive an appropriate JMenuItem, and its text is placed to the right of any image icon. As usual, the item retains its association with the action. The resulting JMenuItem is then returned, which you can use to alter its formatting. All menu item indices that were previously at or after the specified position are incremented.

public void addSeparator( )
Add a separator to the pop-up menu. Typically, a separator consists of a single horizontal line drawn across the pop-up menu. Note that, like menu items, the separator counts as an index in the menu. The separator used is an instance of an inner class, not the regular JSeparator; it is always horizontal.

Display

public void show(Component invoker, int x, int y)
Paint the pop-up menu at the requested coordinates. The method takes a reference to the invoking component. It is functionally equivalent to the following calls: setInvoker( ), setLocation( ), and setVisible( ).

public void setPopupSize(int width, int height)
An alternate way to establish a preferred size for the pop up. (The other way is the popupSize property, which takes a Dimension.)

Miscellaneous

public int getComponentIndex(Component c)
Return the index associated with the component reference c. If there is no match to the component passed in, the method returns -1.

public static boolean getDefaultLightWeightEnabled
Return the default value for the lightWeightPopupEnabled property.

public boolean isPopupTrigger(MouseEvent e)
Since SDK 1.3, an alternate way to check whether a given mouse event should trigger a pop-up menu in the current L&F.

public static void setDefaultLightWeightPopupEnabled(boolean aFlag)
Set the default value of the lightWeightPopupEnabled property, which controls whether a lightweight or heavyweight component is used for the pop up.

public void setSelected(Component c)
Force the pop-up menu's model to select a particular menu item. This forces a property change event in the pop-up menu's single selection model.

public void updateUI( )
Force the default user interface manager to update itself, thus resetting the delegate to display a new PopupMenuUI.

Menu Element Interface

public void menuSelectionChanged(boolean isIncluded)
public MenuElement[ ] getSubElements( )
public Component getComponent( )
public void processMouseEvent(MouseEvent event, MenuElement path[ ], MenuSelectionManager manager)
public void processKeyEvent(KeyEvent event, MenuElement path[ ], MenuSelectionManager manager)
Implement the MenuElement interface, which is covered later in this chapter.

Using Pop-up Menus

Here is a program that demonstrates the use of the JPopupMenu class. The example is similar to the one that generated Figure 14-9, except that the pop up communicates events from the pop-up menu and from each of its menu items.

//  PopupMenuExample.java
//
import java.awt.*;
import java.awt.event.*;
 
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
 
public class PopupMenuExample extends JPanel {
 
    public JPopupMenu popup;
 
    public PopupMenuExample(  ) {
        popup = new JPopupMenu(  ); 
        ActionListener menuListener = new ActionListener(  ) {
            public void actionPerformed(ActionEvent event) {
                System.out.println("Popup menu item [" +
                                   event.getActionCommand(  ) + "] was pressed.");
            }
        };
        JMenuItem item;
        popup.add(item = new JMenuItem("Left", new ImageIcon("left.gif")));
        item.setHorizontalTextPosition(JMenuItem.RIGHT);
        item.addActionListener(menuListener);
        popup.add(item = new JMenuItem("Center", new ImageIcon("center.gif")));
        item.setHorizontalTextPosition(JMenuItem.RIGHT);
        item.addActionListener(menuListener);
        popup.add(item = new JMenuItem("Right", new ImageIcon("right.gif")));
        item.setHorizontalTextPosition(JMenuItem.RIGHT);
        item.addActionListener(menuListener);
        popup.add(item = new JMenuItem("Full", new ImageIcon("full.gif")));
        item.setHorizontalTextPosition(JMenuItem.RIGHT);
        item.addActionListener(menuListener);
        popup.addSeparator(  );
        popup.add(item = new JMenuItem("Settings . . ."));
        item.addActionListener(menuListener);
 
        popup.setLabel("Justification");
        popup.setBorder(new BevelBorder(BevelBorder.RAISED));
        popup.addPopupMenuListener(new PopupPrintListener(  ));
 
        addMouseListener(new MousePopupListener(  ));
    }
 
    // An inner class to check whether mouse events are the pop-up trigger
    class MousePopupListener extends MouseAdapter {
        public void mousePressed(MouseEvent e) { checkPopup(e); }
        public void mouseClicked(MouseEvent e) { checkPopup(e); }
        public void mouseReleased(MouseEvent e) { checkPopup(e); }
 
        private void checkPopup(MouseEvent e) {
            if (e.isPopupTrigger(  )) {
                popup.show(PopupMenuExample.this, e.getX(  ), e.getY(  ));
            }
        }
    }
 
    // An inner class to show when pop-up events occur
    class PopupPrintListener implements PopupMenuListener {
        public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
            System.out.println("Popup menu will be visible!");
        }
        public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
            System.out.println("Popup menu will be invisible!");
        }
        public void popupMenuCanceled(PopupMenuEvent e) {
            System.out.println("Popup menu is hidden!");
        }
    }
 
    public static void main(String s[ ]) {
        JFrame frame = new JFrame("Popup Menu Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setContentPane(new PopupMenuExample(  ));
        frame.setSize(300, 300);
        frame.setVisible(true);
    }
}

The interesting parts of this program are the methods of MousePopupListener. These call a private method, checkPopup( ), to see if we've received an event that should raise the pop-up menu. If we get a valid trigger event, we show the pop up at the mouse location. This is an alternative to the approach of overriding processMouseEvent( ) that was demonstrated in "Displaying the Pop-up Menu."

The PopupMenuEvent Class

This is a simple event that tells listeners that the target pop-up menu is about to become visible or invisible, or that it has been canceled. Note that it doesn't tell which one has occurred. The object implementing PopupMenuListener will define three separate methods that can be called by a pop-up menu; each one indicates exactly what happened with the target pop-up menu object.

Constructor

public PopupMenuEvent(Object source)
The constructor takes a reference to the object that fired the event.

The PopupMenuListener Interface

The PopupMenuListener interface, which is the conduit for receiving the PopupMenuEvent objects, contains three methods. One method is called when the pop up is canceled, and the other two indicate that the pop up is about to show or hide itself. This interface must be implemented by any listener object that wishes to be notified of changes to the pop-up menu.

Methods

public abstract void popupMenuCanceled(PopupMenuEvent e)
Called when the target pop-up menu is canceled or removed from the screen. (This seems to be called rarely in practice.)

public abstract void popupMenuWillBecomeInvisible(PopupMenuEvent e)
Called when the pop-up menu is about to be removed from the screen.

public abstract void popupMenuWillBecomeVisible(PopupMenuEvent e)
Called when the pop-up menu is about show itself on the screen. This is an excellent opportunity to update the contents of the menu (or their enabled states) based on current application conditions.

The JMenu Class

The JMenu class represents the anchored menus attached to a JMenuBar or another JMenu. Menus directly attached to a menu bar are called top-level menus. Submenus, on the other hand, are not attached to a menu bar but to a menu item that serves as its title. This menu item title is typically marked by a right arrow, indicating that its menu appears alongside the menu item if the user selects it. See Figure 14-11.

Figure 14-11. Top-level menu and submenu

 

JMenu is a curious class. It contains a MenuUI delegate, but it uses a ButtonModel for its data model. To see why this is the case, it helps to visualize a menu as two components: a menu item and a pop-up menu. The menu item serves as the title. When it is pressed, it signals the pop-up menu to show itself either below or directly to the right of the menu item. JMenu actually extends the JMenuItem class, which makes it possible to implement the title portion of the menu. This, in effect, makes it a specialized button. On some platforms you can use the mnemonic property of the JMenuItem superclass to define a shortcut for the menu's title and, consequently, the menu. In addition, you can use the enabled property of JMenuItem to disable the menu if desired.

As with pop-up menus, you can add or insert JMenuItem, Component, or Action objects in the pop-up portion of the menu by calling the add( ) and insert( ) methods. You can also add a simple string to the menu; JMenu creates the corresponding JMenuItem object for you internally. The JMenu class assigns an integer index to each menu item and orders them based on the layout manager used for the menu. You can also add separators to the menu by using the addSeparator( ) method.

WARNING: You cannot use keyboard accelerators with JMenu objects (top-level or submenu), because accelerators trigger actual program actions, not simply the display of a menu from which actions can be chosen. On some platforms you can use the setMnemonic( ) method to set a shortcut to bring up the menu, but the only universal, reliable approach is to assign keyboard accelerators to the non-submenu JMenuItems that trigger program actions.

You can programmatically cause the submenu to pop up on the screen by setting the popupMenuVisible property to true. Be aware that the pop up does not appear if the menu's title button is not showing.

Figure 14-12 shows the class diagram for the JMenu component.

Figure 14-12. JMenu class diagram

 

Properties

The JMenu properties are listed in Table 14-8. JMenu uses a JPopupMenu to represent its list of menu items. If you wish to access that underlying menu, you can do so using the popupMenu property. The popupMenuVisible property tracks whether the menu's pop-up portion is currently visible. As noted, setting this to true when the title button is visible causes the pop up to appear. JMenu also contains a selected property, which indicates if the user has selected the title button of the menu. Both properties should mirror each other.

Table 14-8: JMenu properties

Property

Data type

get

is

set

Default value

accessibleContexto

AccessibleContext

·

 

 

JMenu.accessibleJMenu( )

component

Component

·

 

 

 

componentOrientation1.4, o

ComponentOrientation

·

 

·

From L&F

delay

int

·

 

·

0

itemCount

int

·

 

 

0

itemi

JMenuItem

·

 

 

null

layouto

LayoutManager

·

 

·

OverlayLayout( )

menuComponentCount

int

·

 

 

0

menuComponenti

Component

·

 

 

null

menuComponents

Component[ ]

·

 

 

 

menuListeners1.4

MenuListener[ ]

·

 

 

 

modelo

ButtonModel

·

 

·

DefaultButtonModel( )

popupMenu

JPopupMenu

·

 

 

 

popupMenuVisible

boolean

 

·

·

false

selected

boolean

 

·

·

false

subElements

MenuElement[ ]

·

 

 

 

tearOffu

boolean

 

·

 

Throws an Error

topLevelMenu

boolean

 

·

 

 

UIb

MenuUI

 

 

·

From L&F

UIClassID

String

·

 

 

"MenuUI"

1.4since 1.4, bbound, iindexed, ooverridden, uunimplemented
See also properties from the JMenuItem class (Table 14-4).

The topLevelMenu property has the value true if this JMenu is directly attached to a menu bar and is not a submenu. item is an indexed property that allows access to each of the JMenuItem objects in the menu, while itemCount maintains a count of all of the JMenuItem objects that are present. The delay property specifies the amount of time, in milliseconds, that the underlying menu waits to appear or disappear after receiving the corresponding event. The delay must be set to a positive integer, or setDelay( ) throws an IllegalArgumentException.

The menuComponent property is a more generalized version of the item property; it returns the component at the given index as a Component rather than as a JMenuItem. In addition, the menuComponentCount property retains a count of the menu items, separators, and other components currently in the menu. The menuComponents property lets you access each of the items in the menu, returned as an array of Component objects.

The componentOrientation property is used to accommodate non-Western languages in which text does not flow left to right. JMenu overrides this property in order to properly pass changes on to the JPopupMenu delegate it uses.

WARNING: The tearOff property is not yet implemented and is reserved for (increasingly dubious) future use in Swing. Trying to use it throws an Error (rather than something more appropriate like an UnsupportedOp-erationException). Since an Error is supposed to indicate a catastrophic failure of the virtual machine, using this property will almost certainly crash your application.

Constructor

public JMenu( )
public JMenu(Action a)
public JMenu(String s)
public JMenu(String s, boolean b)
Initialize a default JMenu. You have the option of specifying a string for the JMenu to display--as well as a boolean for the tearOff property (which is ignored)--or binding it to an Action.

Menu Items

public JMenuItem add(JMenuItem menuItem)
public Component add(Component c)
public void add(String s)
public JMenuItem add(Action a)
Add various elements to the menus. Objects from both JMenuItem and JComponent can be added, but the latter functions best if it implements the MenuElement interface. If you specify a String as the parameter, a menu item with the appropriate label is created. If you specify an Action, its text and icon properties are used to derive an appropriate JMenuItem, and its text is placed to the right of the icon. It retains its association with the action and is updated to reflect changes to its properties. The resulting JMenuItem is returned, which you can use to alter its formatting.

public void 468addSeparator( )
Add a separator to the menu. Typically, a separator consists of a single horizontal line drawn across the menu.

public void insert(String s, int index)
public JMenuItem insert(JMenuItem mi, int index)
public JMenuItem insert(Action a, int index)
Insert a specific menu item at a particular index. The index must be positive, or the method throws an IllegalArgumentException. You can pass in a JMenuItem, a String, or an Action to these methods. If you specify a String as the parameter, a menu item with the appropriate label is created. If you specify an Action, its text and icon properties are used to derive an appropriate JMenuItem, and its text is placed to the right of the icon. As usual, the menu retains its association with the action. The resulting JMenuItem is returned, which you can use to alter its formatting. All menu items that were previously at or after the specified position are increased by one.

public void insertSeparator(int index)
Insert a horizontal separator at the position specified by the integer index. The index must be positive, or the method throws an IllegalArgumentException. All menu items' indices that were previously at or after the specified position are increased by one.

public void remove(JMenuItem item)
public void remove(int index)
Remove the menu item that matches the JMenuItem passed in or that currently occupies the specified integer index. If there are no matches (or if the position does not exist), no changes are made to the menu. If the function is successful, all menu items' indices following the removed menu item are reduced by one.

public void removeAll( )
Remove all of the items from the menu.

Miscellaneous

public void updateUI( )
Force the default user interface manager to update itself, thus resetting the delegate to display a new MenuUI.

public void setMenuLocation(int x, int y)
Set a custom location at which the menu appears when shown.

public boolean isMenuComponent(Component c)
Determine whether the component c is present anywhere in the menu. This method searches all submenus as well.

public String paramString( )
Return a String specifying the current state of the menu properties (intended for debugging purposes).

Event

JMenu objects fire a MenuEvent when the user has selected or deselected the menu's title button. The JMenu object contains the standard addChangeListener( ) and removeChangeListener( ) methods for maintaining a list of MenuEvent subscribers.

public void addMenuListener(MenuListener listener)
public void removeMenuListener(MenuListener listener)
Add or remove a MenuListener from the list of listeners receiving this menu's events.

MenuElement Interface

public void menuSelectionChanged(boolean isIncluded)
public MenuElement[ ] getSubElements( )
public Component getComponent( )
public void processKeyEvent(KeyEvent event, MenuElement path[ ], MenuSelectionManager manager)
Implement the MenuElement interface, which is covered later in this chapter.

Working with Menus

Here is a program that demonstrates the use of the JMenu class. In this program, we use Swing's Action class to process the menu events. (We'll also use actions for toolbars later in this chapter.)

//  MenuExample.java
//
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
 
public class MenuExample extends JPanel {
 
    public JTextPane pane;
    public JMenuBar menuBar;
 
    public MenuExample(  ) {
        menuBar = new JMenuBar(  );
        JMenu formatMenu = new JMenu("Justify");
        formatMenu.setMnemonic('J');
 
        MenuAction leftJustifyAction = new MenuAction("Left", 
                                       new ImageIcon("left.gif"));
        MenuAction rightJustifyAction = new MenuAction("Right",
                                        new ImageIcon("right.gif"));
        MenuAction centerJustifyAction = new MenuAction("Center",
                                         new ImageIcon("center.gif"));
        MenuAction fullJustifyAction = new MenuAction("Full",
                                       new ImageIcon("full.gif"));
 
        JMenuItem item;
        item = formatMenu.add(leftJustifyAction);
        item.setMnemonic('L');
        item = formatMenu.add(rightJustifyAction);
        item.setMnemonic('R');
        item = formatMenu.add(centerJustifyAction);
        item.setMnemonic('C');
        item = formatMenu.add(fullJustifyAction);
        item.setMnemonic('F');
 
        menuBar.add(formatMenu);
        menuBar.setBorder(new BevelBorder(BevelBorder.RAISED));
 
    }
 
    class MenuAction extends AbstractAction {
   
        public MenuAction(String text, Icon icon) {
            super(text,icon);
        }
 
        public void actionPerformed(ActionEvent e) {
            try { pane.getStyledDocument(  ).insertString(0 ,
                  "Action ["+e.getActionCommand(  )+"] performed!\n", null);
            } catch (Exception ex) { ex.printStackTrace(  ); } 
        }
    }
 
    public static void main(String s[ ]) {
 
        MenuExample example = new MenuExample(  );
        example.pane = new JTextPane(  );
        example.pane.setPreferredSize(new Dimension(250, 250));
        example.pane.setBorder(new BevelBorder(BevelBorder.LOWERED));
 
        JFrame frame = new JFrame("Menu Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setJMenuBar(example.menuBar);
        frame.getContentPane(  ).add(example.pane, BorderLayout.CENTER);
        frame.pack(  );
        frame.setVisible(true);
    }
}

Our Actions are all instances of the inner class MenuActions. As we add each Action to the menu, it creates an appropriate JMenuItem (with the image left-justified) and returns it to us. This allows us to manipulate the resulting menu item in any way we want; in this case, we add a mnemonic for each item. You can run this program on various platforms to see if they support mnemonics. You shouldn't rely on mnemonics as a key part of your user interface in a program intended for multiple platforms (in fact, you should avoid setting them at all unless you are sure the platform supports them).

The resulting program produces a menu bar with a single menu, as shown in Figure 14-13. The menu contains four menu items and is similar in appearance to the pop-up example. When the user clicks any menu item, Swing generates an ActionEvent to be processed by the actionPerformed( ) method of our MenuAction class. As in the previous examples, this results in the name of the menu item being printed. For variety, we have added a simple JTextPane to display the results of our menu choice, instead of using the system output. See Chapters 19 and 22 and for more information on JTextPane.

Figure 14-13. A set of menu items with icons and mnemonics

 

The MenuEvent Class

This is a simple event that tells listeners that the target menu has been raised, selected, or canceled. Note that it doesn't tell which one has occurred. The listener defines three separate methods that can be called to deliver the menu event; each one tells exactly what happened.

Constructor

public MenuEvent(Object source)
The constructor takes a reference to the object that fires the event.

The MenuListener Interface

The MenuListener interface, which is the conduit for receiving MenuEvents, specifies three methods. One method is called when the menu is canceled; the other two are called when the title button of the menu is selected or deselected. This interface must be implemented by any listener object that needs to be notified of changes to the menu object.

Methods

public abstract void menuCanceled(MenuEvent e)
This method is called when the menu is canceled or removed from the screen.

public abstract void menuDeselected(MenuEvent e)
This method is called when the target menu's title button is deselected.

public abstract void menuSelected(MenuEvent e)
This method is called when the target menu's title button is selected.

Selectable Menu Items

So far, we've covered traditional menu items that produce a simple, text-oriented label associated with an action. But that's not the only type of item to which users are accustomed. Swing provides for two selectable menu items: the checkbox menu item and the radio button menu item.

The JCheckBoxMenuItem Class

Checkbox menu items are represented by the JCheckBoxMenuItem class. As you might have guessed, this object behaves similarly to the JCheckBox object. By clicking on a checkbox menu item, you can toggle a UI-defined checkmark that generally appears to the left of the menu item's label. There is no mutual exclusion between adjoining JCheckBoxMenuItem objects-- the user can check any item without affecting the state of the others. Figure 14-14 shows the class diagram for the JCheckBoxMenuItem component.

Figure 14-14. JCheckBoxMenuItem class diagram

 

Properties

Table 14-9 shows the properties of the JCheckBoxMenuItem class. JCheckBoxMenuItem inherits the JMenuItem model (ButtonModel) and its accessors. The JCheckBoxMenuItem class also contains two additional component properties. The state property has the value true if the menu item is currently in the checked state, and false if it is not. The selectedObjects property contains an Object array of size one, consisting of the text of the menu item if it is currently in the checked state. If it is not, getSelectedObjects( ) returns null. The getSelectedObjects( ) method exists for compatibility with AWT's ItemSelectable interface.

Table 14-9: JCheckBoxMenuItem properties

Property

Data type

get

is

set

Default value

accessibleContexto

AccessibleContext

·

 

 

JCheckBoxMenuItem.AccessibleJCheckBoxMenuItem( )

selectedObjectso

Object[ ]

·

 

 

 

state

boolean

·

 

·

false

UIb

CheckBoxMenuItemUI

·

 

·

From L&F

UIClassIDo

String

·

 

 

"CheckBoxMenuItem"

bbound, ooverridden
See also properties from the JMenuItem class (Table 14-4).

Constructors

public JCheckBoxMenuItem( )
public JCheckBoxMenuItem(Action action)
public JCheckBoxMenuItem(Icon icon)
public JCheckBoxMenuItem(String text)
public JCheckBoxMenuItem(String text, Icon icon)
public JCheckBoxMenuItem(String text, boolean checked)
public JCheckBoxMenuItem(String text, Icon icon, boolean checked)
These constructors initialize the JCheckBoxMenuItem with a specified action (since Version 1.3), icon, or string. The additional boolean value initializes the state property, specifying whether the menu item is initially checked.

Miscellaneous

public void updateUI( )
Force the current UI manager to reset and repaint the delegate for the component, thus updating the component's L&F.

Using Checkbox Menu Items

Here's a program using the JCheckBoxMenuItem class. It is similar to the JMenu example, except that each menu item now has a checkmark next to it. We've done nothing to make the items mutually exclusive; that comes next. We have, however, reworked the code to use more-portable keyboard accelerators rather than mnemonics. Note that we used M (middle) as the accelerator for the Center option because C is generally reserved for Copy. Figure 14-15 shows the result.

// CheckBoxMenuItemExample.java
//
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
 
public class CheckBoxMenuItemExample extends JPanel {
    public JTextPane pane;
    public JMenuBar menuBar;
    public JToolBar toolBar;
 
    public CheckBoxMenuItemExample(  ) {
        menuBar = new JMenuBar(  );
        JMenu justifyMenu = new JMenu("Justify");
        ActionListener actionPrinter = new ActionListener(  ) {
            public void actionPerformed(ActionEvent e) {
                try { pane.getStyledDocument(  ).insertString(0 ,
                      "Action ["+e.getActionCommand(  )+"] performed!\n", null);
                } catch (Exception ex) { ex.printStackTrace(  ); }
            }
        };
        JCheckBoxMenuItem leftJustify = new
               JCheckBoxMenuItem("Left", new ImageIcon("left.gif"));
        leftJustify.setHorizontalTextPosition(JMenuItem.RIGHT);
        leftJustify.setAccelerator(KeyStroke.getKeyStroke('L',
                        Toolkit.getDefaultToolkit(  ).getMenuShortcutKeyMask(  )));
        leftJustify.addActionListener(actionPrinter);
        JCheckBoxMenuItem rightJustify = new
               JCheckBoxMenuItem("Right", new ImageIcon("right.gif"));
        rightJustify.setHorizontalTextPosition(JMenuItem.RIGHT);
        rightJustify.setAccelerator(KeyStroke.getKeyStroke('R',
                        Toolkit.getDefaultToolkit(  ).getMenuShortcutKeyMask(  )));
        rightJustify.addActionListener(actionPrinter);
        JCheckBoxMenuItem centerJustify = new
               JCheckBoxMenuItem("Center", new ImageIcon("center.gif"));
        centerJustify.setHorizontalTextPosition(JMenuItem.RIGHT);
        centerJustify.setAccelerator(KeyStroke.getKeyStroke('M',
                        Toolkit.getDefaultToolkit(  ).getMenuShortcutKeyMask(  )));
        centerJustify.addActionListener(actionPrinter);
        JCheckBoxMenuItem fullJustify = new
               JCheckBoxMenuItem("Full", new ImageIcon("full.gif"));
        fullJustify.setHorizontalTextPosition(JMenuItem.RIGHT);
        fullJustify.setAccelerator(KeyStroke.getKeyStroke('F',
                        Toolkit.getDefaultToolkit(  ).getMenuShortcutKeyMask(  )));
        fullJustify.addActionListener(actionPrinter);
 
        justifyMenu.add(leftJustify);
        justifyMenu.add(rightJustify);
        justifyMenu.add(centerJustify);
        justifyMenu.add(fullJustify);
 
        menuBar.add(justifyMenu);
        menuBar.setBorder(new BevelBorder(BevelBorder.RAISED));
 
    }
 
    public static void main(String s[ ]) {
        CheckBoxMenuItemExample example = new CheckBoxMenuItemExample(  );
        example.pane = new JTextPane(  );
        example.pane.setPreferredSize(new Dimension(250, 250));
        example.pane.setBorder(new BevelBorder(BevelBorder.LOWERED));
 
        JFrame frame = new JFrame("Menu Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setJMenuBar(example.menuBar);
        frame.getContentPane(  ).add(example.pane, BorderLayout.CENTER);
        frame.pack(  );
        frame.setVisible(true);
    }
}
Figure 14-15. A series of checkbox menu items

 

The JRadioButtonMenuItem Class

Swing implements radio button menu items with the JRadioButtonMenuItem class. As you might expect, it shares the characteristics of the JRadioButton class and is intended to represent a group of mutually exclusive choices. Some L&Fs indicate this exclusivity visually by showing circular "buttons" to the left of the selectable choices.

TIP: Even though L&Fs visually distinguish between checkbox and radio button items, the distinction can be subtle and unfamiliar to users, so it's a good idea to use separators (see "The JSeparator Class" later in this chapter) as well as other visual cues that suggest the logical grouping of mutually exclusive items within the menu.

Although you might expect otherwise, radio button menu items don't enforce mutual exclusion by themselves. Instead, you need to use a ButtonGroup object to limit the user to a single selection. Figure 14-16 shows the class diagram for the JRadioButtonMenuItem component.

Figure 14-16. Radio button menu item class diagram

 

Properties

Table 14-10 shows the properties of the JRadioButtonMenuItem class. Unlike JCheckBoxMenuItem, there is no state property that indicates the current selection state of the menu item. Instead, you typically use this class in conjunction with a ButtonGroup, which contains a getSelected( ) method for extracting the correct object.

Table 14-10: JRadioButtonMenuItem properties

Property

Data type

get

is

set

Default value

accessibleContexto

AccessibleContext

·

 

 

JRadioButtonMenuItem.AccessibleJRadioButtonMenu-Item( )

UIb

RadioButtonMenuItemUI

·

 

·

From L&F

UIClassIDo

String

·

 

 

"RadioButtonMenuItem"

bbound, ooverridden
See also properties from the JMenuItem class (Table 14-4).

Constructor

public JRadioButtonMenuItem( )
public JRadioButtonMenuItem(Action action)
public JRadioButtonMenuItem(Icon icon)
public JRadioButtonMenuItem(String text)
public JRadioButtonMenuItem(String text, Icon icon)
Initialize the JRadioButtonMenuItem with the specified action (since Version 1.3), icon, or string.

Miscellaneous

public void updateUI( )
Force the current UI manager to reset and repaint the delegate for the component, thus updating the component's L&F.

Enforcing Mutual Exclusion

The following program shows how to implement the mutually exclusive nature of radio button menu items:

//  RadioButtonMenuItemExample.java
//
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
 
public class RadioButtonMenuItemExample extends JPanel {
 
    public JTextPane pane;
    public JMenuBar menuBar;
    public JToolBar toolBar;
 
    public RadioButtonMenuItemExample(  ) {
        menuBar = new JMenuBar(  );
        JMenu justifyMenu = new JMenu("Justify");
        ActionListener actionPrinter = new ActionListener(  ) {
            public void actionPerformed(ActionEvent e) {
                try { pane.getStyledDocument(  ).insertString(0 ,
                      "Action ["+e.getActionCommand(  )+"] performed!\n", null);
                } catch (Exception ex) { ex.printStackTrace(  ); }
            }
        };
        JRadioButtonMenuItem leftJustify = new
               JRadioButtonMenuItem("Left", new ImageIcon("left.gif"));
        leftJustify.setHorizontalTextPosition(JMenuItem.RIGHT);
        leftJustify.setAccelerator(KeyStroke.getKeyStroke('L',
                        Toolkit.getDefaultToolkit(  ).getMenuShortcutKeyMask(  )));
        leftJustify.addActionListener(actionPrinter);
        JRadioButtonMenuItem rightJustify = new
               JRadioButtonMenuItem("Right", new ImageIcon("right.gif"));
        rightJustify.setHorizontalTextPosition(JMenuItem.RIGHT);
        rightJustify.setAccelerator(KeyStroke.getKeyStroke('R',
                        Toolkit.getDefaultToolkit(  ).getMenuShortcutKeyMask(  )));
        rightJustify.addActionListener(actionPrinter);
        JRadioButtonMenuItem centerJustify = new
               JRadioButtonMenuItem("Center", new ImageIcon("center.gif"));
        centerJustify.setHorizontalTextPosition(JMenuItem.RIGHT);
        centerJustify.setAccelerator(KeyStroke.getKeyStroke('M',
                        Toolkit.getDefaultToolkit(  ).getMenuShortcutKeyMask(  )));
        centerJustify.addActionListener(actionPrinter);
        JRadioButtonMenuItem fullJustify = new
               JRadioButtonMenuItem("Full", new ImageIcon("full.gif"));
        fullJustify.setHorizontalTextPosition(JMenuItem.RIGHT);
        fullJustify.setAccelerator(KeyStroke.getKeyStroke('F',
                        Toolkit.getDefaultToolkit(  ).getMenuShortcutKeyMask(  )));
        fullJustify.addActionListener(actionPrinter);
 
        ButtonGroup group = new ButtonGroup(  );
        group.add(leftJustify);
        group.add(rightJustify);
        group.add(centerJustify);
        group.add(fullJustify);
 
        justifyMenu.add(leftJustify);
        justifyMenu.add(rightJustify);
        justifyMenu.add(centerJustify);
        justifyMenu.add(fullJustify);
 
        menuBar.add(justifyMenu);
        menuBar.setBorder(new BevelBorder(BevelBorder.RAISED));
    }
 
    public static void main(String s[ ]) {
 
        RadioButtonMenuItemExample example = new
                                        RadioButtonMenuItemExample(  );
        example.pane = new JTextPane(  );
        example.pane.setPreferredSize(new Dimension(250, 250));
        example.pane.setBorder(new BevelBorder(BevelBorder.LOWERED));
 
        JFrame frame = new JFrame("Menu Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setJMenuBar(example.menuBar);
        frame.getContentPane(  ).add(example.pane, BorderLayout.CENTER);
        frame.pack(  );
        frame.setVisible(true);
    }
}

Figure 14-17 shows the result. We use a ButtonGroup object to make our JRadioButtonMenuItems mutually exclusive. Selecting any of the menu items deselects the others. Since text justification is mutually exclusive, this example shows how you would implement a real justification menu.

Figure 14-17. An example of radio button menu items

 

The JSeparator Class

You may have noticed that both JMenu and JPopupMenu contain addSeparator( ) methods to add separators to menus. In doing so, each class instantiates a JSeparator object and positions it in the menu. However, JSeparator exists as a component unto itself outside of menus, and, because it extends JComponent, it can be positioned inside a container like any other Swing component. JSeparator is a simple component that provides separation between logical groups of menu items. In some L&Fs it shows up as a horizontal line drawn across its entire width; in others, it is invisible and just adds a little extra space between elements. It has no model, only a delegate.

Properties

Table 14-11 shows the properties of JSeparator.

Table 14-11: JSeparator properties

Property

Data type

get

is

set

Default value

accessibleContexto

AccessibleContext

·

 

 

JSeparator.accessibleJSeparator( )

orientation

int

·

 

·

SwingConstants.HORIZONTAL

UIb

SeparatorUI

·

 

·

From L&F

UIClassIDo

String

·

 

 

"SeparatorUI"

bbound, ooverridden
See also properties from the JComponent class (Table 3-6).

Constructor

JSeparator( )
JSeparator(int orientation)
Create a separator. By default, this separator is horizontal; if you specify an orientation, it should be either SwingConstants.HORIZONTAL or SwingConstants.VERTICAL.

Miscellaneous

public void updateUI( )
Force the current UI manager to reset and repaint the delegate for the component, thus updating the component's L&F.

Using a Separator Outside of a Menu

We've already seen how a separator can be used in menus to highlight the grouping of menu items. However, separators are components in themselves and can be used for a variety of tasks. Here is a program that adds a separator between a series of buttons:

// SeparatorExample.java
//
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
 
public class SeparatorExample extends JPanel {
 
    public SeparatorExample(  ) {
        super(true);
 
        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
        Box box1 = new Box(BoxLayout.X_AXIS);
        Box box2 = new Box(BoxLayout.X_AXIS);
        Box box3 = new Box(BoxLayout.X_AXIS);
 
        box1.add(new JButton("Press Me"));
        box1.add(new JButton("No Me!"));
        box1.add(new JButton("Ignore Them!"));
        box2.add(new JSeparator(  ));
        box3.add(new JButton("I'm the Button!"));
        box3.add(new JButton("It's me!"));
        box3.add(new JButton("Go Away!"));
 
        add(box1);
        add(box2);
        add(box3);
    }
 
    public static void main(String s[ ]) {
 
        SeparatorExample example = new SeparatorExample(  );
 
        JFrame frame = new JFrame("Separator Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setContentPane(example);
        frame.pack(  );
        frame.setVisible(true);
    }
}

This code yields the interface shown in Figure 14-18. Note that on platforms where separators are invisible, they are difficult or impossible to notice when used in this unorthodox way. (Even in this example, in which the separator has a visual representation, it's pretty hard to see!)

Figure 14-18. A standalone separator between two groups of buttons

 

The MenuElement Interface

As we saw in the previous examples, one nice feature of Swing menus is that we are not constrained to using text for menu items. However, the possibilities don't have to stop with icons, either. In fact, with a little work you can create or extend any Java component to serve as a menu item. There is one catch: your new menu item must implement the MenuElement interface. Swing declares five methods in the MenuElement interface; these methods are called by Swing's internal MenuSelectionManager when various actions take place.

Why is this necessary? Let's look at the traditional menu item, such as the Paste item in the Edit menu. When the user raises the Edit menu, and the mouse passes over the Paste menu item, the menu item typically highlights itself, usually by changing color. This tells the user that releasing (or clicking, depending on the L&F) the mouse button chooses the Paste option. When the mouse leaves the menu item, it returns to its normal color. However, what if we wanted to make the text bold instead of highlighting it? What if we wanted to substitute another icon image in the menu item when the mouse passed over it? By calling the methods of this interface, menus allow menu items to define their own unique behavior.

Methods

public void processMouseEvent(MouseEvent event,MenuElement path[ ], MenuSelectionManager manager)
This method handles events triggered by the mouse. In addition to the MouseEvent, the current path of selected menu elements is provided, as well as a reference to the current menu selection manager. You can take whatever action you feel is necessary with this method.

public void processKeyEvent(KeyEvent event, MenuElement path[ ], MenuSelectionManager manager)
This method handles events triggered by keystrokes. In addition to the KeyEvent, the current path of selected menu elements is provided as well as a reference to the current menu selection manager. You can take whatever action you feel is necessary in this method.

public void menuSelectionChanged(boolean isIncluded)
Called when the menu element is added or removed from the current target menu.

public MenuElement[ ] getSubElements( )
Return an array of subelements for the target MenuElement. This is needed in the event that a particular menu element has a submenu.

public Component getComponent( )
Return a reference to the component responsible for painting the menu item.

Making Arbitrary Components into Menu Elements

It is relatively easy to convert any Swing component into a menu element and drop it in a menu. Here is a program that places a JSlider inside a pop-up menu and uses it as a hidden control for an underlying component.

//  MenuElementExample.java
//
import java.awt.*;
import java.awt.event.*;
 
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
 
public class MenuElementExample extends JPanel {
 
    public JPopupMenu popup;
    SliderMenuItem slider;
    int theValue = 0;
 
    public MenuElementExample(  ) {
 
        popup = new JPopupMenu(  ); 
        slider = new SliderMenuItem(  );
 
        popup.add(slider);
        popup.add(new JSeparator(  ));
 
        JMenuItem ticks = new JCheckBoxMenuItem("Slider Tick Marks");
        ticks.addActionListener(new ActionListener(  ) {
            public void actionPerformed(ActionEvent event) {
                slider.setPaintTicks(!slider.getPaintTicks(  ));
            }
        });
        JMenuItem labels = new JCheckBoxMenuItem("Slider Labels");
        labels.addActionListener(new ActionListener(  ) {
            public void actionPerformed(ActionEvent event) {
                slider.setPaintLabels(!slider.getPaintLabels(  ));
            }
        });
        popup.add(ticks);
        popup.add(labels);
        popup.addPopupMenuListener(new PopupPrintListener(  ));
 
        addMouseListener(new MousePopupListener(  ));
    }
 
    // Inner class to check whether mouse events are the pop-up trigger
    class MousePopupListener extends MouseAdapter {
        public void mousePressed(MouseEvent e) { checkPopup(e); }
        public void mouseClicked(MouseEvent e) { checkPopup(e); }
        public void mouseReleased(MouseEvent e) { checkPopup(e); }
 
        private void checkPopup(MouseEvent e) {
            if (e.isPopupTrigger(  )) {
                popup.show(MenuElementExample.this, e.getX(  ), e.getY(  ));
            }
        }
    }
 
    // Inner class to print information in response to pop-up events
    class PopupPrintListener implements PopupMenuListener {
        public void popupMenuWillBecomeVisible(PopupMenuEvent e) { }
 
        public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {
            theValue = slider.getValue(  );
            System.out.println("The value is now " + theValue);
        }
 
        public void popupMenuCanceled(PopupMenuEvent e) {
            System.out.println("Popup menu is hidden!");
        }
    }
  
    public static void main(String s[ ]) {
        JFrame frame = new JFrame("Menu Element Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setContentPane(new MenuElementExample(  ));
        frame.setSize(300, 300);
        frame.setVisible(true);
    }
 
    // Inner class that defines our special slider menu item
    class SliderMenuItem extends JSlider implements MenuElement {
 
        public SliderMenuItem(  ) {
            setBorder(new CompoundBorder(new TitledBorder("Control"),
                                  new EmptyBorder(10, 10, 10, 10)));
 
            setMajorTickSpacing(20); 
            setMinorTickSpacing(10); 
        }
 
        public void processMouseEvent(MouseEvent e, MenuElement path[ ],
                                      MenuSelectionManager manager) {}
 
        public void processKeyEvent(KeyEvent e, MenuElement path[ ],
                                    MenuSelectionManager manager) {}
 
        public void menuSelectionChanged(boolean isIncluded) {}
  
        public MenuElement[ ] getSubElements(  ) {return new MenuElement[0];}
 
        public Component getComponent(  ) {return this;}
    }
}

As with our previous pop-up example, PopupMenuExample, we implement MouseListener and check incoming mouse events to see whether to show the pop up. The inner class SliderMenuItem implements the MenuElement interface, and is the focus of our example. In this case, it's fairly easy. Our menu slider never has subelements, doesn't have a concept of a selection, and doesn't need to do anything special with mouse or key events.

The interface resulting from our example is shown in Figure 14-19. We provide a JSlider object, a separator, and two JCheckBoxMenuItem objects, which control the state of the slider. The slider is also surrounded by a titled border. When the user adjusts the slider and dismisses the pop up, we print the current value of the slider to the standard output. With a little bit of imagination, you can do just about anything with a pop-up menu. Of course, if it's something unexpected, you should carefully consider whether it is likely to confuse or confound your users.

Figure 14-19. A JSlider masquerading as a pop-up menu element in two L&Fs

 

Toolbars

Toolbars are another approach to providing access to commonly used application features. They are more likely than menus to use graphical representations of commands. Because they remain on-screen at all times (unlike menus, which drop down only when activated) they can provide a useful "dashboard" for indicating the current state of the application. On the other hand, they take up more room than menu bars, so it's good to let the user decide whether they should be visible at all.

Toolbars have the ability to "tear" themselves from their location within a frame and embed their components in a moveable standalone window. This gives the user the freedom to drag the toolbar anywhere on the screen. In addition, toolbars can "dock" in locations where the layout manager can support them.

The JToolBar Class

Like the menu bar, the JToolBar class is a container for various components. You can add any component to the toolbar, including buttons, combo boxes, and even additional menus. Like menus, the toolbar is easiest to work with when paired with Action objects.

When a component is added to the toolbar, it is assigned an integer index that determines its display order from left to right. While there is no restriction on the type of component that can be added, the toolbar generally looks best if it uses components that are the same vertical height. Note that toolbars have a default border installed by the L&F. If you don't like the default, you can override the border with one of your own using the setBorder( ) method. Alternatively, you can deactivate the drawing of the border by setting the borderPainted property to false.

JToolBar has its own separator that inserts a blank space on the toolbar; you can use the addSeparator( ) method to access this separator. Separators are useful if you want to add space between groups of related toolbar components. The separator for toolbars is actually an inner class. Be sure not to confuse this separator with the JSeparator class.

Figure 14-20 shows the class diagram for the JToolBar component.

Figure 14-20. JToolBar class diagram

 

Floating toolbars

Although toolbars can be easily positioned in Swing containers, they do not have to stay there. Instead, you can "float" the toolbar by holding the mouse button down while the cursor is over an empty section of the toolbar (that is, not over any of its components) and dragging. This places the toolbar in a moveable child window; you can position it anywhere in the viewing area. Toolbars can then reattach themselves to specific locations, or hotspots, within the frame. Letting go of the toolbar while dragging it over a hotspot anchors the toolbar back into the container. Figure 14-21 is an example of a floating toolbar.

Figure 14-21. A floating toolbar

 

It is best to place a toolbar in a container that supports the BorderLayout. If you intend to make the toolbar floatable, place it along either the north, south, east, or west side of the container, and leave the remaining sides open. This allows the toolbar to define anchor spots when it is being dragged and ensures that the resulting layout is not ambiguous.

If you want to disable floating, you can reset the floatable property to false:

JToolBar toolBar = new JToolBar(  );
toolBar.setFloatable(false);

Properties

The properties of the JToolBar class are shown in Table 14-12. The borderPainted property defines whether the toolbar should paint its border. The JToolBar constructor resets the layout manager of the component to a BoxLayout along the x axis (this becomes a y-axis BoxLayout if the orientation is VERTICAL), which it uses to place any child components. The margin property defines the insets that appear between the toolbar's edges and its components. The floatable property defines whether the toolbar can be separated from the container and "floated" in a standalone window. You can use the indexed componentAtIndex property to access any of the components on the toolbar. The orientation property determines whether the toolbar is horizontal or vertical. Its value must be either HORIZONTAL or VERTICAL (constants defined in SwingConstants). Attempting to set orientation to some other value throws an IllegalArgumentException. Finally, starting with SDK 1.4, rollover can be set to cause the toolbar's button borders to be drawn only when the mouse is hovering over them, a style of interface popularized by dynamic web sites. Not all L&Fs honor a true value for rollover.

Table 14-12: JToolBar properties

Property

Data type

get

is

set

Default value

accessibleContext

AccessibleContext

·

 

 

JToolBar.AccessibleJToolBar( )

borderPaintedb

boolean

 

·

·

true

componentAtIndexi

Component

·

 

 

 

floatableb

boolean

 

·

·

true

layouto

LayoutManager

·

 

·

BoxLayout(X_AXIS)

marginb

Insets

·

 

·

Insets(0,0,0,0)

orientationb

int

·

 

·

SwingConstants.HORIZONTAL

rollover1.4, b

boolean

 

·

·

false

UIb

ToolBarUI

·

 

·

From L&F

UIClassIDo

String

·

 

 

"ToolBarUI"

1.4since 1.4, bbound, iindexed, ooverridden
See also properties from the JComponent class (Table 3-6).

Event

JToolbar generates a PropertyChangeEvent when any of its bound properties are changed.

Constructor

public JToolBar( )
public JToolBar(int orientation)
public JToolBar(String name)
public JToolBar(String name, int orientation)
Create a JToolBar, optionally supplying a name that appears as a title when the toolbar is floating (the ability to assign a name started with SDK 1.3). The orientation is horizontal by default; if you specify an orientation, it must be SwingConstants.HORIZONTAL or SwingConstants.VERTICAL.

Adding actions

public JButton add(Action a)
Add an Action to the toolbar. The method creates a simple JButton with the text of the action placed below its image.[1] It then returns the JButton, allowing you to reset any of the button's attributes. (As you'd expect, the method for adding a component to a toolbar is inherited from Container.)

Miscellaneous

public void updateUI( )
Force the current UIManager to repaint the UI delegate for the component, updating the component's L&F.

public int getComponentIndex(Component c)
Return the integer index of the component c, or -1 if it's not found. Note that any separators in the toolbar take up index positions.

public void addSeparator( )
public void addSeparator(Dimension size)
Add a separator to the toolbar. Be sure not to confuse the toolbar separator with JSeparator, which is a separate Swing component. The toolbar separator created by this method is simply a blank area of space used to provide spacing between groups of toolbar components. The size is normally up to the toolbar, though you can specify the separator's size explicitly if you wish.

Creating a Toolbar

The following example adds a toolbar to the JMenu example and provides a glimpse of the true power of using Action objects for building a user interface.

To add some interesting complexity, we also allow the user to choose a font from a combo box, showing that you can use other kinds of components in a toolbar. Note that we add the combo box and a JLabel for it as separate components and that the combo box uses its own actionPerformed( ) method.

//  ToolBarExample.java
//
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
 
public class ToolBarExample extends JPanel {
 
    public JTextPane pane;
    public JMenuBar menuBar;
    public JToolBar toolBar;
    String fonts[ ] = {"Serif","SansSerif","Monospaced","Dialog","DialogInput"};
 
    public ToolBarExample(  ) {
        menuBar = new JMenuBar(  );
 
        // Create a set of actions to use in both the menu and toolbar.
        DemoAction leftJustifyAction = new DemoAction("Left",
            new ImageIcon("left.gif"), "Left justify text", 'L');
        DemoAction rightJustifyAction = new DemoAction("Right",
            new ImageIcon("right.gif"), "Right justify text", 'R');
        DemoAction centerJustifyAction = new DemoAction("Center",
            new ImageIcon("center.gif"), "Center justify text", 'M');
        DemoAction fullJustifyAction = new DemoAction("Full",
            new ImageIcon("full.gif"), "Full justify text", 'F');
 
       JMenu formatMenu = new JMenu("Justify");
       formatMenu.add(leftJustifyAction);
       formatMenu.add(rightJustifyAction);
       formatMenu.add(centerJustifyAction);
       formatMenu.add(fullJustifyAction);
       menuBar.add(formatMenu);
 
       toolBar = new JToolBar("Formatting");
       toolBar.add(leftJustifyAction);
       toolBar.add(rightJustifyAction);
       toolBar.add(centerJustifyAction);
       toolBar.add(fullJustifyAction);
 
       toolBar.addSeparator(  );
       JLabel label = new JLabel("Font");
       toolBar.add(label);
 
       toolBar.addSeparator(  );
       JComboBox combo = new JComboBox(fonts);
       combo.addActionListener(new ActionListener(  ) {
           public void actionPerformed(ActionEvent e) {
               try { pane.getStyledDocument(  ).insertString(0,
                   "Font [" + ((JComboBox)e.getSource(  )).getSelectedItem(  ) +
                   "] chosen!\n", null);
               } catch (Exception ex) { ex.printStackTrace(  ); }
           }
       });
       toolBar.add(combo);
  
       // Disable one of the Actions.
       fullJustifyAction.setEnabled(false);
    }
 
    public static void main(String s[ ]) {
 
        ToolBarExample example = new ToolBarExample(  );
        example.pane = new JTextPane(  );
        example.pane.setPreferredSize(new Dimension(250, 250));
        example.pane.setBorder(new BevelBorder(BevelBorder.LOWERED));
        example.toolBar.setMaximumSize(example.toolBar.getSize(  ));
 
        JFrame frame = new JFrame("Menu Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setJMenuBar(example.menuBar);
        frame.getContentPane(  ).add(example.toolBar, BorderLayout.NORTH);
        frame.getContentPane(  ).add(example.pane, BorderLayout.CENTER);
        frame.pack(  );
        frame.setVisible(true);
    }
 
    class DemoAction extends AbstractAction {
   
        public DemoAction(String text, Icon icon, String description, 
                          char accelerator) {
            super(text, icon);
            putValue(ACCELERATOR_KEY, KeyStroke.getKeyStroke(accelerator,
                     Toolkit.getDefaultToolkit(  ).getMenuShortcutKeyMask(  )));
            putValue(SHORT_DESCRIPTION, description);
        }
 
        public void actionPerformed(ActionEvent e) {
            try { pane.getStyledDocument(  ).insertString(0,
                  "Action [" + getValue(NAME) + "] performed!\n", null);
            } catch (Exception ex) { ex.printStackTrace(  ); } 
        }
    }
}

Note the efficiency we've achieved: by creating a single set of Actions to represent our justification modes, we can create a corresponding menu entry or toolbar button in a single line of code. Each has the appropriate label and/or icon, accelerator key (for menus), and tooltip. Try holding your mouse over the buttons to see the tooltip. Also notice that by disabling the Full justify action we automatically disabled both the corresponding menu item and toolbar button. When an action is disabled, all the associated components are notified of the property change. In our program, both the menu item and the toolbar button for full justification are grayed, as shown in Figure 14-22.

Figure 14-22. Disabling actions automatically grays the toolbar and menu representations

 

To see the toolbar float, click on the textured area at the left edge and drag it outside the window. You can also drag it to any edge of the frame to anchor it at that position.

A JToolBar is a regular Swing component, so you can use more than one in an application. If you do so, and you wish to make the toolbars floatable, it is best to place each toolbar in a concentric BorderLayout container, leaving the other three sides unpopulated. This ensures that the toolbars maintain their respective positions if they are both dragged to a new side.


1. In SDK 1.3, Sun discouraged the use of this method without actually deprecating it. People have pointed out that there is no adequate replacement, and this has been acknowledged. So use it, but keep your eye out for news.

Back to: Java Swing, 2nd Edition


oreilly.com Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
International | About O'Reilly | Affiliated Companies | Privacy Policy

© 2001, O'Reilly & Associates, Inc.
webmaster@oreilly.com