FOCUSED control alt 7");
JButton buttonB =
new JButton("
FOCUS/RELEASE VK_ENTER");
JButton buttonC =
new JButton("
ANCESTOR VK_F4+SHIFT_MASK");
JButton buttonD =
new JButton("
WINDOW ' '");
// Define ActionListener
Action actionListener = new AbstractAction() {
public void actionPerformed(ActionEvent actionEvent) {
JButton source = (JButton)actionEvent.getSource();
System.out.println("Activated: " + source.getText());
}
};
KeyStroke controlAlt7 = KeyStroke.getKeyStroke("control alt 7");
InputMap inputMap = buttonA.getInputMap();
inputMap.put(controlAlt7, ACTION_KEY);
ActionMap actionMap = buttonA.getActionMap();
actionMap.put(ACTION_KEY, actionListener);
KeyStroke enter = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true);
inputMap = buttonB.getInputMap();
inputMap.put(enter, ACTION_KEY);
buttonB.setActionMap(actionMap);
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
KeyStroke shiftF4 =
KeyStroke.getKeyStroke(KeyEvent.VK_F4, InputEvent.SHIFT_MASK);
inputMap =
buttonC.getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
inputMap.put(shiftF4, ACTION_KEY);
buttonC.setActionMap(actionMap);
KeyStroke space = KeyStroke.getKeyStroke(' ');
inputMap = buttonD.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
inputMap.put(space, ACTION_KEY);
buttonD.setActionMap(actionMap);
frame.setLayout(new GridLayout(2,2));
frame.add(buttonA);
frame.add(buttonB);
frame.add(buttonC);
frame.add(buttonD);
frame.setSize(400, 200);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
■Tip For text components, you can get the Keymap and bind an Action to a KeyStroke in one step with
addActionForKeyStroke(KeyStroke, Action).
Figure 2-7 shows what the running program looks like.
Figure 2-7. KeyStroke listening example
45
46
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
Using Mnemonics and Accelerators
The Swing libraries also use KeyStroke objects for several internal functions. Two such functions
are component mnemonics and accelerators, which work as follows:
• In a component mnemonic, one character in a label appears underlined. When that
character is pressed along with a platform-specific hotkey combination, the component
is activated. For instance, pressing Alt-A in the window shown in Figure 2-8 would select
the About button on a Windows XP platform.
• A menu accelerator activates a menu item when it is not visible. For instance, pressing
Ctrl-P would select the Print menu item in the window shown in Figure 2-8 when the File
menu isn’t visible.
Figure 2-8. Mnemonics and menu shortcuts
You’ll learn more about mnemonics and accelerators in Chapter 6.
Swing Focus Management
The term focus refers to when a component acquires the input focus. When a component has
the input focus, it serves as the source for all key events, such as text input. In addition, certain
components have some visual markings to indicate that they have the input focus, as shown in
Figure 2-9. When certain components have the input focus, you can trigger selection with a
keyboard key (usually the spacebar or Enter key), in addition to selection with a mouse. For
instance, with a button, pressing the spacebar activates it.
Figure 2-9. A JButton showing it has input focus
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
■Note The focus subsystem had a major overhaul with the 1.4 release of J2SE. All the older guts are still
present, but should be avoided. The older stuff didn’t work well and was very buggy. Sun’s fix was to essentially throw everything away and start over, but old APIs are still present. In your quest to work with the focus
subsystem, learn to use only the updated APIs, not the older ones. Classes like javax.swing.FocusManager
and javax.swing.DefaultFocusManager are completely obsolete now.
An important concept in focus management is the focus cycle, which maps the focus traversal
order for the closed set of components within a specific Container. The following classes are
also major players in focus management:
• FocusTraversalPolicy: A java.awt class that defines the algorithm used to determine the
next and previous focusable components.
• KeyboardFocusManager: A java.awt class that acts as the controller for keyboard navigation
and focus changes. To request a focus change, you tell the manager to change focusable
components; you don’t request focus on a particular component.
You can find out when the Swing component gets the input focus by registering a
FocusListener. The listener allows you to find out when a component gains or loses focus,
which component lost focus when another component gained it, and which component got
focus when another component lost focus. Additionally, a temporary focus change can happen
for something like a pop-up menu. The component that lost focus will receive it again when
the menu goes down.
The installed focus traversal policy describes how to move between the focusable components of a window. By default, the next component is defined by the order in which components
are added to a container, as shown in Figure 2-10. For Swing applications, this focus traversal
starts at the top left of the figure and goes across each row and down to the bottom right. This is the
default policy, LayoutFocusTraversalPolicy. When all the components are in the same container,
this traversal order is called a focus cycle and can be limited to remain within that container.
■Note A user can press Tab or Shift-Tab to move forward or backward through the components in a
container, thus transferring the input focus.
47
48
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
Figure 2-10. Default focus ordering
Moving the Focus
As an example of some basic capabilities, let’s look at how to create two listeners to handle
input focus: a MouseListener that moves the input focus to a component when the mouse
enters its space, and an ActionListener that transfers the input focus to the next component.
The MouseListener merely needs to call requestFocusInWindow() when the mouse enters
the component.
import java.awt.*;
import java.awt.event.*;
public class MouseEnterFocusMover extends MouseAdapter {
public void mouseEntered(MouseEvent mouseEvent) {
Component component = mouseEvent.getComponent();
if (!component.hasFocus()) {
component.requestFocusInWindow();
}
}
}
For the ActionListener, you need to call the focusNextComponent() method for the
KeyboardFocusManager.
import java.awt.*;
import java.awt.event.*;
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
public class ActionFocusMover implements ActionListener {
public void actionPerformed(ActionEvent actionEvent) {
KeyboardFocusManager manager =
KeyboardFocusManager.getCurrentKeyboardFocusManager();
manager.focusNextComponent();
}
}
The ActionFocusMover and MouseEnterFocusMover show two different ways of programmatically moving focus around. The ActionFocusMover uses the KeyboardFocusManager for traversal.
In MouseEnterFocusMover, the call to requestFocusInWindow() says that you would like for the
suggested component to get focus for the window of the application. However, getting focus
can be turned off. If the component isn’t focusable, either because the default setting of the
focusable property is false or you called component.setFocusable(false), then the component
will be skipped over and the next component after it gets focus; the component is removed
from the tab focus cycle. (Think of a scrollbar that isn’t in the focus cycle, but is draggable to
change a setting.)
The program in Listing 2-11 uses the two event handlers for moving focus around. It creates a
3×3 grid of buttons, in which each button has an attached mouse listener and a focus listener.
The even buttons are selectable but aren’t focusable.
Listing 2-11. Focus Traversal Sample
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class FocusSample {
public static void main(String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("Focus Sample");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ActionListener actionListener = new ActionFocusMover();
MouseListener mouseListener = new MouseEnterFocusMover();
frame.setLayout(new GridLayout(3,3));
for (int i=1; i<10; i++) {
JButton button = new JButton(Integer.toString(i));
button.addActionListener(actionListener);
button.addMouseListener(mouseListener);
if ((i%2) != 0) { // odd - enabled by default
button.setFocusable(false);
}
frame.add(button);
}
49
50
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
frame.setSize(300, 200);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
Figure 2-11 shows the main window of the program.
Figure 2-11. Focus management example
Examining Focus Cycles
One customization option available at the Swing container level is the focus cycle. Remember
that the focus cycle for a container is a map of the focus traversal order for the closed set of
components. You can limit the focus cycle to stay within the bounds of a container by setting
the focusCycleRoot property to be true, thus restricting the focus traversal from going beyond
an inner container. Then, when the Tab key is pressed within the last component of the container,
the focus cycle will wrap back to the first component in the container, instead of moving the
input focus to the first component outside the container. When Shift-Tab is pressed in the first
component, it wraps to the last component of the container, instead of to the prior component
in the outer container.
Figure 2-12 illustrates how the focus ordering would look if you placed the middle three
buttons from Figure 2-10 within a container restricted in this way. In this cycle, you cannot get
to the first component on the third row by pressing the Tab key to move forward. To be able to
tab into the second row container, you need to set the focusTraversalPolicyProvider property
to true. Otherwise, while the panel will keep the traversal policy within the second row, tabbing will
never get you into the third row.
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
Figure 2-12. Restrictive focus cycle
The program in Listing 2-12 demonstrates the behavior illustrated in Figure 2-12. The
on-screen program will look just like Figure 2-11; it just behaves differently.
Listing 2-12. Restricting the Focus Cycle
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class FocusCycleSample {
public static void main(String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("Focus Cycle Sample");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridBagLayout());
GridBagConstraints constraints = new GridBagConstraints();
constraints.weightx
= 1.0;
constraints.weighty
= 1.0;
constraints.gridwidth = 1;
constraints.gridheight = 1;
constraints.fill
= GridBagConstraints.BOTH;
51
52
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
// Row One
constraints.gridy=0;
for (int i=0; i<3; i++) {
JButton button = new JButton("" + i);
constraints.gridx=i;
frame.add(button, constraints);
}
// Row Two
JPanel panel = new JPanel();
panel.setFocusCycleRoot(true);
panel.setFocusTraversalPolicyProvider(true);
panel.setLayout(new GridLayout(1,3));
for (int i=0; i<3; i++) {
JButton button = new JButton("" + (i+3));
panel.add(button);
}
constraints.gridx=0;
constraints.gridy=1;
constraints.gridwidth=3;
frame.add(panel, constraints);
// Row Three
constraints.gridy=2;
constraints.gridwidth=1;
for (int i=0; i<3; i++) {
JButton button = new JButton("" + (i+6));
constraints.gridx=i;
frame.add(button, constraints);
}
frame.setSize(300, 200);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
FocusTraversalPolicy Class
The FocusTraversalPolicy is responsible for determining the focus traversal order. It allows
you to specify the next and previous components in the order. This class offers six methods for
controlling traversal order:
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
• getComponentAfter(Container aContainer, Component aComponent)
• getComponentBefore(Container aContainer, Component aComponent)
• getDefaultComponent(Container aContainer)
• getFirstComponent(Container aContainer)
• getInitialComponent(Window window)
• getLastComponent(Container aContainer)
Swing provides five predefined traversal policies, as listed in Table 2-5. By picking the right
traversal policy for your application, or rolling your own, you can determine how users will
navigate around the screens.
Table 2-5. Predefined Traversal Policies
Policy
Description
ContainerOrderFocusTraversalPolicy
The components are traversed in the order they are
added to their container. The component must be
visible, displayable, enabled, and focusable to be
part of the focus cycle.
DefaultFocusTraversalPolicy
The default policy for AWT programs, this extends
ContainerOrderFocusTraversalPolicy to check
with the component peer (the operating system) if
the component hasn’t explicitly set focusability.
The focusability of a peer depends on the Java
runtime implementation.
InternalFrameFocusTraversalPolicy
Special policy for JInternalFrame, with behavior to
determine initial focusable component based on
the default component of the frame.
SortingFocusTraversalPolicy
Here, you provide a Comparator to the policy
constructor to define the focus cycle order.
LayoutFocusTraversalPolicy
The default policy for Swing programs, this takes
into account geometric settings of components
(height, width, position), and then goes top down,
left to right to determine navigation order. The topdown, left-right order is determined by the current
ComponentOrientation setting for your locale. For
instance, Hebrew would be in right-left order instead.
To demonstrate, the program in Listing 2-13 reverses the functionality of Tab and ShiftTab. When you run the program, it looks the same as the screen shown earlier in Figure 2-11,
with the 3×3 set of buttons. However, with this version, the initial focus starts on the 9 button,
and pressing Tab takes you to 8, then 7, and so on. Shift-Tab goes in the other, more normal,
order.
53
54
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
Listing 2-13. Reversing Focus Traversal
import
import
import
import
import
import
javax.swing.*;
java.awt.*;
java.awt.event.*;
java.util.Comparator;
java.util.Arrays;
java.util.List;
public class NextComponentSample {
public static void main(String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("Reverse Sample");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridLayout(3,3));
// for (int i=1; i<10; i++) {
for (int i=9; i>0; i--) {
JButton button = new JButton(Integer.toString(i));
frame.add(button, 0);
}
final Container contentPane = frame.getContentPane();
Comparator comp = new Comparator() {
public int compare(Component c1, Component c2) {
Component comps[] = contentPane.getComponents();
List list = Arrays.asList(comps);
int first = list.indexOf(c1);
int second = list.indexOf(c2);
return second - first;
}
};
FocusTraversalPolicy policy = new SortingFocusTraversalPolicy(comp);
frame.setFocusTraversalPolicy(policy);
frame.setSize(300, 200);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
KeyboardFocusManager Class
The abstract KeyboardFocusManager class in the AWT library serves as the control mechanism
framework for the input focus behavior of Swing components. The DefaultKeyboardFocusManager
is the concrete implementation. The focus manager allows you to both programmatically
discover who currently has the input focus and to change it.
The component with the current input focus is called the focus owner. This is accessible
via the focusOwner property of KeyboardFocusManager. You can also discover the focusedWindow
and activeWindow properties. The focused window is the window containing the focus owner, and
the active window is either the focused window or the frame or dialog containing the focus owner.
The simple concept of moving to the previous or next component is supported in many
different ways. First, you can use the shortcut API methods of Component and Container:
• Component.transferFocus()
• Component.transferFocusBackward()
• Component.transferFocusUpCycle()
• Container.transferFocusDownCycle()
The first two methods request focus to move to the next or previous component, respectively. The up and down cycle methods request that you move up out of the current focus cycle
or down into the next cycle.
The following methods map directly to methods of the KeyboardFocusManager:
• focusNextComponent()
• focusPreviousComponent()
• upFocusCycle()
• downFocusCycle()
A second set of the same four methods accepts a second parameter of a Component. If the
component isn’t specified, these methods change the focused component based on the current
focus owner. If a component is provided, the change is based on that component.
Tab and Shift-Tab are used for keyboard focus traversal because they are defined as the
default focus traversal keys for most, if not all, components. To define your own traversal keys,
you can replace or append to a key set via the setFocusTraversalKeys() method of Component.
Different sets are available for forward, backward, and up-cycle, as specified by the FORWARD_
TRAVERSAL_KEYS, BACKWARD_TRAVERSAL_KEYS, and UP_CYCLE_TRAVERSAL_KEYS constants of
KeyboardFocusManager. You can set and get key sets for each. For instance, to add the F3 key
as an up-cycle key for a component, use the following code:
Set set = component.getFocusTraversalKeys(
KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS);
KeyStroke stroke = KeyStroket.getKeyStroke("F3");
set.add(stroke);
component.setFocusTraversalKeys(KeyboardFocusManager.UP_CYCLE_TRAVERSAL_KEYS, set);
55
56
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
Verifying Input During Focus Traversal
Swing offers the abstract InputVerifier class for component-level verification during focus
traversal with any JComponent. Just subclass InputVerifier and provide your own public
boolean verify(JComponent) method to verify the contents of the component.
Listing 2-14 provides a simple numeric text field verification example, showing three text
fields, of which only two have verification. Unless fields one and three are valid, you can’t tab
out of them.
Listing 2-14. Numeric Input Verifier
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class VerifierSample {
public static void main(String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("Verifier Sample");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JTextField textField1 = new JTextField();
JTextField textField2 = new JTextField();
JTextField textField3 = new JTextField();
InputVerifier verifier = new InputVerifier() {
public boolean verify(JComponent comp) {
boolean returnValue;
JTextField textField = (JTextField)comp;
try {
Integer.parseInt(textField.getText());
returnValue = true;
} catch (NumberFormatException e) {
returnValue = false;
}
return returnValue;
}
};
textField1.setInputVerifier(verifier);
textField3.setInputVerifier(verifier);
frame.add(textField1, BorderLayout.NORTH);
frame.add(textField2, BorderLayout.CENTER);
frame.add(textField3, BorderLayout.SOUTH);
frame.setSize(300, 100);
frame.setVisible(true);
}
CHAPTER 2 ■ EVENT HANDLING WITH THE SWING COMPONENT SET
};
EventQueue.invokeLater(runner);
}
}
■Tip To make sure that cancel-type buttons get the input focus no matter what when using InputVerifier,
use the setVerifyInputWhenFocusTarget(false) method with the component.
Summary
In this chapter, you looked at the many ways of dealing with event handling when using Swing
components. Because Swing components are built on top of AWT components, you can use the
delegation-based event-handling mechanism common with those components. You then learned
about the multithreading limitations of the Swing components and how to get around them
with the invokeAndWait() and invokeLater() methods of EventQueue. You also explored how
the Swing components use the JavaBeans PropertyChangeListener approach for notification of
bound property changes.
Besides exploring the similarities between the Swing components and AWT components,
you also looked at several of the new features that the Swing library offers. You explored the
Action interface and how it can simplify complex user-interface development by completely
separating the event-handling task from the visual component. You looked at the technique for
registering KeyStroke objects to components to simplify listening for key events. Finally, you
explored Swing’s focus management capabilities and how to customize the focus cycle and use
the FocusTraversalPolicy and KeyboardFocusManager, as well as validating input with the
InputVerifier.
In Chapter 3, you’ll meet the Model-View-Controller (MVC) architecture of the Swing
component set. You’ll learn how MVC can make your user interface development efforts
much easier.
57
CHAPTER 3
■■■
The Model-View-Controller
Architecture
C
hapter 2 explored how to deal with event producers and consumers with regard to Swing
components. We looked at how event handling with Swing components goes beyond the
event-handling capabilities of the original AWT components. In this chapter, we will take the
Swing component design one step further to examine what is called the Model-View-Controller
(MVC) architecture.
Understanding the Flow of MVC
First introduced in Smalltalk in the late 1980s, the MVC architecture is a special form of the
Observer pattern described in Chapter 2. The model part of the MVC holds the state of a
component and serves as the Subject. The view part of the MVC serves as the Observer of the
Subject to display the model’s state. The view creates the controller, which defines how the
user interface reacts to user input.
MVC Communication
Figure 3-1 shows how the MVC elements communicate—in this case, with Swing’s multiline
text component, the JTextArea. In MVC terms, the JTextArea serves as the view part within the
MVC architecture. Displayed within the component is a Document, which is the model for the
JTextArea. The Document stores the state information for the JTextArea, such as the text contents.
Within the JTextArea is the controller, in the form of an InputMap. It maps keyboard input to
commands in an ActionMap, and those commands are mapped to TextAction objects, which
can modify the Document. When the modification happens, the Document creates a
DocumentEvent and sends it back to the JTextArea.
59
60
CHAPTER 3 ■ THE MODEL-VIEW-CONTROLLER ARCHITECTURE
Figure 3-1. MVC communication mechanism
UI Delegates for Swing Components
This example demonstrates an important aspect of the MVC architecture within the Swing
world. Complex interactions need to happen between the view and the controller. The Swing
design combines these two elements into a delegate object to simplify the overall design. This
results in each Swing component having a UI delegate that is in charge of rendering the current
state of the component and dealing with user input events.
Sometimes, the user events result in changes to the view that don’t affect the model. For
instance, the cursor position is an attribute of the view. The model doesn’t care about the position of the cursor, only the text contents. User input that affects the cursor position isn’t passed
along to the model. At other times, user input that affects the contents of the Document (for
example, pressing the Backspace key) is passed along. Pressing the Backspace key results in a
character being removed from the model. Because of this tight coupling, each Swing component
has a UI delegate.
To demonstrate, Figure 3-2 shows the makeup of the JTextArea, with respect to the model
and UI delegate. The UI delegate for the JTextArea starts with the TextUI interface, with
its basic implementation in the BasicTextUI class. In turn, this is specialized with the
BasicTextAreaUI for the JTextArea. The BasicTextAreaUI creates a view that is either a
PlainView or a WrappedPlainView. On the model side, things are much simpler. The Document
interface is implemented by the AbstractDocument class, which is further specialized by the
PlainDocument.
The text components will be explained more fully in Chapters 15 and 16. As the diagram in
Figure 3-2 demonstrates, much is involved in working with the text components. In most cases,
you don’t need to deal with the specifics to the degree shown in this figure. However, all of
these classes are working behind the scenes. The UI-delegate part of the MVC architecture will
be discussed further in Chapter 20, when we explore how to customize delegates.
CHAPTER 3 ■ THE MODEL-VIEW-CONTROLLER ARCHITECTURE
Figure 3-2. The JTextArea MVC architecture
Sharing Data Models
Because data models store only the state information, you can share a model across multiple
components. Then each component view can be used to modify the model.
In the case of Figure 3-3, three different JTextArea components are used to modify one
Document model. If a user modifies the contents of one JTextArea, the model is changed,
causing the other text areas to automatically reflect the updated document state. It isn’t necessary
for any Document view to manually notify others sharing the model.
61
62
CHAPTER 3 ■ THE MODEL-VIEW-CONTROLLER ARCHITECTURE
Figure 3-3. Sharing MVC data models
Sharing of a data model can be done in either one of two ways:
• You can create the data model apart from any component and tell each component to
use the data model.
• You can create one component first, get the model from the first component, and then
share it with the other components.
Listing 3-1 demonstrates how to share a data model using the latter technique.
Listing 3-1. Sharing an MVC Model
import java.awt.*;
import javax.swing.*;
import javax.swing.text.*;
public class ShareModel {
public static void main (String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("Sharing Sample");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container content = frame.getContentPane();
JTextArea textarea1 = new JTextArea();
Document document = textarea1.getDocument();
JTextArea textarea2 = new JTextArea(document);
JTextArea textarea3 = new JTextArea(document);
content.setLayout(new BoxLayout(content, BoxLayout.Y_AXIS));
content.add(new JScrollPane(textarea1));
CHAPTER 3 ■ THE MODEL-VIEW-CONTROLLER ARCHITECTURE
content.add(new JScrollPane(textarea2));
content.add(new JScrollPane(textarea3));
frame.setSize (300, 400);
frame.setVisible (true);
}
};
EventQueue.invokeLater(runner);
}
}
Figure 3-4 shows how this program might look after editing the shared document. Notice
that the three text areas are capable of viewing (or modifying) different areas of the document.
They aren’t limited to adding text only at the end, for instance. This is because each text area
manages the position and cursor separately. The position and cursor are attributes of the view,
not the model.
Figure 3-4. Sharing a document between JTextArea components
Understanding the Predefined Data Models
When working with Swing components, it’s helpful to understand the data models behind
each of the components because the data models store their state. Understanding the data
model for each component helps you to separate the parts of the component that are visual
(and thus part of the view) from those that are logical (and thus part of the data model). For
example, by understanding this separation, you can see why the cursor position within a
JTextArea isn’t part of the data model, but rather is part of the view.
Table 3-1 provides a complete listing of the Swing components, the interface that describes
the data model for each component, as well as the specific implementations. If a component
isn’t listed, that component inherits its data model from its parent class, most likely
63
64
CHAPTER 3 ■ THE MODEL-VIEW-CONTROLLER ARCHITECTURE
AbstractButton. In addition, in some cases, multiple interfaces are used to describe a component, because the data is stored in one model and the selection of the data is in a second model.
In the case of the JComboBox, the MutableComboBoxModel interface extends from ComboBoxModel.
No predefined class implements the ComboBoxModel interface without also implementing the
MutableComboBoxModel interface.
Table 3-1. Swing Component Models
Component
Data Model Interface
Implementations
AbstractButton
ButtonModel
DefaultButtonModel
JColorChooser
ColorSelectionModel
DefaultColorSelectionModel
JComboBox
ComboBoxModel
N/A
MutableComboBoxModel
DefaultComboBoxModel
JFileChooser
ListModel
BasicDirectoryModel
JList
ListModel
AbstractListModel
DefaultListModel
ListSelectionModel
DefaultListSelectionModel
JMenuBar
SingleSelectionModel
DefaultSingleSelectionModel
JPopupMenu
SingleSelectionModel
DefaultSingleSelectionModel
JProgressBar
BoundedRangeModel
DefaultBoundedRangeModel
JScrollBar
BoundedRangeModel
DefaultBoundedRangeModel
JSlider
BoundedRangeModel
DefaultBoundedRangeModel
JSpinner
SpinnerModel
AbstractSpinnerModel
SpinnerDateModel
SpinnerListModel
SpinnerNumberModel
JTabbedPane
SingleSelectionModel
DefaultSingleSelectionModel
JTable
TableModel
AbstractTableModel
DefaultTableModel
JTextComponent
TableColumnModel
DefaultTableColumnModel
ListSelectionModel
DefaultListSelectionModel
Document
AbstractDocument
PlainDocument
StyledDocument
DefaultStyleDocument
HTMLDocument
CHAPTER 3 ■ THE MODEL-VIEW-CONTROLLER ARCHITECTURE
Table 3-1. Swing Component Models (Continued)
Component
Data Model Interface
Implementations
JToggleButton
ButtonModel
JToggleButton
ToggleButtonModel
JTree
TreeModel
DefaultTreeModel
TreeSelectionModel
DefaultTreeSelectionModel
JTree.EmptySelectionModel
When directly accessing the model of a Swing component, if you change the model, all
registered views are automatically notified. This, in turn, causes the views to revalidate themselves to ensure that the components display their proper current states. This automatic
propagation of state changes is one reason why MVC has become so popular. In addition,
using the MVC architecture helps programs become more maintainable as they change over
time and their complexity grows. No longer will you need to worry about losing state information if you change visual component libraries!
Summary
This chapter provided a quick look at how the Swing components use a modified MVC architecture. You explored what makes up this modified architecture and how one particular
component, the JTextArea, maps into this architecture. In addition, the chapter discussed the
sharing of data models between components and listed all the data models for the different
Swing components.
In Chapter 4, you’ll start to look at the individual components that make up the Swing
component library. In addition, you’ll explore the Swing component class hierarchy as you
examine the base JComponent component from the Swing library.
65
CHAPTER 4
■■■
Core Swing Components
I
n Chapter 3, you received a quick introduction to the Model-View-Controller (MVC) pattern
used by the components of the JFC/Swing project. In this chapter, you’ll begin to explore how
to use the key parts of the many available components.
All Swing components start with the JComponent class. Although some parts of the Swing
libraries aren’t rooted with the JComponent class, all the components share JComponent as the
common parent class at some level of their ancestry. It’s with this JComponent class that
common behavior and properties are defined. In this chapter, you’ll look at common functionality such as component painting, customization, tooltips, and sizing.
As far as specific JComponent descendent classes are concerned, you’ll specifically look at
the JLabel, JButton, and JPanel, three of the more commonly used Swing component classes.
They require an understanding of the Icon interface for displaying images within components,
as well as of the ImageIcon class for when using predefined images and the GrayFilter class for
support. In addition, you’ll look at the AbstractButton class, which serves as the parent class to
the JButton. The data model shared by all AbstractButton subclasses is the ButtonModel interface; you’ll explore that and the specific implementation class, the DefaultButtonModel.
JComponent Class
The JComponent class serves as the abstract root class from which all Swing components
descend. The JComponent class has 42 descendent subclasses, each of which inherits much of
the JComponent functionality. Figure 4-1 shows this hierarchy.
Although the JComponent class serves as the common root class for all Swing components,
many classes in the libraries for the Swing project descend from classes other than JComponent.
Those include all the high-level container objects such as JFrame, JApplet, and JInternalFrame;
all the MVC-related classes; event-handling–related interfaces and classes; and much more.
All of these will be discussed in later chapters.
Although all Swing components extend JComponent, the JComponent class extends the AWT
Container class, which, in turn, extends from the AWT Component class. This means that many
aspects of the JComponent are shared with both the AWT Component and Container classes.
67
68
CHAPTER 4 ■ CORE SWING COMPONENTS
Figure 4-1. JComponent class hierarchy diagram
■Note JComponent extends from the Container class, but most of the JComponent subclasses aren’t
themselves containers of other components. To see if a particular Swing component is truly a container,
check the BeanInfo for the class to see if the isContainer property is set to true. To get the BeanInfo
for a class, ask the Introspector.
CHAPTER 4 ■ CORE SWING COMPONENTS
Component Pieces
The JComponent class defines many aspects of AWT components that go above and beyond the
capabilities of the original AWT component set. This includes customized painting behavior
and several different ways to customize display settings, such as colors, fonts, and any other
client-side settings.
Painting JComponent Objects
Because the Swing JComponent class extends from the Container class, the basic AWT painting
model is followed: All painting is done through the paint() method, and the repaint() method
is used to trigger updates. However, many tasks are done differently. The JComponent class optimizes many aspects of painting for improved performance and extensibility. In addition, the
RepaintManager class is available to customize painting behavior even further.
■Note The public void update(Graphics g) method, inherited from Component, is never invoked
on Swing components.
To improve painting performance and extensibility, the JComponent splits the painting
operation into three tasks. The public void paint(Graphics g) method is subdivided into three
separate protected method calls. In the order called, they are paintComponent(g), paintBorder(g),
and paintChildren(g), with the Graphics argument passed through from the original paint()
call. The component itself is first painted through paintComponent(g). If you want to customize
the painting of a Swing component, you override paintComponent() instead of paint(). Unless
you want to completely replace all the painting, you would call super.paintComponent() first,
as shown here, to get the default paintComponent() behavior.
public class MyComponent extends JPanel {
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// Customize after calling super.paintComponent(g)
}
...
}
■Note When running a program that uses Swing components in Java 5.0, the Graphics argument passed
to the paint() method and on to paintComponent() is technically a Graphics2D argument. Therefore,
after casting the Graphics argument to a Graphics2D object, you could use the Java 2D capabilities of the
platform, as you would when defining a drawing Stroke, Shape, or AffineTransform.
69
70
CHAPTER 4 ■ CORE SWING COMPONENTS
The paintBorder() and paintChildren() methods tend not to be overridden. The
paintBorder() method draws a border around the component, a concept described more
fully in Chapter 7. The paintChildren() method draws the components within the Swing
container object, if any are present.
To optimize painting, the JComponent class provides three additional painting properties:
opaque, optimizedDrawingEnabled, and doubleBuffered. These work as follows:
• Opacity: The opaque property for a JComponent defines whether a component is transparent. When transparent, the container of the JComponent must paint the background
behind the component. To improve performance, you can leave the JComponent opaque
and let the JComponent draw its own background, instead of relying on the container to
draw the covered background.
• Optimization: The optimizedDrawingEnabled property determines whether immediate
children can overlap. If children cannot overlap, the repaint time is reduced considerably. By default, optimized drawing is enabled for most Swing components, except for
JDesktopPane, JLayeredPane, and JViewport.
• Double buffering: By default, all Swing components double buffer their drawing operations
into a buffer shared by the complete container hierarchy; that is, all the components
within a window (or subclass). This greatly improves painting performance, because
when double buffering is enabled (with the doubleBuffered property), there is only a
single screen update drawn.
■Note For synchronous painting, you can call one of the public void paintImmediately() methods.
(Arguments are either a Rectangle or its parts—position and dimensions.) However, you’ll rarely need to
call this directly unless your program has real-time painting requirements.
The public void revalidate() method of JComponent also offers painting support. When
this method is called, the high-level container of the component validates itself. This is unlike
the AWT approach requiring a direct call to the revalidate() method of that high-level
component.
The last aspect of the Swing component painting enhancements is the RepaintManager
class.
RepaintManager Class
The RepaintManager class is responsible for ensuring the efficiency of repaint requests on the
currently displayed Swing components, making sure the smallest “dirty” region of the screen is
updated when a region becomes invalid.
Although rarely customized, RepaintManager is public and provides a static installation
routine to use a custom manager: public static void setCurrentManager(RepaintManager
manager). To get the current manager, just ask with public static void currentManager
(JComponent). The argument is usually null, unless you’ve customized the manager to provide
component-level support. Once you have the manager, one thing you can do is get the offscreen buffer for a component as an Image. Because the buffer is what is eventually shown on
CHAPTER 4 ■ CORE SWING COMPONENTS
the screen, this effectively allows you to do a screen dump of the inside of a window (or any
JComponent).
Component comp = ...
RepaintManager manager = RepaintManager.currentManager(null);
Image htmlImage = manager.getOffscreenBuffer(comp, comp.getWidth(),
comp.getHeight());
// or
Image volatileImage = manager.getVolatileOffscreenBuffer(comp, comp.getWidth(),
comp.getHeight());
Table 4-1 shows the two properties of RepaintManager. They allow you to disable double
buffering for all drawing operations of a component (hierarchy) and to set the maximum
double buffer size, which defaults to the end user’s screen size.
Table 4-1. RepaintManager Properties
Property Name
Data Type
Access
doubleBufferingEnabled
boolean
Read-write
doubleBufferMaximumSize
Dimension
Read-write
■Tip To globally disable double-buffered drawing, call RepaintManager.currentManager(aComponent).
setDoubleBufferingEnabled(false).
Although it’s rarely done, providing your own RepaintManager subclass does allow you to
customize the mechanism of painting dirty regions of the screen, or at least track when the
painting is finished. Overriding any of the following four methods allows you to customize the
mechanisms:
public synchronized void addDirtyRegion(JComponent component, int x, int y,
int width, int height)
public Rectangle getDirtyRegion(JComponent component)
public void markCompletelyClean(JComponent component)
public void markCompletelyDirty(JComponent component)
UIDefaults Class
The UIDefaults class represents a lookup table containing the display settings installed for the
current look and feel, such as which font to use within a JList, as well as what color or icon
should be displayed within a JTree node. The use of UIDefaults will be detailed in Chapter 20
with the coverage of Java’s pluggable look and feel architecture. Here, you will get a brief introduction to the UIDefaults table.
71
72
CHAPTER 4 ■ CORE SWING COMPONENTS
Whenever you create a component, the component automatically asks the UIManager to
look in the UIDefaults table for the current settings for that component. Most color- and fontrelated component settings, as well as some others not related to colors and fonts, are configurable.
If you don’t like a particular setting, you can simply change it by updating the appropriate entry
in the UIDefaults lookup table.
■Note All predefined resource settings in the UIDefaults table implement the UIResource interface,
which allows the components to monitor which settings have been customized just by looking for those
settings that don’t implement the interface.
First, you need to know the name of the UIDefaults setting you want to change. You can
find the setting names in Appendix A of this book, which contains a complete alphabetical
listing of all known settings for the predefined look and feel types in J2SE 5.0. (These differ a
little from release to release.) In addition, included with the description of each component is
a table containing the UIResource-related property elements. (To find the specific component
section in the book, consult the table of contents or the index.)
Once you know the name of a setting, you can store a new setting with the public static
void put(Object key, Object value) method of UIManager, where key is the key string. For
instance, the following code will change the default background color of newly created buttons
to black and the foreground color to red:
UIManager.put("Button.background", Color.BLACK);
UIManager.put("Button.foreground", Color.RED);
Fetching UIResource Properties
If you’re creating your own components, or just need to find out the current value setting, you
can ask the UIManager. Although the public static Object get(Object key) method is the
most generic, it requires you to cast the return value to the appropriate class type. Alternatively, you
could use one of the more specific getXXX() methods, which does the casting for you, to return
the appropriate type:
public
public
public
public
public
public
public
public
public
public
static
static
static
static
static
static
static
static
static
static
boolean getBoolean(Object key)
Border getBorder(Object key)
Color getColor(Object key)
Dimension getDimension(Object key)
Font getFont(Object key)
Icon getIcon(Object key)
Insets getInsets(Object key)
int getInt(Object key)
String getString(Object key)
ComponentUI getUI(JComponent target)
There is a second set of overloaded methods that accept a second argument for the Locale.
CHAPTER 4 ■ CORE SWING COMPONENTS
■Note You can also work with the UIDefaults directly, by calling the public static UIDefaults
getDefaults() method of UIManager.
Client Properties
In addition to the UIManager maintaining a table of key/value pair settings, each instance of
every component can manage its own set of key/value pairs. This is useful for maintaining
aspects of a component that may be specific to a particular look and feel, or for maintaining
data associated with a component without requiring the definition of new classes or methods
to store such data.
public final void putClientProperty(Object key, Object value)
public final Object getClientProperty(Object key)
■Note Calling putClientProperty() with a value of null causes the key to be removed from the client
property table.
For instance, the JTree class has a property with the Metal look and feel for configuring the
line style for connecting or displaying nodes within a JTree. Because the setting is specific to
one look and feel, it doesn’t make sense to add something to the tree API. Instead, you set the
property by calling the following on a particular tree instance:
tree.putClientProperty("JTree.lineStyle", "None")
Then, when the look and feel is the default Metal, lines will connect the nodes of the tree.
If another look and feel is installed, the client property will be ignored. Figure 4-2 shows a tree
with and without lines.
Figure 4-2. A JTree, with and without angled lines
73
74
CHAPTER 4 ■ CORE SWING COMPONENTS
■Note The list of client properties is probably one of the least documented aspects of Swing. Chapter 20
lists the available properties I was able to determine. Also, while Metal is the default look and feel, what you
see is called Ocean. Ocean is a theme of the Metal look and feel and makes Metal look a bit flashier.
JComponent Properties
You’ve seen some of the pieces shared by the different JComponent subclasses. Now it’s time to
look at the JavaBeans properties. Table 4-2 shows the complete list of properties defined by
JComponent, including those inherited through the AWT Container and Component classes.
Table 4-2. JComponent Properties
Property Name
Data Type
Component
Access
Container
Access
JComponent
Access
accessibleContext
AccessibleContext
Read-only
N/A
Read-only
actionMap
ActionMap
N/A
N/A
Read-write
alignmentX
float
Read-only
Read-only Read-write
alignmentY
float
Read-only
Read-only Read-write
ancestorListeners
AncestorListener[ ]
N/A
N/A
Read-only
autoscrolls
boolean
N/A
N/A
Read-write
background
Color
Read-write
bound
N/A
Write-only
backgroundSet
boolean
Read-only
N/A
N/A
border
Border
N/A
N/A
Read-write
bound
bounds
Rectangle
Read-write
N/A
N/A
colorModel
ColorModel
Read-only
N/A
N/A
componentCount
int
N/A
Read-only N/A
componentListeners
ComponentListener[ ]
Read-only
N/A
N/A
componentOrientation
ComponentOrientation
Read-write
bound
N/A
N/A
componentPopupMenu
JPopupMenu
N/A
N/A
Read-write
components
Component[ ]
N/A
Read-only N/A
containerListeners
ContainerListener[ ]
N/A
Read-only N/A
cursor
Cursor
Read-write
N/A
N/A
cursorSet
boolean
Read-only
N/A
N/A
debugGraphicsOptions
int
N/A
N/A
Read-write
displayable
boolean
Read-only
N/A
N/A
CHAPTER 4 ■ CORE SWING COMPONENTS
75
Table 4-2. JComponent Properties (Continued)
Property Name
Data Type
Component
Access
Container
Access
JComponent
Access
doubleBuffered
boolean
Read-only
N/A
Read-write
dropTarget
DropTarget
Read-write
N/A
N/A
enabled
boolean
Read-write
N/A
Write-only
bound
focusable
boolean
Read-write
bound
N/A
N/A
focusCycleRoot
boolean
N/A
Read-write N/A
bound
focusCycleRootAncestor
Container
Read-only
N/A
N/A
focusListeners
FocusListener[ ]
Read-only
N/A
N/A
focusOwner
boolean
Read-only
N/A
N/A
focusTraversalKeysEnabled
boolean
Read-write
bound
N/A
N/A
focusTraversalPolicy
FocusTraversalPolicy
N/A
Read-write N/A
bound
focusTraversalPolicyProvider
boolean
N/A
Read-write N/A
bound
focusTraversalPolicySet
boolean
N/A
Read-only N/A
font
Font
Read-write
bound
Write-only Write-only
fontSet
boolean
Read-only
N/A
N/A
foreground
Color
Read-write
bound
N/A
Write-only
foregroundSet
boolean
Read-only
N/A
N/A
graphics
Graphics
Read-only
N/A
Read-only
graphicsConfiguration
GraphicsConfiguration
Read-only
N/A
N/A
height
int
Read-only
N/A
Read-only
hierarchyBoundsListeners
HierarchyBoundsListener[ ]
Read-only
N/A
N/A
hierarchyListeners
HierarchyListener[ ]
Read-only
N/A
N/A
ignoreRepaint
boolean
Read-write
N/A
N/A
inheritsPopupMenu
boolean
N/A
N/A
Read-write
inputContext
InputContext
Read-only
N/A
N/A
inputMap
InputMap
N/A
N/A
Read-only
inputMethodListeners
InputMethodListener[ ]
Read-only
N/A
N/A
inputMethodRequests
InputMethodRequests
Read-only
N/A
N/A
76
CHAPTER 4 ■ CORE SWING COMPONENTS
Table 4-2. JComponent Properties (Continued)
Property Name
Data Type
Component
Access
Container
Access
JComponent
Access
inputVerifier
InputVerifier
N/A
N/A
Read-write
bound
insets
Insets
N/A
Read-only Read-only
keyListeners
KeyListener[ ]
Read-only
N/A
layout
LayoutManager
N/A
Read-write N/A
lightweight
boolean
Read-only
N/A
N/A
locale
Locale
Read-write
bound
N/A
N/A
location
Point
Read-write
N/A
N/A
locationOnScreen
Point
Read-only
N/A
N/A
maximumSize
Dimension
Read-write
bound
Read-only Read-write
maximumSizeSet
boolean
Read-only
N/A
minimumSize
Dimension
Read-write
bound
Read-only Read-write
minimumSizeSet
boolean
Read-only
N/A
N/A
mouseListeners
MouseListener[ ]
Read-only
N/A
N/A
mouseMotionListeners
MouseMotionListener[ ]
Read-only
N/A
N/A
mousePosition
Point
Read-only
N/A
N/A
mouseWheelListeners
MouseWheelListener
Read-only
N/A
N/A
name
String
Read-write
bound
N/A
N/A
opaque
boolean
Read-only
N/A
Read-write
bound
optimizedDrawingEnabled
boolean
N/A
N/A
Read-only
paintingTile
boolean
N/A
N/A
Read-only
parent
Container
Read-only
N/A
N/A
preferredSize
Dimension
Read-write
bound
Read-only Read-write
preferredSizeSet
boolean
Read-only
N/A
N/A
propertyChangeListeners
PropertyChangeListener[ ]
Read-only
N/A
N/A
registeredKeyStrokes
KeyStroke[ ]
N/A
N/A
Read-only
requestFocusEnabled
boolean
N/A
N/A
Read-write
rootPane
JRootPane
N/A
N/A
Read-only
N/A
N/A
CHAPTER 4 ■ CORE SWING COMPONENTS
77
Table 4-2. JComponent Properties (Continued)
Property Name
Data Type
Component
Access
Container
Access
JComponent
Access
showing
boolean
Read-only
N/A
N/A
size
Dimension
Read-write
N/A
N/A
toolkit
Toolkit
Read-only
N/A
N/A
tooltipText
String
N/A
N/A
Read-write
topLevelAncestor
Container
N/A
N/A
Read-only
transferHandler
TransferHandler
N/A
N/A
Read-write
bound
treeLock
Object
Read-only
N/A
N/A
uiClassID
String
N/A
N/A
Read-only
valid
boolean
Read-only
N/A
N/A
validateRoot
boolean
N/A
N/A
Read-only
verifyInputWhenFocusTarget
boolean
N/A
N/A
Read-write
bound
vetoableChangeListeners
VetoableChangeListener[ ]
N/A
N/A
Read-only
visible
boolean
Read-write
N/A
Write-only
visibleRect
Rectangle
N/A
N/A
Read-only
width
int
Read-only
N/A
Read-only
x
int
Read-only
N/A
Read-only
y
int
Read-only
N/A
Read-only
■Note Additionally, there’s a read-only class property defined at the Object level, the parent of the
Component class.
Including the properties from the parent hierarchy, approximately 92 properties of
JComponent exist. As that number indicates, the JComponent class is extremely well suited for
visual development. There are roughly ten categories of JComponent properties, as described in
the following sections.
Position-Oriented Properties
The x and y properties define the location of the component relative to its parent. The
locationOnScreen is just another location for the component, this time relative to the screen’s
origin (the upper-left corner). The width and height properties define the size of the component.
The visibleRect property describes the part of the component visible within the topLevelAncestor,
whereas the bounds property defines the component’s area, whether visible or not.
78
CHAPTER 4 ■ CORE SWING COMPONENTS
Component-Set-Oriented Properties
The components and componentCount properties enable you to find out what the children
components are of the particular JComponent. For each component in the components property
array, the current component would be its parent. In addition to determining a component’s
parent, you can find out its rootPane or topLevelAncestor.
Focus-Oriented Properties
The focusable, focusCycleRoot, focusCycleRootAncestor, focusOwner, focusTraversalKeysEnabled,
focusTraversalPolicy, focusTraversalPolicyProvider, focusTraversablePolicySet,
requestFocusEnabled, verifyInputWhenFocusTarget, and inputVerifier properties define the
set of focus-oriented properties. These properties control the focus behavior of JComponent and
were discussed in Chapter 2.
Layout-Oriented Properties
The alignmentX, alignmentY, componentOrientation, layout, maximumSize, minimumSize,
preferredSize, maximumSizeSet, minimumSizeSet, and preferredSizeSet properties are
used to help with layout management.
Painting Support Properties
The background and foreground properties describe the current drawing colors. The font property
describes the text style to draw. The backgroundSet, foregroundSet, and fontSet properties
describe if the properties are explicitly set. The insets and border properties are intermixed
to describe the drawing of a border around a component. The graphics property permits realtime drawing, although the paintImmediately() method might now suffice.
To improve performance, there are the opaque (false is transparent), doubleBuffered,
ignoreRepaint, and optimizedDrawingEnabled properties. The colorModel and paintingTile
properties store intermediate drawing information. The graphicsConfiguration property adds
support for virtual devices.
debugGraphicsOption allows you to slow down the drawing of your component if you can’t
figure out why it’s not painted properly. The debugGraphicsOption property is set to one or
more of the settings shown in Table 4-3.
Table 4-3. DebugGraphics Settings
DebugGraphics Settings
Description
DebugGraphics.BUFFERED_OPTION
Causes a window to pop up, displaying the drawing of
the double-buffered image
DebugGraphics.FLASH_OPTION
Causes the drawing to be done more slowly, flashing
between steps
DebugGraphics.LOG_OPTION
Causes a message to be printed to the screen as each
step is done
DebugGraphics.NONE_OPTION
Disables all options
CHAPTER 4 ■ CORE SWING COMPONENTS
You can combine multiple DebugGraphics settings with the bitwise OR (|) operator, as in
this example:
JComponent component = new ...();
component.setDebugGraphicsOptions(DebugGraphics.BUFFERED_OPTION |
DebugGraphics.FLASH_OPTION | DebugGraphics.LOG_OPTION);
Internationalization Support Properties
The inputContext, inputMethodRequests, and locale properties help when creating multilingual
operations.
State Support Properties
To get state information about a component, all you have to do is ask; there’s much you can
discover. The autoscrolls property lets you place a component within a JViewport and it automatically scrolls when dragged. The validateRoot property is used when revalidate() has
been called and returns true when the current component is at the point it should stop. The
remaining seven properties are self-explanatory: displayable, dropTarget, enabled, lightweight,
showing, valid, and visible.
Event Support Properties
The registeredKeyStrokes, inputMap, and actionMap properties allow you to register keystroke
responses with a window. All the getXXXListeners() methods allow you to get the current set
of listeners for a particular listener type.
Pop-Up Support Properties
There are two types of pop-ups associated with a component: tooltips and pop-up menus.
The toolTipText property is set to display pop-up support text over a component. The
componentPopupMenu and inheritsPopupMenu properties are related to automatically showing
pop-up menus associated with the component. The mousePosition property helps to position
these.
Other Properties
The remaining properties don’t seem to have any kind of logical grouping. The accessibleContext
property is for support with the javax.accessibility package. The cursor property lets you
change the cursor to one of the available cursors, where cursorSet is used to recognize when
the property is explicitly set. The toolkit property encapsulates platform-specific behaviors
for accessing system resources. The transferHandler property is there for drag-and-drop
support. The name property gives you the means to recognize a particular instance of a class.
The treelock property is the component tree-synchronization locking resource. The uiClassID
property is new; it allows subclasses to return the appropriate class ID for their specific instance.
79
80
CHAPTER 4 ■ CORE SWING COMPONENTS
Handling JComponent Events
There are many different types of events that all JComponent subclasses share. Most of these
come from parent classes, like Component and Container. First, you’ll explore the use of
PropertyChangeListener, which is inherited from Container. Then you’ll look at the use of two
event-handling capabilities shared by all JComponent subclasses: VetoableChangeListener and
AncestorListener. Finally, you’ll see the complete set of listeners inherited from Component.
Listening to Component Events with a PropertyChangeListener
The JComponent class has several component bound properties, directly and indirectly. By
binding a PropertyChangeListener to the component, you can listen for particular JComponent
property changes, and then respond accordingly.
public interface PropertyChangeListener extends EventListener {
public void propertyChange(PropertyChangeEvent propertyChangeEvent);
}
To demonstrate, the PropertyChangeListener in Listing 4-1 demonstrates the behavior you
might need when listening for changes to an Action type property within a JButton component.
The property that changes determines which if block is executed.
Listing 4-1. Watching for Changes to a JButton
import java.beans.*;
import javax.swing.*;
public class ActionChangedListener implements PropertyChangeListener {
private JButton button;
public ActionChangedListener(JButton button) {
this.button = button;
}
public void propertyChange(PropertyChangeEvent e) {
String propertyName = e.getPropertyName();
if (e.getPropertyName().equals(Action.NAME)) {
String text = (String)e.getNewValue();
button.setText(text);
button.repaint();
} else if (propertyName.equals("enabled")) {
Boolean enabledState = (Boolean)e.getNewValue();
button.setEnabled(enabledState.booleanValue());
button.repaint();
CHAPTER 4 ■ CORE SWING COMPONENTS
}
else if (e.getPropertyName().equals(Action.SMALL_ICON)) {
Icon icon = (Icon)e.getNewValue();
button.setIcon(icon);
button.invalidate();
button.repaint();
}
}
}
■Note You can bind a PropertyChangeListener to a specific property by adding the listener with
addPropertyChangeListener(String propertyName, PropertyChangeListener listener).
This allows your listener to avoid having to check for the specific property that changed.
Listening to JComponent Events with a VetoableChangeListener
The VetoableChangeListener is another JavaBeans listener that Swing components use.
It works with constrained properties, whereas the PropertyChangeListener works with
only bound properties. A key difference between the two is that the public void
vetoableChange(PropertyChangeEvent propertyChangeEvent) method can throw a
PropertyVetoException if the listener doesn’t like the requested change.
public interface VetoableChangeListener extends EventListener {
public void vetoableChange(PropertyChangeEvent propertyChangeEvent)
throws PropertyVetoException;
}
■Note Only one Swing class, JInternalFrame, has constrained properties. The listener is meant primarily for
programmers to use with their own newly created components.
Listening to JComponent Events with an AncestorListener
You can use an AncestorListener to find out when a component moves, is made visible, or is
made invisible. It’s useful if you permit your users to customize their screens by moving
components around and possibly removing components from the screens.
public interface AncestorListener extends EventListener {
public void ancestorAdded(AncestorEvent ancestorEvent);
public void ancestorMoved(AncestorEvent ancestorEvent);
public void ancestorRemoved(AncestorEvent ancestorEvent);
}
81
82
CHAPTER 4 ■ CORE SWING COMPONENTS
To demonstrate, Listing 4-2 associates an AncestorListener with the root pane of a JFrame.
You’ll see the messages Removed, Added, and Moved when the program first starts up. In addition,
you’ll see Moved messages when you drag the frame around.
Listing 4-2. Listening for Ancestor Events
import java.awt.*;
import javax.swing.*;
import javax.swing.event.*;
public class AncestorSampler {
public static void main (String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("Ancestor Sampler");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
AncestorListener ancestorListener = new AncestorListener() {
public void ancestorAdded(AncestorEvent ancestorEvent) {
System.out.println ("Added");
}
public void ancestorMoved(AncestorEvent ancestorEvent) {
System.out.println ("Moved");
}
public void ancestorRemoved(AncestorEvent ancestorEvent) {
System.out.println ("Removed");
}
};
frame.getRootPane().addAncestorListener(ancestorListener);
frame.setSize(300, 200);
frame.setVisible(true);
frame.getRootPane().setVisible(false);
frame.getRootPane().setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
Listening to Inherited Events of a JComponent
In addition to the ability to listen for an instance of an AncestorEvent or PropertyChangeEvent
with a JComponent, the JComponent inherits the ability to listen to many other events from its
Container and Component superclasses.
CHAPTER 4 ■ CORE SWING COMPONENTS
Table 4-4 lists ten event listeners. You may find yourself using the JComponent listener
interfaces quite a bit, but the older ones work, too. Use the ones most appropriate for the task
at hand.
Table 4-4. JComponent Inherited Event Listeners
Class
Event Listener
Event Object
Component
ComponentListener
componentHidden(ComponentEvent)
componentMoved(ComponentEvent)
componentResized(ComponentEvent)
componentShown(ComponentEvent)
Component
FocusListener
focusGained(FocusEvent)
focusLost(FocusEvent)
Component
HierarchyBoundsListener
ancestorMoved(HierarchyEvent)
ancestorResized(HierarchyEvent)
Component
HierarchyListener
hierarchyChanged(HierarchyEvent)
Component
InputMethodListener
caretPositionChanged
(InputMethodEvent)
inputMethodTextChanged
(InputMethodEvent)
Component
KeyListener
keyPressed(KeyEvent)
keyReleased(KeyEvent)
keyTyped(KeyEvent)
Component
MouseListener
mouseClicked(MouseEvent)
mouseEntered(MouseEvent)
mouseExited(MouseEvent)
mousePressed(MouseEvent)
mouseReleased(MouseEvent)
Component
MouseMotionListener
mouseDragged(MouseEvent)
mouseMoved(MouseEvent)
Component
MouseWheelListener
mouseWheelMoved(MouseWheelEvent)
Container
ContainerListener
componentAdded(ContainerEvent)
componentRemoved(ContainerEvent)
83
84
CHAPTER 4 ■ CORE SWING COMPONENTS
JToolTip Class
The Swing components support the ability to display brief pop-up messages when the cursor
rests over them. The class used to display pop-up messages is JToolTip.
Creating a JToolTip
Calling the public void setToolTipText(String text) method of JComponent automatically
causes the creation of a JToolTip instance when the mouse rests over a component with the
installed pop-up message. You don’t normally call the JToolTip constructor directly. There’s
only one constructor, and it’s of the no-argument variety.
Tooltip text is normally one line long. However, if the text string begins with (in any
case), then the contents can be any HTML 3.2 formatted text. For instance, the following line
causes the pop-up message shown in Figure 4-3:
component.setToolTipText("Tooltip Message");
Figure 4-3. HTML-based tooltip text
Creating Customized JToolTip Objects
You can easily customize the display characteristics for all pop-up messages by setting UIResource
elements for JToolTip, as shown in the “Customizing a JToolTip Look and Feel” section later in
this chapter.
The JComponent class defines an easy way for you to customize the display characteristics
of the tooltip when it’s placed over a specific component. Simply subclass the component you
want to customize and override its inherited public JToolTip createToolTip() method. The
createToolTip() method is called when the ToolTipManager has determined that it’s time to
display the pop-up message.
To customize the pop-up tooltip appearance, just override the method and customize the
JToolTip returned from the inherited method. For instance, the following source demonstrates
the setting of a custom coloration for the tooltip for a JButton, as shown in Figure 4-4.
JButton b = new JButton("Hello, World") {
public JToolTip createToolTip() {
JToolTip tip = super.createToolTip();
tip.setBackground(Color.YELLOW);
tip.setForeground(Color.RED);
return tip;
}
};
CHAPTER 4 ■ CORE SWING COMPONENTS
Figure 4-4. Tooltip text displayed with custom colors
After the JToolTip has been created, you can configure the inherited JComponent properties
or any of the properties specific to JToolTip, as shown in Table 4-5.
Table 4-5. JToolTip Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
component
JComponent
Read-write
tipText
String
Read-write
UI
ToolTipUI
Read-only
UIClassID
String
Read-only
Displaying Positional Tooltip Text
Swing components can even support the display of different tooltip text, depending on where
the mouse pointer is located. This requires overriding the public boolean contains(int x, int y)
method, which originates from the Component class.
For instance, after enhancing the customized JButton created in the previous section
(Figure 4-4), the tooltip text will differ, depending on whether or not the mouse pointer is
within 50 pixels from the left edge of the component.
JButton button = new JButton("Hello, World") {
public JToolTip createToolTip() {
JToolTip tip = super.createToolTip();
tip.setBackground(Color.YELLOW);
tip.setForeground(Color.RED);
return tip;
}
public boolean contains(int x, int y) {
if (x < 50) {
setToolTipText("Got Green Eggs?");
} else {
setToolTipText("Got Ham?");
}
return super.contains(x, y);
}
};
85
86
CHAPTER 4 ■ CORE SWING COMPONENTS
Customizing a JToolTip Look and Feel
Each installable Swing look and feel provides a different JToolTip appearance and a set of
default UIResource value settings. Figure 4-5 shows the appearance of the JToolTip component
for the preinstalled set of look and feel types: Motif, Windows, and Ocean.
Motif
Windows
Ocean
Figure 4-5. JToolTip under different look and feel types
The available set of UIResource-related properties for a JToolTip is shown in Table 4-6.
For the JToolTip component, there are nine different properties.
Table 4-6. JToolTip UIResource Elements
Property String
Object Type
ToolTip.background
Color
ToolTip.backgroundInactive
Color
ToolTip.border
Border
ToolTip.borderInactive
Color
ToolTip.font
Font
ToolTip.foreground
Color
ToolTip.foregroundInactive
Color
ToolTip.hideAccelerator
Boolean
ToolTipUI
String
As noted earlier in this chapter, the JToolTip class supports the display of arbitrary HTML
content. This permits the display of multiple-column and multiple-row input.
ToolTipManager Class
Although the JToolTip is something of a passive object, in the sense that the JComponent creates
and shows the JToolTip on its own, there are many more configurable aspects of its usage.
However, these configurable aspects are the responsibility of the class that manages tooltips,
not the JToolTip itself. The class that manages tooltip usage is aptly named ToolTipManager.
With the Singleton design pattern, no constructor for ToolTipManager exists. Instead, you have
access to the current manager through the static sharedInstance() method of ToolTipManager.
CHAPTER 4 ■ CORE SWING COMPONENTS
ToolTipManager Properties
Once you have accessed the shared instance of ToolTipManager, you can customize when and
if tooltip text appears. As Table 4-7 shows, there are five configurable properties.
Table 4-7. ToolTipManager Properties
Property Name
Data Type
Access
dismissDelay
int
Read-write
enabled
boolean
Read-write
initialDelay
int
Read-write
lightWeightPopupEnabled
boolean
Read-write
reshowDelay
int
Read-only
Initially, tooltips are enabled, but you can disable them with ToolTipManager.
sharedInstance().setEnabled(false). This allows you to always associate tooltips with
components, while letting the end user enable and disable them when desired.
There are three timing-oriented properties: initialDelay, dismissDelay, and reshowDelay.
They all measure time in milliseconds. The initialDelay property is the number of milliseconds
the user must rest the mouse inside the component before the appropriate tooltip text appears.
The dismissDelay specifies the length of time the text appears while the mouse remains
motionless; if the user moves the mouse, it also causes the text to disappear. The reshowDelay
determines how long a user must remain outside a component before reentry would cause the
pop-up text to reappear.
The lightWeightPopupEnabled property is used to determine the pop-up window type to
hold the tooltip text. If the property is true and the pop-up text fits entirely within the bounds
of the top-level window, the text appears within a Swing JPanel. If this property is false and the
pop-up text fits entirely within the bounds of the top-level window, the text appears within an
AWT Panel. If part of the text wouldn’t appear within the top-level window no matter what the
property setting is, the pop-up text would appear within a Window.
Although not properties of ToolTipManager, two other methods of ToolTipManager are
worth mentioning:
public void registerComponent(JComponent component)
public void unregisterComponent(JComponent component)
When you call the setToolTipText() method of JComponent, this causes the component to
register itself with the ToolTipManager. There are times, however, when you need to register
a component directly. This is necessary when the display of part of a component is left to
another renderer. With JTree, for instance, a TreeCellRenderer displays each node of the tree.
When the renderer displays the tooltip text, you “register” the JTree and tell the renderer what
text to display.
87
88
CHAPTER 4 ■ CORE SWING COMPONENTS
JTree tree = new JTree(...);
ToolTipManager.sharedInstance().registerComponent(tree);
TreeCellRenderer renderer = new ATreeCellRenderer(...);
tree.setCellRenderer(renderer);
...
public class ATreeCellRenderer implements TreeCellRenderer {
...
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) {
...
renderer.setToolTipText("Some Tip");
return renderer;
}
}
■Note If this sounds confusing, don’t worry. We’ll revisit the JTree in Chapter 17.
JLabel Class
The first real Swing component to examine closely is the simplest, the JLabel. The JLabel
serves as the replacement component for the AWT Label but it can do much more. Whereas the
AWT Label is limited to a single line of text, the Swing JLabel can have text, images, or both. The
text can be a single line of text or HTML. In addition JLabel can support different enabled and
disabled images. Figure 4-6 shows some sample JLabel components.
Figure 4-6. Sample JLabel components
■Note A JLabel subclass is used as the default renderer for each of the JList, JComboBox, JTable,
and JTree components.
CHAPTER 4 ■ CORE SWING COMPONENTS
Creating a JLabel
There are six constructors for JLabel:
public JLabel()
JLabel label = new JLabel();
public JLabel(Icon image)
Icon icon = new ImageIcon("dog.jpg");
JLabel label = new JLabel(icon);
public JLabel(Icon image, int horizontalAlignment)
Icon icon = new ImageIcon("dog.jpg");
JLabel label = new JLabel(icon, JLabel.RIGHT);
public JLabel(String text)
JLabel label = new JLabel("Dog");
public JLabel(String text, int horizontalAlignment)
JLabel label = new JLabel("Dog", JLabel.RIGHT);
public JLabel(String text, Icon icon, int horizontalAlignment)
Icon icon = new ImageIcon("dog.jpg");
JLabel label = new JLabel("Dog", icon, JLabel.RIGHT);
With the constructors for JLabel, you can customize any of three properties of the
JLabel: text, icon, or horizontalAlignment. By default, the text and icon properties are empty,
whereas the initial horizontalAlignment property setting depends on the constructor arguments.
These settings can be any of JLabel.LEFT, JLabel.CENTER, or JLabel.RIGHT. In most cases, not
specifying the horizontalAlignment setting results in a left-aligned label. However, if only the
initial icon is specified, then the default alignment is centered.
JLabel Properties
Table 4-8 shows the 14 properties of JLabel. They allow you to customize the content, position,
and (in a limited sense) the behavior of the JLabel.
Table 4-8. JLabel Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
disabledIcon
Icon
Read-write bound
displayedMnemonic
char
Read-write bound
displayedMnemonicIndex
int
Read-write bound
horizontalAlignment
int
Read-write bound
horizontalTextPosition
int
Read-write bound
89
90
CHAPTER 4 ■ CORE SWING COMPONENTS
Table 4-8. JLabel Properties (Continued)
Property Name
Data Type
Access
icon
Icon
Read-write bound
iconTextGap
int
Read-write bound
labelFor
Component
Read-write bound
text
String
Read-write bound
UI
LabelUI
Read-write
UIClassID
String
Read-only
verticalAlignment
int
Read-write bound
verticalTextPosition
int
Read-write bound
The content of the JLabel is the text and its associated image. Displaying an image within
a JLabel will be discussed in the “Interface Icon” section later in this chapter. However, you can
display different icons, depending on whether the JLabel is enabled or disabled. By default, the
icon is a grayscaled version of the enabled icon, if the enabled icon comes from an Image object
(ImageIcon, as described later in the chapter). If the enabled icon doesn’t come from an Image,
there’s no icon when JLabel is disabled, unless manually specified.
The position of the contents of the JLabel is described by four different properties:
horizontalAlignment, horizontalTextPosition, verticalAlignment, and verticalTextPosition.
The horizontalAlignment and verticalAlignment properties describe the position of the contents
of the JLabel within the container in which it’s placed.
■Note Alignments have an effect only if there’s extra space for the layout manager to position the component. If you’re using a layout manager such as FlowLayout, which sizes components to their preferred size,
these settings will effectively be ignored.
The horizontal position can be any of the JLabel constants LEFT, RIGHT, or CENTER. The
vertical position can be TOP, BOTTOM, or CENTER. Figure 4-7 shows various alignment settings,
with the label reflecting the alignments.
The text position properties reflect where the text is positioned relative to the icon when
both are present. The properties can be set to the same constants as the alignment constants.
Figure 4-8 shows various text position settings, with each label reflecting the setting.
■Note The constants for the different positions come from the SwingConstants interface that the
JLabel class implements.
CHAPTER 4 ■ CORE SWING COMPONENTS
Figure 4-7. Various JLabel alignments
Figure 4-8. Various JLabel text positions
JLabel Event Handling
No event-handling capabilities are specific to the JLabel. Besides the event-handling capabilities
inherited through JComponent, the closest thing there is for event handling with the JLabel is the
combined usage of the displayedMnemonic, displayedMnemonicIndex, and labelFor properties.
When the displayedMnemonic and labelFor properties are set, pressing the keystroke specified
by the mnemonic, along with the platform-specific hotkey (usually Alt), causes the input focus
to shift to the component associated with the labelFor property. This can be helpful when a
component doesn’t have its own manner of displaying a mnemonic setting, such as with all the
text input components. Here is an example, which results in the display shown in Figure 4-9:
JLabel label = new JLabel("Username");
JTextField textField = new JTextField();
label.setDisplayedMnemonic(KeyEvent.VK_U);
label.setLabelFor(textField);
Figure 4-9. Using a JLabel to display the mnemonic for another component
91
92
CHAPTER 4 ■ CORE SWING COMPONENTS
The displayedMnemonicIndex property adds the ability for the mnemonic highlighted to
not be the first instance of mnemonic in the label’s text. The index you specify represents the
position in the text, not the instance of the mnemonic. To highlight the second e in Username,
you would specify an index of 7: label.setDisplayedMnemonicIndex(7).
■Note The component setting of the labelFor property is stored as a client property of the JLabel with
the LABELED_BY_PROPERTY key constant. The setting is used for accessibility purposes.
Customizing a JLabel Look and Feel
Each installable Swing look and feel provides a different JLabel appearance and set of default
UIResource value settings. Although appearances differ based on the current look and feel, the
differences are minimal within the preinstalled set of look and feel types. Table 4-9 shows the
available set of UIResource-related properties for a JLabel. There are eight different properties
for the JLabel component.
Table 4-9. JLabel UIResource Elements
Property String
Object Type
Label.actionMap
ActionMap
Label.background
Color
Label.border
Border
Label.disabledForeground
Color
Label.disabledShadow
Color
Label.font
Font
Label.foreground
Color
LabelUI
String
Interface Icon
The Icon interface is used to associate glyphs with various components. A glyph (like a symbol
on a highway sign that conveys information nonverbally, such as “winding road ahead!”) can
be a simple drawing or a GIF image loaded from disk with the ImageIcon class. The interface
contains two properties describing the size and a method to paint the glyph.
public interface Icon {
// Properties
public int getIconHeight();
public int getIconWidth();
// Other methods
public void paintIcon(Component c, Graphics g, int x, int y);
}
CHAPTER 4 ■ CORE SWING COMPONENTS
Creating an Icon
Creating an Icon is as simple as implementing the interface. All you need to do is specify the
size of the icon and what to draw. Listing 4-3 shows one such Icon implementation. The icon is
a diamond-shaped glyph in which the size, color, and filled-status are all configurable.
■Tip In implementing the paintIcon() method of the Icon interface, translate the drawing coordinates
of the graphics context based on the x and y position passed in, and then translate them back when the
drawing is done. This greatly simplifies the different drawing operations.
Listing 4-3. Reusable Diamond Icon Definition
import javax.swing.*;
import java.awt.*;
public class DiamondIcon implements Icon {
private Color color;
private boolean selected;
private int width;
private int height;
private Polygon poly;
private static final int DEFAULT_WIDTH = 10;
private static final int DEFAULT_HEIGHT = 10;
public DiamondIcon(Color color) {
this(color, true, DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
public DiamondIcon(Color color, boolean selected) {
this(color, selected, DEFAULT_WIDTH, DEFAULT_HEIGHT);
}
public DiamondIcon(Color color, boolean selected, int width, int height) {
this.color = color;
this.selected = selected;
this.width = width;
this.height = height;
initPolygon();
}
private void initPolygon() {
poly = new Polygon();
int halfWidth = width/2;
int halfHeight = height/2;
poly.addPoint(0, halfHeight);
poly.addPoint(halfWidth, 0);
93
94
CHAPTER 4 ■ CORE SWING COMPONENTS
poly.addPoint(width, halfHeight);
poly.addPoint(halfWidth, height);
}
public int getIconHeight() {
return height;
}
public int getIconWidth() {
return width;
}
public void paintIcon(Component c, Graphics g, int x, int y) {
g.setColor(color);
g.translate(x, y);
if (selected) {
g.fillPolygon(poly);
} else {
g.drawPolygon(poly);
}
g.translate(-x, -y);
}
}
Using an Icon
Once you have your Icon implementation, using the Icon is as simple as finding a component
with an appropriate property. For example, here’s the icon with a JLabel:
Icon icon = new DiamondIcon(Color.RED, true, 25, 25);
JLabel label = new JLabel(icon);
Figure 4-10 shows what such a label might look like.
Figure 4-10. Using an Icon in a JLabel
ImageIcon Class
The ImageIcon class presents an implementation of the Icon interface for creating glyphs from
AWT Image objects, whether from memory (a byte[ ]), off a disk (a file name), or over the
network (a URL). Unlike with regular Image objects, the loading of an ImageIcon is immediately
started when the ImageIcon is created, though it might not be fully loaded when used. In addition,
CHAPTER 4 ■ CORE SWING COMPONENTS
unlike Image objects, ImageIcon objects are serializable so that they can be easily used by
JavaBean components.
Creating an ImageIcon
There are nine constructors for an ImageIcon:
public ImageIcon()
Icon icon = new ImageIcon();
icon.setImage(anImage);
public ImageIcon(Image image)
Icon icon = new ImageIcon(anImage);
public ImageIcon(String filename)
Icon icon = new ImageIcon(filename);
public ImageIcon(URL location)
Icon icon = new ImageIcon(url);
public ImageIcon(byte imageData[])
Icon icon = new ImageIcon(aByteArray);
public ImageIcon(Image image, String description)
Icon icon = new ImageIcon(anImage, "Duke");
public ImageIcon(String filename, String description)
Icon icon = new ImageIcon(filename, filename);
public ImageIcon(URL location, String description)
Icon icon = new ImageIcon(url, location.getFile());
public ImageIcon(byte imageData[], String description)
Icon icon = new ImageIcon(aByteArray, "Duke");
The no-argument version creates an uninitialized version (empty). The remaining eight
offer the ability to create an ImageIcon from an Image, byte array, file name String, or URL, with
or without a description.
Using an ImageIcon
Using an ImageIcon is as simple as using an Icon: just create the ImageIcon and associate it with
a component.
Icon icon = new ImageIcon("Warn.gif");
JLabel label3 = new JLabel("Warning", icon, JLabel.CENTER)
95
96
CHAPTER 4 ■ CORE SWING COMPONENTS
ImageIcon Properties
Table 4-10 shows the six properties of ImageIcon. The height and width of the ImageIcon are the
height and width of the actual Image object. The imageLoadStatus property represents the results of
the loading of the ImageIcon from the hidden MediaTracker, either MediaTracker.ABORTED,
MediaTracker.ERRORED, or MediaTracker.COMPLETE.
Table 4-10. ImageIcon Properties
Property Name
Data Type
Access
description
String
Read-write
iconHeight
int
Read-only
iconWidth
int
Read-only
image
Image
Read-write
imageLoadStatus
int
Read-only
imageObserver
ImageObserver
Read-write
Sometimes, it’s useful to use an ImageIcon to load an Image, and then just ask for the Image
object from the Icon.
ImageIcon imageIcon = new ImageIcon(...);
Image image = imageIcon.getImage();
There is one major problem with using ImageIcon objects: They don’t work when the
image and class file using the icon are both loaded in a JAR (Java archive) file, unless you explicitly
specify the full URL for the file within the JAR (jar:http://www.example.com/directory/
foo.jar!/com/example/image.gif). You can’t just specify the file name as a String and let the
ImageIcon find the file. You must manually get the image data first, and then pass the data
along to the ImageIcon constructor.
To help with loading images outside JAR files, Listing 4-4 shows an ImageLoader class that
provides a public static Image getImage(Class relativeClass, String filename) method.
You specify both the base class where the image file relative is found and the file name for the
image file. Then you just need to pass the Image object returned to the constructor of ImageIcon.
Listing 4-4. Image Loading Support Class
import java.awt.*;
import java.io.*;
public final class ImageLoader {
private ImageLoader() {
}
CHAPTER 4 ■ CORE SWING COMPONENTS
public static Image getImage(Class relativeClass, String filename) {
Image returnValue = null;
InputStream is = relativeClass.getResourceAsStream(filename);
if (is != null) {
BufferedInputStream bis = new BufferedInputStream(is);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
int ch;
while ((ch = bis.read()) != -1) {
baos.write(ch);
}
returnValue = Toolkit.getDefaultToolkit().createImage(baos.toByteArray());
} catch (IOException exception) {
System.err.println("Error loading: " + filename);
}
}
return returnValue;
}
}
Here’s how you use the helper class:
Image warnImage = ImageLoader.getImage(LabelJarSample.class, "Warn.gif");
Icon warnIcon = new ImageIcon(warnImage);
JLabel label2 = new JLabel(warnIcon);
■Tip Keep in mind that the Java platform supports GIF89A animated images.
GrayFilter Class
One additional class worth mentioning here is GrayFilter. Many of the Swing component
classes rely on this class to create a disabled version of an Image to be used as an Icon. The
components use the class automatically, but there might be times when you need an AWT
ImageFilter that does grayscales. You can convert an Image from normal to grayed out with
a call to the one useful method of the class: public static Image createDisabledImage
(Image image).
Image normalImage = ...
Image grayImage = GrayFilter.createDisabledImage(normalImage)
You can now use the grayed-out image as the Icon on a component:
Icon warningIcon = new ImageIcon(grayImage);
JLabel warningLabel = new JLabel(warningIcon);
97
98
CHAPTER 4 ■ CORE SWING COMPONENTS
AbstractButton Class
The AbstractButton class is an important Swing class that works behind the scenes as the
parent class of all the Swing button components, as shown at the top of Figure 4-1. The
JButton, described in the “JButton Class” section later in this chapter, is the simplest of the
subclasses. The remaining subclasses are described in later chapters.
Each of the AbstractButton subclasses uses the ButtonModel interface to store their
data model. The DefaultButtonModel class is the default implementation used. In addition,
you can group any set of AbstractButton objects into a ButtonGroup. Although this grouping
is most natural with the JRadioButton and JRadioButtonMenuItem components, any of the
AbstractButton subclasses will work.
AbstractButton Properties
Table 4-11 lists the 32 properties (with mnemonic listed twice) of AbstractButton shared by all its
subclasses. They allow you to customize the appearance of all the buttons.
Table 4-11. AbstractButton Properties
Property Name
Data Type
Access
action
Action
Read-write bound
actionCommand
String
Read-write
actionListeners
ActionListener[ ]
Read-only
borderPainted
boolean
Read-write bound
changeListeners
ChangeListener[ ]
Read-only
contentAreaFilled
boolean
Read-write bound
disabledIcon
Icon
Read-write bound
disabledSelectedIcon
Icon
Read-write bound
displayedMnemonicIndex
int
Read-write bound
enabled
boolean
Write-only
focusPainted
boolean
Read-write bound
horizontalAlignment
int
Read-write bound
horizontalTextPosition
int
Read-write bound
icon
Icon
Read-write bound
iconTextGap
int
Read-write bound
itemListeners
ItemListener[ ]
Read-only
layout
LayoutManager
Write-only
margin
Insets
Read-write bound
mnemonic
char
Read-write bound
CHAPTER 4 ■ CORE SWING COMPONENTS
Table 4-11. AbstractButton Properties (Continued)
Property Name
Data Type
Access
mnemonic
int
Write-only
model
ButtonModel
Read-write bound
multiClickThreshhold
long
Read-write
pressedIcon
Icon
Read-write bound
rolloverEnabled
boolean
Read-write bound
rolloverIcon
Icon
Read-write bound
rolloverSelectedIcon
Icon
Read-write bound
selected
boolean
Read-write
selectedIcon
Icon
Read-write bound
selectedObjects
Object[ ]
Read-only
text
String
Read-write bound
UI
ButtonUI
Read-write
verticalAlignment
int
Read-write bound
verticalTextPosition
int
Read-write bound
■Note AbstractButton has a deprecated label property. You should use the equivalent text
property instead.
One property worth mentioning is multiClickThreshhold. This property represents a time,
in milliseconds. If a button is selected with a mouse multiple times within this time period,
additional action events won’t be generated. By default, the value is zero, meaning each press
generates an event. To avoid accidental duplicate submissions from happening in important
dialogs, set this value to some reasonable level above zero.
■Tip Keep in mind that all AbstractButton children can use HTML with its text property to display
HTML content within the label. Just prefix the property setting with the string .
ButtonModel/Class DefaultButtonModel Interface
The ButtonModel interface is used to describe the current state of the AbstractButton component.
In addition, it describes the set of event listeners objects that are supported by all the different
AbstractButton children. Its definition follows:
99
100
CHAPTER 4 ■ CORE SWING COMPONENTS
public interface ButtonModel extends ItemSelectable {
// Properties
public String getActionCommand();
public void setActionCommand(String newValue);
public boolean isArmed();
public void setArmed(boolean newValue);
public boolean isEnabled();
public void setEnabled(boolean newValue);
public void setGroup(ButtonGroup newValue);
public int getMnemonic();
public void setMnemonic(int newValue);
public boolean isPressed();
public void setPressed(boolean newValue);
public boolean isRollover();
public void setRollover(boolean newValue);
public boolean isSelected();
public void setSelected(boolean newValue);
// Listeners
public void addActionListener(ActionListener listener);
public void removeActionListener(ActionListener listener);
public void addChangeListener(ChangeListener listener);
public void removeChangeListener(ChangeListener listener);
public void addItemListener(ItemListener listener);
public void removeItemListener(ItemListener listener);
}
The specific implementation of ButtonModel you’ll use, unless you create your own, is the
DefaultButtonModel class. The DefaultButtonModel class defines all the event registration
methods for the different event listeners and manages the button state and grouping within
a ButtonGroup. Its set of nine properties is shown in Table 4-12. They all come from the
ButtonGroup interface, except selectedObjects, which is new to the DefaultButtonModel class,
but more useful to the JToggleButton.ToggleButtonModel, which is discussed in Chapter 5.
Table 4-12. DefaultButtonModel Properties
Property Name
Data Type
Access
actionCommand
String
Read-write
armed
boolean
Read-write
enabled
boolean
Read-write
group
ButtonGroup
Read-write
mnemonic
int
Read-write
pressed
boolean
Read-write
rollover
boolean
Read-write
selected
boolean
Read-write
selectedObjects
Object[ ]
Read-only
CHAPTER 4 ■ CORE SWING COMPONENTS
Most of the time, you don’t access the ButtonModel directly. Instead, the components that
use the ButtonModel wrap their property calls to update the model’s properties.
■Note The DefaultButtonModel also lets you get the listeners for a specific type with public
EventListener[ ] getListeners(Class listenerType).
Understanding AbstractButton Mnemonics
A mnemonic is a special keyboard accelerator that when pressed causes a particular action to
happen. In the case of the JLabel discussed earlier in the “JLabel Class” section, pressing the
displayed mnemonic causes the associated component to get the input focus. In the case of an
AbstractButton, pressing the mnemonic for a button causes its selection.
The actual pressing of the mnemonic requires the pressing of a look-and-feel–specific
hotkey (the key tends to be the Alt key). So, if the mnemonic for a button were the B key, you
would need to press Alt-B to activate the button with the B-key mnemonic. When the button is
activated, registered listeners will be notified of appropriate state changes. For instance, with
the JButton, all ActionListener objects would be notified.
If the mnemonic key is part of the text label for the button, you’ll see the character underlined. This does depend on the current look and feel and could be displayed differently. In
addition, if the mnemonic isn’t part of the text label, there will not be a visual indicator for
selecting the particular mnemonic key, unless the look and feel shows it in the tooltip text.
Figure 4-11 shows two buttons: one with a W-key mnemonic, and the other with an H-key
mnemonic. The left button has a label with W in its contents, so it shows the first W underlined.
The second component doesn’t benefit from this behavior on the button, but in the Ocean look
and feel, identifies it only if the tooltip text is set and shown.
Figure 4-11. AbstractButton mnemonics
To assign a mnemonic to an abstract button, you can use either one of the setMnemonic()
methods. One accepts a char argument and the other an int. Personally, I prefer the int variety,
in which the value is one of the many VK_* constants from the KeyEvent class. You can also
specify the mnemonic by position via the displayedMnemonicIndex property.
AbstractButton button1 = new JButton("Warning");
button1.setMnemonic(KeyEvent.VK_W);
content.add(button1);
101
102
CHAPTER 4 ■ CORE SWING COMPONENTS
Understanding AbstractButton Icons
AbstractButton has seven specific icon properties. The natural or default icon is the icon property. It is used for all cases unless a different icon is specified or there is a default behavior
provided by the component. The selectedIcon property is the icon used when the button is
selected. The pressedIcon is used when the button is pressed. Which of these two icons is used
depends on the component, because a JButton is pressed but not selected, whereas a
JCheckBox is selected but not pressed.
The disabledIcon and disabledSelectedIcon properties are used when the button has
been disabled with setEnabled(false). By default, if the icon is an ImageIcon, a grayscaled
version of the icon will be used.
The remaining two icon properties, rolloverIcon and rolloverSelectedIcon, allow you to
display different icons when the mouse moves over the button (and rolloverEnabled is true).
Understanding Internal AbstractButton Positioning
The horizontalAlignment, horizontalTextPosition, verticalAlignment, and
verticalTextPosition properties share the same settings and behavior as the JLabel class.
They’re listed in Table 4-13.
Table 4-13. AbstractButton Position Constants
Position Property
Available Settings
horizontalAlignment
LEFT, CENTER, RIGHT
horizontalTextPosition
LEFT, CENTER, RIGHT
verticalAlignment
TOP, CENTER, BOTTOM
verticalTextPosition
TOP, CENTER, BOTTOM
Handling AbstractButton Events
Although you do not create AbstractButton instances directly, you do create subclasses.
All of them share a common set of event-handling capabilities. You can register
PropertyChangeListener, ActionListener, ItemListener, and ChangeListener objects with
abstract buttons. The PropertyChangeListener object will be discussed here, and the
remaining objects listed will be discussed in later chapters, with the appropriate components.
Like the JComponent class, the AbstractButton component supports the registering of
PropertyChangeListener objects to detect when bound properties of an instance of the class
change. Unlike the JComponent class, the AbstractButton component provides the following set
of class constants to signify the different property changes:
• BORDER_PAINTED_CHANGED_PROPERTY
• CONTENT_AREA_FILLED_CHANGED_PROPERTY
• DISABLED_ICON_CHANGED_PROPERTY
• DISABLED_SELECTED_ICON_CHANGED_PROPERTY
• FOCUS_PAINTED_CHANGED_PROPERTY
CHAPTER 4 ■ CORE SWING COMPONENTS
• HORIZONTAL_ALIGNMENT_CHANGED_PROPERTY
• HORIZONTAL_TEXT_POSITION_CHANGED_PROPERTY
• ICON_CHANGED_PROPERTY
• MARGIN_CHANGED_PROPERTY
• MNEMONIC_CHANGED_PROPERTY
• MODEL_CHANGED_PROPERTY
• PRESSED_ICON_CHANGED_PROPERTY
• ROLLOVER_ENABLED_CHANGED_PROPERTY
• ROLLOVER_ICON_CHANGED_PROPERTY
• ROLLOVER_SELECTED_ICON_CHANGED_PROPERTY
• SELECTED_ICON_CHANGED_PROPERTY
• TEXT_CHANGED_PROPERTY
• VERTICAL_ALIGNMENT_CHANGED_PROPERTY
• VERTICAL_TEXT_POSITION_CHANGED_PROPERTY
Therefore, instead of hard-coding specific text strings, you can create a
PropertyChangeListener that uses these constants, as shown in Listing 4-5.
Listing 4-5. Base PropertyChangeListener for AbstractButton
import javax.swing.*;
import java.beans.*;
public class AbstractButtonPropertyChangeListener
implements PropertyChangeListener {
public void propertyChange(PropertyChangeEvent e) {
String propertyName = e.getPropertyName();
if (e.getPropertyName().equals(AbstractButton.TEXT_CHANGED_PROPERTY)) {
String newText = (String) e.getNewValue();
String oldText = (String) e.getOldValue();
System.out.println(oldText + " changed to " + newText);
} else if (e.getPropertyName().equals(AbstractButton.ICON_CHANGED_PROPERTY)) {
Icon icon = (Icon) e.getNewValue();
if (icon instanceof ImageIcon) {
System.out.println("New icon is an image");
}
}
}
}
103
104
CHAPTER 4 ■ CORE SWING COMPONENTS
JButton Class
The JButton component is the basic AbstractButton component that can be selected. It supports
text, images, and HTML-based labels, as shown in Figure 4-12.
Figure 4-12. Sample JButton components
Creating a JButton
The JButton class has five constructors:
public JButton()
JButton button = new JButton();
public JButton(Icon image)
Icon icon = new ImageIcon("dog.jpg");
JButton button = new JButton(icon);
public JButton(String text)
JButton button = new JButton("Dog");
public JButton(String text, Icon icon)
Icon icon = new ImageIcon("dog.jpg");
JButton button = new JButton("Dog", icon);
public JButton(Action action)
Action action = ...;
JButton button = new JButton(action);
You can create a button with or without a text label or icon. The icon represents the default
or selected icon property from AbstractButton.
■Note Creating a JButton from an Action initializes the text label, icon, enabled status, and tooltip text.
In addition, the ActionListener of the Action will be notified upon button selection.
CHAPTER 4 ■ CORE SWING COMPONENTS
JButton Properties
The JButton component doesn’t add much to the AbstractButton. As Table 4-14 shows, of the
four properties of JButton, the only new behavior added is enabling the button to be the default.
Table 4-14. JButton Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
defaultButton
boolean
Read-only
defaultCapable
boolean
Read-write bound
UIClassID
String
Read-only
The default button tends to be drawn with a different and darker border than the remaining
buttons. When a button is the default, pressing the Enter key while in the top-level window
causes the button to be selected. This works only as long as the component with the input
focus, such as a text component or another button, doesn’t consume the Enter key. Because
the defaultButton property is read-only, how (you might be asking) do you set a button as the
default? All top-level Swing windows contain a JRootPane, to be described in Chapter 8. You tell
this JRootPane which button is the default by setting its defaultButton property. Only buttons
whose defaultCapable property is true can be configured to be the default. Figure 4-13 shows
the top-right button set as the default.
Figure 4-13. Setting a default button
Listing 4-6 demonstrates setting the default button component, as well as using a basic
JButton. If the default button appearance doesn’t seem that obvious in Figure 4-13, wait until
the JOptionPane is described in Chapter 9, where the difference in appearance will be more
obvious. Figure 4-13 uses a 2-by-2 GridLayout for the screen. The extra two arguments to the
constructor represent gaps to help make the default button’s appearance more obvious.
105
106
CHAPTER 4 ■ CORE SWING COMPONENTS
Listing 4-6. Configuring a Default Button
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class DefaultButton {
public static void main(String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("DefaultButton");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridLayout(2, 2, 10, 10));
JButton button1 = new JButton("Text Button");
button1.setMnemonic(KeyEvent.VK_B);
frame.add(button1);
Icon warnIcon = new ImageIcon("Warn.gif");
JButton button2 = new JButton(warnIcon);
frame.add(button2);
JButton button3 = new JButton("Warning", warnIcon);
frame.add(button3);
String htmlButton = "HTMLButton " +
"Multi-line";
JButton button4 = new JButton(htmlButton);
frame.add(button4);
JRootPane rootPane = frame.getRootPane();
rootPane.setDefaultButton(button2);
frame.setSize(300, 200);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
Handling JButton Events
The JButton component itself has no specific event-handling capabilities. They’re all inherited
from AbstractButton. Although you can listen for change events, item events, and property
change events, the most helpful listener with the JButton is the ActionListener.
CHAPTER 4 ■ CORE SWING COMPONENTS
When the JButton component is selected, all registered ActionListener objects are notified.
When the button is selected, an ActionEvent is passed to each listener. This event passes along
the actionCommand property of the button to help identify which button was selected when
a shared listener is used across multiple components. If the actionCommand property hasn’t
been explicitly set, the current text property is passed along instead. The explicit use of the
actionCommand property is helpful with localization. Because the text property of the JButton is
what the user sees, you as the handler of the button selection event listener cannot rely on a
localized text label for determining which button was selected. So, while the text property can
be localized so that a Yes button in English can say Sí in a Spanish version, if you explicitly set
the actionCommand to be the "Yes" string, then no matter which language the user is running in,
the actionCommand will remain "Yes" and not take on the localized text property setting.
Listing 4-7 adds the event-handling capabilities to the default button example in Listing 4-6
(see Figure 4-13). Notice that the default button behavior works properly: press Enter from any
component, and button 2 (the default) will be activated.
Listing 4-7. Watching Button Selection Events
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ActionButtonSample {
public static void main(String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("DefaultButton");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ActionListener actionListener = new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
String command = actionEvent.getActionCommand();
System.out.println("Selected: " + command);
}
};
frame.setLayout(new GridLayout(2, 2, 10, 10));
JButton button1 = new JButton("Text Button");
button1.setMnemonic(KeyEvent.VK_B);
button1.setActionCommand("First");
button1.addActionListener(actionListener);
frame.add(button1);
107
108
CHAPTER 4 ■ CORE SWING COMPONENTS
Icon warnIcon = new ImageIcon("Warn.gif");
JButton button2 = new JButton(warnIcon);
button2.setActionCommand("Second");
button2.addActionListener(actionListener);
frame.add(button2);
JButton button3 = new JButton("Warning", warnIcon);
button3.setActionCommand("Third");
button3.addActionListener(actionListener);
frame.add(button3);
String htmlButton = "HTMLButton " +
"Multi-line";
JButton button4 = new JButton(htmlButton);
button4.setActionCommand("Fourth");
button4.addActionListener(actionListener);
frame.add(button4);
JRootPane rootPane = frame.getRootPane();
rootPane.setDefaultButton(button2);
frame.setSize(300, 200);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
Customizing a JButton Look and Feel
Each installable Swing look and feel provides a different JButton appearance and set of default
UIResource value settings. Figure 4-14 shows the appearance of the JButton component for the
preinstalled set of look and feel types: Motif, Windows, and Ocean.
Motif
Windows
Figure 4-14. JButton under different look and feel types
Ocean
CHAPTER 4 ■ CORE SWING COMPONENTS
The available set of UIResource-related properties for a JButton is shown in Table 4-15. For
the JButton component, there are 34 different properties.
Table 4-15. JButton UIResource Elements
Property String
Object Type
Button.actionMap
ActionMap
Button.background
Color
Button.border
Border
Button.contentAreaFilled
Boolean
Button.darkShadow
Color
Button.dashedRectGapHeight
Integer
Button.dashedRectGapWidth
Integer
Button.dashedRectGapX
Integer
Button.dashedRectGapY
Integer
Button.defaultButtonFollowsFocus
Boolean
Button.disabledForeground
Color
Button.disabledGrayRange
Integer[ ]
Button.disabledShadow
Color
Button.disabledText
Color
Button.disabledToolBarBorderBackground
Color
Button.focus
Color
Button.focusInputMap
InputMap
Button.font
Font
Button.foreground
Color
Button.gradient
List
Button.highlight
Color
Button.icon
Icon
Button.iconTextGap
Integer
Button.light
Color
Button.margin
Insets
Button.rollover
Boolean
Button.rolloverIconType
String
Button.select
Color
Button.shadow
Color
Button.showMnemonics
Boolean
109
110
CHAPTER 4 ■ CORE SWING COMPONENTS
Table 4-15. JButton UIResource Elements (Continued)
Property String
Object Type
Button.textIconGap
Integer
Button.textShiftOffset
Integer
Button.toolBarBorderBackground
Color
ButtonUI
String
JPanel Class
The last of the basic Swing components is the JPanel component. The JPanel component
serves as both a general-purpose container object, replacing the AWT Panel container, and a
replacement for the Canvas component, for those times when you need a drawable Swing
component area.
Creating a JPanel
There are four constructors for JPanel:
public JPanel()
JPanel panel = new JPanel();
public JPanel(boolean isDoubleBuffered)
JPanel panel = new JPanel(false);
public JPanel(LayoutManager manager)
JPanel panel = new JPanel(new GridLayout(2,2));
public JPanel(LayoutManager manager, boolean isDoubleBuffered)
JPanel panel = new JPanel(new GridLayout(2,2), false);
With the constructors, you can either change the default layout manager from FlowLayout
or change the default double buffering that is performed from true to false.
Using a JPanel
You can use JPanel as your general-purpose container or as a base class for a new component.
For the general-purpose container, the procedure is simple: Just create the panel, set its layout
manager if necessary, and add components using the add() method.
JPanel panel = new JPanel();
JButton okButton = new JButton("OK");
panel.add(okButton);
JButton cancelButton = new JButton("Cancel");
panel.add(cancelButton);
CHAPTER 4 ■ CORE SWING COMPONENTS
When you want to create a new component, subclass JPanel and override the public void
paintComponent(Graphics g) method. Although you can subclass JComponent directly, it seems
more appropriate to subclass JPanel. Listing 4-8 demonstrates a simple component that draws
an oval to fit the size of the component; it also includes a test driver.
Listing 4-8. Oval Panel Component
import java.awt.*;
import javax.swing.*;
public class OvalPanel extends JPanel {
Color color;
public OvalPanel() {
this(Color.black);
}
public OvalPanel(Color color) {
this.color = color;
}
public void paintComponent(Graphics g) {
int width = getWidth();
int height = getHeight();
g.setColor(color);
g.drawOval(0, 0, width, height);
}
public static void main(String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("Oval Sample");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new GridLayout(2, 2));
Color colors[] = {Color.RED, Color.BLUE, Color.GREEN, Color.YELLOW};
for (int i=0; i<4; i++) {
OvalPanel panel = new OvalPanel(colors[i]);
frame.add(panel);
}
frame.setSize(300, 200);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
111
112
CHAPTER 4 ■ CORE SWING COMPONENTS
Figure 4-15 shows the test driver program results.
Figure 4-15. The new OvalPanel component
■Note By default, JPanel components are opaque. This differs from JComponent, whose opacity
property setting by default is false. A false setting for opacity means the component is transparent.
Customizing a JPanel Look and Feel
The available set of UIResource-related properties for a JPanel is shown in Table 4-16. For the
JPanel component, there are five different properties. These settings may have an effect on the
components within the panel.
Table 4-16. JPanel UIResource Elements
Property String
Object Type
Panel.background
Color
Panel.border
Border
Panel.font
Font
Panel.foreground
Color
PanelUI
String
Summary
In this chapter, you explored the root of all Swing components: the JComponent class. From
there, you looked at some of the common elements of all components, such as tooltips, as well
as specific components such as JLabel. You also learned how to put glyphs (nonverbal images)
on components with the help of the Icon interface and the ImageIcon class, and the GrayFilter
image filter for disabled icons.
CHAPTER 4 ■ CORE SWING COMPONENTS
You also learned about the AbstractButton component, which serves as the root component for all Swing button objects. You looked at its data model interface, ButtonModel, and the
default implementation of this interface, DefaultButtonModel. Next, you looked at the JButton
class, which is the simplest of the AbstractButton implementations. And lastly, you looked at
the JPanel as the basic Swing container object.
In Chapter 5, you’ll start to dig into some of the more complex AbstractButton implementations: the toggle buttons.
113
CHAPTER 5
■■■
Toggle Buttons
N
ow that you’ve seen the capabilities of the relatively simple Swing components JLabel and
JButton, it’s time to take a look at more active components, specifically those that can be toggled.
These so-called toggleable components—JToggleButton, JCheckBox, and JRadioButton—provide
the means for your users to select from among a set of options. These options are either on or
off, or enabled or disabled. When presented in a ButtonGroup, only one of the options in the
group can be selected at a time. To deal with this selection state, the components share a common
data model with ToggleButtonModel. Let’s take a look at the data model, the components’
grouping mechanism with ButtonGroup, and the individual components.
ToggleButtonModel Class
The JToggleButton.ToggleButtonModel class is a public inner class of JToggleButton. The class
customizes the behavior of the DefaultButtonModel class, which, in turn, is an implementation
of the ButtonModel interface.
The customization affects the data models of all AbstractButton components in the
same ButtonGroup—a class explored next. In short, a ButtonGroup is a logical grouping of
AbstractButton components. At any one time, only one of the AbstractButton components
in the ButtonGroup can have the selected property of its data model set to true. The remaining
ones must be false. This does not mean that only one selected component in the group can
exist at a time. If multiple components in a ButtonGroup share a ButtonModel, multiple selected
components in the group can exist. If no components share a model, at most, the user can
select one component in the group. Once the user has selected that one component, the user
cannot interactively deselect the selection. However, programmatically, you can deselect all
group elements.
The definition of JToggleButton.ToggleButtonModel follows.
public class ToggleButtonModel extends DefaultButtonModel {
// Constructors
public ToggleButtonModel();
// Properties
public boolean isSelected();
public void setPressed(boolean newValue);
public void setSelected(boolean newvalue);
}
115
116
CHAPTER 5 ■ TOGGLE BUTTONS
The ToggleButtonModel class defines the default data model for both the JToggleButton
and its subclasses JCheckBox and JRadioButton, described in this chapter, as well as the
JCheckBoxMenuItem and JRadioButtonMenuItem classes described in Chapter 6.
■Note Internally, Swing’s HTML viewer component uses the ToggleButtonModel for its check box and
radio button input form elements.
ButtonGroup Class
Before describing the ButtonGroup class, let’s demonstrate its usage. The program shown in
Listing 5-1 creates objects that use the ToggleButtonModel and places them into a single group.
As the program demonstrates, in addition to adding the components into the screen’s container,
you must add each component to the same ButtonGroup. This results in a pair of add() method
calls for each component. Furthermore, the container for the button group tends to place
components in a single column and to label the grouping for the user with a titled border,
though neither of these treatments are required. Figure 5-1 shows the output of the program.
Listing 5-1. Odd Collection of Button Components
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
public class AButtonGroup {
public static void main(String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("Button Group");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new JPanel(new GridLayout(0, 1));
Border border =
BorderFactory.createTitledBorder("Examples");
panel.setBorder(border);
ButtonGroup group = new ButtonGroup();
AbstractButton abstract1 =
new JToggleButton("Toggle Button");
panel.add(abstract1);
group.add(abstract1);
AbstractButton abstract2 =
new JRadioButton("Radio Button");
panel.add(abstract2);
group.add(abstract2);
CHAPTER 5 ■ TOGGLE BUTTONS
AbstractButton abstract3 =
new JCheckBox("Check Box");
panel.add(abstract3);
group.add(abstract3);
AbstractButton abstract4 =
new JRadioButtonMenuItem("Radio Button Menu Item");
panel.add(abstract4);
group.add(abstract4);
AbstractButton abstract5 =
new JCheckBoxMenuItem("Check Box Menu Item");
panel.add(abstract5);
group.add(abstract5);
frame.add(panel, BorderLayout.CENTER);
frame.setSize(300, 200);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
Figure 5-1. ButtonGroup/ToggleButtonModel example
As previously stated, the ButtonGroup class represents a logical grouping of AbstractButton
components. The ButtonGroup is not a visual component; therefore, there’s nothing visual on
screen when a ButtonGroup is used. Any AbstractButton component can be added to the grouping
with public void add(AbstractButton abstractButton). Although any AbstractButton
component can belong to a ButtonGroup, only when the data model for the component is
ToggleButtonModel will the grouping have any effect. The result of having a component with a
data model of ToggleButtonModel in a ButtonGroup is that after the component is selected, the
ButtonGroup deselects any currently selected component in the group.
■Note Technically speaking, the model doesn’t need to be ToggleButtonModel as long as the custom
model exhibits the same behavior of limiting the number of selected component models to one.
117
118
CHAPTER 5 ■ TOGGLE BUTTONS
Although the add() method is typically the only ButtonGroup method you’ll ever need, the
following class definition shows that it’s not the only method of ButtonGroup in existence:
public class ButtonGroup implements Serializable {
// Constructor
public ButtonGroup();
// Properties
public int getButtonCount();
public Enumeration getElements();
public ButtonModel getSelection();
// Other methods
public void add(AbstractButton aButton);
public boolean isSelected(ButtonModel theModel) ;
public void remove(AbstractButton aButton);
public void setSelected(ButtonModel theModel, boolean newValue);
}
One interesting thing the class definition shows is that given a ButtonGroup, you cannot
directly find out the selected AbstractButton. You can directly ask only which ButtonModel is
selected. However, getElements() returns an Enumeration of all the AbstractButton elements in
the group. You can then loop through all the buttons to find the selected one (or ones) by using
code similar to the following:
Enumeration elements = group.getElements();
while (elements.hasMoreElements()) {
AbstractButton button = (AbstractButton)elements.nextElement();
if (button.isSelected()) {
System.out.println("The winner is: " + button.getText());
break; // Don't break if sharing models -- could show multiple buttons selected
}
}
The other interesting method of ButtonGroup is setSelected(). The two arguments of the
method are a ButtonModel and a boolean. If the boolean value is false, the selection request is
ignored. If the ButtonModel isn’t the model for a button in the ButtonGroup, then the ButtonGroup
deselects the currently selected model, causing no buttons in the group to be selected. The
proper usage of the method is to call the method with a model of a component in the group and
a new state of true. For example, if aButton is an AbstractButton and aGroup is the ButtonGroup,
then the method call would look like aGroup.setSelected(aButton.getModel(), true).
■Note If you add a selected button to a ButtonGroup that already has a previously selected button, the
previous button retains its state and the newly added button loses its selection.
Now, let’s look at the various components whose data model is the ToggleButtonModel.
CHAPTER 5 ■ TOGGLE BUTTONS
JToggleButton Class
The JToggleButton is the first of the toggleable components. It’s discussed first because it’s
the parent class of the two other components that are not menu-oriented: JCheckBox and
JRadioButton. The JToggleButton is like a JButton that stays depressed when selected, instead
of bouncing back to an unselected state. To deselect the selected component, you must reselect it.
JToggleButton isn’t a commonly used component, but you might find it useful on a toolbar,
such as in Microsoft Word (for paragraph alignment, among other instances) or in a file dialog
box, as shown in the upper-right corner of Figure 5-2.
*4OGGLE"UTTON
Figure 5-2. Sample JToggleButton components from file chooser
Defining the JToggleButton structure are two objects that customize the AbstractButton
parent class: ToggleButtonModel and ToggleButtonUI. The ToggleButtonModel class represents
a customized ButtonModel data model for the component, whereas ToggleButtonUI is the user
interface delegate.
Now that you know about the different pieces of a JToggleButton, let’s find out how to
use them.
Creating JToggleButton Components
Eight constructors are available for JToggleButton:
public JToggleButton()
JToggleButton aToggleButton = new JToggleButton();
public JToggleButton(Icon icon)
JToggleButton aToggleButton = new JToggleButton(new DiamondIcon(Color.PINK))
119
120
CHAPTER 5 ■ TOGGLE BUTTONS
public JToggleButton(Icon icon, boolean selected)
JToggleButton aToggleButton = new JToggleButton(new DiamondIcon(Color.PINK), true);
public JToggleButton(String text)
JToggleButton aToggleButton = new JToggleButton("Sicilian");
public JToggleButton(String text, boolean selected)
JToggleButton aToggleButton = new JToggleButton("Thin Crust", true);
public JToggleButton(String text, Icon icon)
JToggleButton aToggleButton = new JToggleButton("Thick Crust",
new DiamondIcon(Color.PINK));
public JToggleButton(String text, Icon icon, boolean selected)
JToggleButton aToggleButton = new JToggleButton("Stuffed Crust",
new DiamondIcon(Color.PINK), true);
public JToggleButton(Action action)
Action action = ...;
JToggleButton aToggleButton = new JToggleButton(action);
Each allows you to customize one or more of the label, icon, or initial selection state.
Unless specified otherwise, the label is empty with no text or icon, and the button initially is
not selected.
■Note Surprisingly, Swing lacks a constructor that accepts only an initial state of a boolean setting.
Lacking this constructor, you need to create a JToggleButton with the no-argument constructor variety,
and then call setSelected(boolean newValue) directly or work with an Action.
JToggleButton Properties
After creating a JToggleButton, you can modify each of its many properties. Although there are
about 100 inherited properties, Table 5-1 shows only the two introduced with JToggleButton.
The remaining properties come from AbstractButton, JComponent, Container, and Component.
Table 5-1. JToggleButton Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
UIClassID
String
Read-only
You can change one or more of the text, icon, or selected properties set in the constructor,
as well as any of the other AbstractButton properties described in Chapter 4. You configure
CHAPTER 5 ■ TOGGLE BUTTONS
the primary three properties with the appropriate getter and setter methods: get/setText(),
get/setIcon(), and is/setSelected(), or setAction(action). The other properties have corresponding getter and setter methods.
The more visual configurable options of JToggleButton (and its subclasses) include the
various icons for the different states of the button. Besides the standard icon, you can display
a different icon when the button is selected, among other state changes. However, if you’re
changing icons based on the currently selected state, then JToggleButton probably isn’t the
most appropriate component to use. You should use one of its subclasses, JCheckBox or
JRadioButton, explored later in this chapter.
■Note Keep in mind that the JButton component ignores the selectedIcon property.
Handling JToggleButton Selection Events
After configuring a JToggleButton, you can handle selection events in one of three ways: with
an ActionListener, an ItemListener, or a ChangeListener. This is in addition to providing an
Action to the constructor, which would be notified like an ActionListener.
Listening to JToggleButton Events with an ActionListener
If you’re interested only in what happens when a user selects or deselects the JToggleButton,
you can attach an ActionListener to the component. After the user selects the button, the
component notifies any registered ActionListener objects. Unfortunately, this isn’t the desired
behavior, because you must then actively determine the state of the button so that you can
respond appropriately for selecting or deselecting. To find out the selected state, you must get
the model for the event source, and then ask for its selection state, as the following sample
ActionListener source shows:
ActionListener actionListener = new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
AbstractButton abstractButton = (AbstractButton)actionEvent.getSource();
boolean selected = abstractButton.getModel().isSelected();
System.out.println("Action - selected=" + selected + "\ n");
}
};
Listening to JToggleButton Events with an ItemListener
The better listener to attach to a JToggleButton is the ItemListener. The ItemEvent passed to
the itemStateChanged() method of ItemListener includes the current selection state of the
button. This allows you to respond appropriately, without needing to search for the current
button state.
To demonstrate, the following ItemListener reports the state of a selected ItemEventgenerating component:
121
122
CHAPTER 5 ■ TOGGLE BUTTONS
ItemListener itemListener = new ItemListener() {
public void itemStateChanged(ItemEvent itemEvent) {
int state = itemEvent.getStateChange();
if (state == ItemEvent.SELECTED) {
System.out.println("Selected");
} else {
System.out.println("Deselected");
}
}
};
Listening to JToggleButton Events with a ChangeListener
Attaching a ChangeListener to a JToggleButton provides even more flexibility. Any attached
listener will be notified of the data model changes for the button, corresponding to changes in
its armed, pressed, and selected properties. Listening for notification from the three listeners—
ActionListener, ItemListener, and ChangeListener—allows you to react seven different times.
Figure 5-3 shows the sequencing of the ButtonModel property changes, and when the model
notifies each of the listeners.
Figure 5-3. JToggleButton notification sequencing diagram
To demonstrate the ChangeListener notifications, the following code fragment defines a
ChangeListener that reports the state changes to the three properties of the button model:
ChangeListener changeListener = new ChangeListener() {
public void stateChanged(ChangeEvent changeEvent) {
AbstractButton abstractButton = (AbstractButton)changeEvent.getSource();
ButtonModel buttonModel = abstractButton.getModel();
boolean armed = buttonModel.isArmed();
boolean pressed = buttonModel.isPressed();
boolean selected = buttonModel.isSelected();
System.out.println("Changed: " + armed + "/" + pressed + "/" + selected);
}
};
CHAPTER 5 ■ TOGGLE BUTTONS
After you attach the ChangeListener to a JToggleButton and select the component by
pressing and releasing the mouse over the component, the following output results:
Changed:
Changed:
Changed:
Changed:
Changed:
true/false/false
true/true/false
true/true/true
true/false/true
false/false/true
With all three listeners attached to the same button, notification of registered ItemListener
objects would happen after the selected property changes—in other words, between lines 3
and 4. Listing 5-2 demonstrates all three listeners attached to the same JToggleButton. With
regard to the registered ActionListener objects, notification happens after releasing the button,
but before the armed state changes to false, falling between lines 4 and 5.
Listing 5-2. Listening for Toggle Selection
import
import
import
import
javax.swing.*;
javax.swing.event.*;
java.awt.*;
java.awt.event.*;
public class SelectingToggle {
public static void main(String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("Selecting Toggle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JToggleButton toggleButton = new JToggleButton("Toggle Button");
// Define ActionListener
ActionListener actionListener = new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
AbstractButton abstractButton = (AbstractButton)actionEvent.getSource();
boolean selected = abstractButton.getModel().isSelected();
System.out.println("Action - selected=" + selected + "\n");
}
};
// Define ChangeListener
ChangeListener changeListener = new ChangeListener() {
public void stateChanged(ChangeEvent changeEvent) {
AbstractButton abstractButton = (AbstractButton)changeEvent.getSource();
ButtonModel buttonModel = abstractButton.getModel();
boolean armed = buttonModel.isArmed();
boolean pressed = buttonModel.isPressed();
boolean selected = buttonModel.isSelected();
System.out.println("Changed: " + armed + "/" + pressed + "/" +
selected);
}
};
123
124
CHAPTER 5 ■ TOGGLE BUTTONS
// Define ItemListener
ItemListener itemListener = new ItemListener() {
public void itemStateChanged(ItemEvent itemEvent) {
int state = itemEvent.getStateChange();
if (state == ItemEvent.SELECTED) {
System.out.println("Selected");
} else {
System.out.println("Deselected");
}
}
};
// Attach Listeners
toggleButton.addActionListener(actionListener);
toggleButton.addChangeListener(changeListener);
toggleButton.addItemListener(itemListener);
frame.add(toggleButton, BorderLayout.NORTH);
frame.setSize(300, 125);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
Customizing a JToggleButton Look and Feel
Each installable Swing look and feel provides a different JToggleButton appearance and set of
default UIResource values. Figure 5-4 shows the appearance of the JToggleButton component
for the preinstalled set of look and feel types: Motif, Windows, and Ocean. As the button labels
might indicate, the first button is selected, the second has the input focus (and isn’t selected),
and the third button isn’t selected.
-OTIF
7INDOWS
/CEAN
Figure 5-4. JToggleButton under different look and feel types
CHAPTER 5 ■ TOGGLE BUTTONS
The available set of UIResource-related properties for a JToggleButton is shown in Table 5-2.
The JToggleButton component has 17 different properties.
Table 5-2. JToggleButton UIResource Elements
Property String
Object Type
ToggleButton.background
Color
ToggleButton.border
Border
ToggleButton.darkShadow
Color
ToggleButton.disabledText
Color
ToggleButton.focus
Color
ToggleButton.focusInputMap
Object[ ]
ToggleButton.font
Font
ToggleButton.foreground
Color
ToggleButton.gradient
List
ToggleButton.highlight
Color
ToggleButton.light
Color
ToggleButton.margin
Insets
ToggleButton.select
Color
ToggleButton.shadow
Color
ToggleButton.textIconGap
Integer
ToggleButton.textShiftOffset
Integer
ToggleButtonUI
String
JCheckBox Class
The JCheckBox class represents the toggle component that, by default, displays a check box
icon next to the text label for a two-state option. The check box icon uses an optional check
mark to show the current state of the object, instead of keeping the button depressed, as with
the JToggleButton. With the JCheckBox, the icon shows the state of the object, whereas with the
JToggleButton, the icon is part of the label and isn’t usually used to show state information.
With the exception of the UI-related differences between JCheckBox and JToggleButton, the
two components are identical. Figure 5-5 demonstrates how check box components might
appear in a pizza-ordering application.
125
126
CHAPTER 5 ■ TOGGLE BUTTONS
Figure 5-5. Sample JCheckBox components
The JCheckBox is made up of several pieces. Like JToggleButton, the JCheckBox uses a
ToggleButtonModel to represent its data model. The user interface delegate is CheckBoxUI.
Although the ButtonGroup is available to group together check boxes, it isn’t normally appropriate.
When multiple JCheckBox components are within a ButtonGroup, they behave like JRadioButton
components but look like JCheckBox components. Because of this visual irregularity, you
shouldn’t put JCheckBox components into a ButtonGroup.
Now that you’ve seen the different pieces of a JCheckBox, let’s find out how to use them.
Creating JCheckBox Components
Eight constructors exist for JCheckBox:
public JCheckBox()
JCheckBox aCheckBox = new JCheckBox();
public JCheckBox(Icon icon)
JCheckBox aCheckBox = new JCheckBox(new DiamondIcon(Color.RED, false));
aCheckBox.setSelectedIcon(new DiamondIcon(Color.PINK, true));
public JCheckBox(Icon icon, boolean selected)
JCheckBox aCheckBox = new JCheckBox(new DiamondIcon(Color.RED, false), true);
aCheckBox.setSelectedIcon(new DiamondIcon(Color.PINK, true));
public JCheckBox(String text)
JCheckBox aCheckBox = new JCheckBox("Spinach");
public JCheckBox(String text, boolean selected)
JCheckBox aCheckBox = new JCheckBox("Onions", true);
public JCheckBox(String text, Icon icon)
JCheckBox aCheckBox = new JCheckBox("Garlic", new DiamondIcon(Color.RED, false));
aCheckBox.setSelectedIcon(new DiamondIcon(Color.PINK, true));
CHAPTER 5 ■ TOGGLE BUTTONS
public JCheckBox(String text, Icon icon, boolean selected)
JCheckBox aCheckBox = new JCheckBox("Anchovies", new DiamondIcon(Color.RED,
false), true);
aCheckBox.setSelectedIcon(new DiamondIcon(Color.PINK, true));
public JCheckBox(Action action)
Action action = ...;
JCheckBox aCheckBox = new JCheckBox(action);
■Note Configuring a JCheckBox from an Action sets the label, state, and tooltip text, but not the icon.
Each allows you to customize either none or up to three properties for the label, icon, or
initial selection state. Unless specified otherwise, there’s no text in the label and the default
selected/unselected icon for the check box appears unselected.
If you do initialize the icon in the constructor, it’s the icon for the unselected state of the
check box, with the same icon displayed when the check box is selected. You must also either
initialize the selected icon with the setSelectedIcon(Icon newValue) method, described later,
or make sure the icon is state-aware and updates itself. If you don’t configure the selected icon
and don’t use a state-aware icon, the same icon will appear for both the selected and unselected
state. Normally, an icon that doesn’t change its visual appearance between selected and unselected states isn’t desirable for a JCheckBox.
■Note A state-aware icon is one that asks the associated component for the value of the selected
property.
JCheckBox Properties
After creating a JCheckBox, you can modify each of its many properties. Two properties specific
to JCheckBox (shown in Table 5-3) override the behavior of its parent JToggleButton. The third
borderPaintedFlat property was introduced in the 1.3 release of the JDK. All the remaining
properties are inherited through parents of JToggleButton.
Table 5-3. JCheckBox Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
borderPaintedFlat
boolean
Read-write bound
UIClassID
String
Read-only
127
128
CHAPTER 5 ■ TOGGLE BUTTONS
The borderPaintedFlat property permits a look and feel to display the border around
the check icon as two-dimensional (flat) instead of three-dimensional. By default, the
borderPaintedFlat property is false, meaning the border will be three-dimensional. Figure 5-6
shows what a flat border looks like, where the first, third, and fifth borders are flat, and the
second and fourth are not. A look and feel may choose to ignore this property. However, it is
useful for renderers for components such tables and trees, where they show only state and are
not selectable. The Windows and Motif look and feel types take advantage of the property;
Metal (and Ocean) does not.
Figure 5-6. Alternating flat JCheckBox borders for the Windows look and feel: Anchovies, Onions,
and Spinach are flat; Garlic and Pepperoni are not.
As the constructor listing demonstrated, if you choose to set an icon with a constructor,
the constructor sets only one icon for the unselected state. If you want the check box icon to
show the correct state visually, you must use a state-aware icon or associate a different icon for
the selected state with setSelectedIcon(). Having two different visual state representations is
what most users expect from a JCheckBox, so unless you have a good reason to do otherwise, it’s
best to follow the design convention for normal user interfaces.
The fourth button at the bottom of the screen shown in Figure 5-7 demonstrates confusing
icon usage within a JCheckBox. The check box always appears selected. The figure displays what
the screen looks like with Pizza selected, Calzone unselected, Anchovies unselected, and Stuffed
Crust unselected (although the last one appears selected).
Figure 5-7. Multiple JCheckBox components with various icons
CHAPTER 5 ■ TOGGLE BUTTONS
Listing 5-3 demonstrates three valid means of creating JCheckBox components with
different icons, one using a state-aware icon. The last check box shows bad icon usage.
Listing 5-3. Sampling JCheckBox
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class IconCheckBoxSample {
private static class CheckBoxIcon implements Icon {
private ImageIcon checkedIcon = new ImageIcon("Plus.gif");
private ImageIcon uncheckedIcon = new ImageIcon("Minus.gif");
public void paintIcon(Component component, Graphics g, int x, int y) {
AbstractButton abstractButton = (AbstractButton)component;
ButtonModel buttonModel = abstractButton.getModel();
g.translate(x,y);
ImageIcon imageIcon = buttonModel.isSelected() ?
checkedIcon : uncheckedIcon;
Image image = imageIcon.getImage();
g.drawImage(image, 0, 0, component);
g.translate(-x,-y);
}
public int getIconWidth() {
return 20;
}
public int getIconHeight() {
return 20;
}
}
public static void main(String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("Iconizing CheckBox");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Icon checked = new DiamondIcon (Color.BLACK, true);
Icon unchecked = new DiamondIcon (Color.BLACK, false);
JCheckBox aCheckBox1 = new JCheckBox("Pizza", unchecked);
aCheckBox1.setSelectedIcon(checked);
JCheckBox aCheckBox2 = new JCheckBox("Calzone");
aCheckBox2.setIcon(unchecked);
aCheckBox2.setSelectedIcon(checked);
129
130
CHAPTER 5 ■ TOGGLE BUTTONS
Icon checkBoxIcon = new CheckBoxIcon();
JCheckBox aCheckBox3 = new JCheckBox("Anchovies", checkBoxIcon);
JCheckBox aCheckBox4 = new JCheckBox("Stuffed Crust", checked);
frame.setLayout(new GridLayout(0,1));
frame.add(aCheckBox1);
frame.add(aCheckBox2);
frame.add(aCheckBox3);
frame.add(aCheckBox4);
frame.setSize(300, 200);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
Handling JCheckBox Selection Events
As with the JToggleButton, you can handle JCheckBox events in any one of three ways: with an
ActionListener, an ItemListener, or a ChangeListener. The constructor that accepts an Action
just adds the parameter as an ActionListener.
Listening to JCheckBox Events with an ActionListener
Subscribing to ActionEvent generation with an ActionListener allows you to find out when the
user toggles the state of the JCheckBox. As with JToggleButton, the subscribed listener is told of
the selection, but not the new state. To find out the selected state, you must get the model for the
event source and ask, as the following sample ActionListener source shows. This listener
modifies the check box label to reflect the selection state.
ActionListener actionListener = new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
AbstractButton abstractButton = (AbstractButton)actionEvent.getSource();
boolean selected = abstractButton.getModel().isSelected();
String newLabel = (selected ? SELECTED_LABEL : DESELECTED_LABEL);
abstractButton.setText(newLabel);
}
};
Listening to JCheckBox Events with an ItemListener
For JCheckBox, as with JToggleButton, the better listener to subscribe to is an ItemListener. The
ItemEvent passed to the itemStateChanged() method of ItemListener includes the current
state of the check box. This allows you to respond appropriately, without need to find out the
current button state.
To demonstrate, the following ItemListener swaps the foreground and background colors
based on the state of a selected component. In this ItemListener, the foreground and background colors are swapped only when the state is selected.
CHAPTER 5 ■ TOGGLE BUTTONS
ItemListener itemListener = new ItemListener() {
public void itemStateChanged(ItemEvent itemEvent) {
AbstractButton abstractButton = (AbstractButton)itemEvent.getSource();
Color foreground = abstractButton.getForeground();
Color background = abstractButton.getBackground();
int state = itemEvent.getStateChange();
if (state == ItemEvent.SELECTED) {
abstractButton.setForeground(background);
abstractButton.setBackground(foreground);
}
}
};
Listening to JCheckBox Events with a ChangeListener
The ChangeListener responds to the JCheckBox just as with the JToggleButton. A subscribed
ChangeListener would be notified when the button is armed, pressed, selected, or released.
In addition, the ChangeListener is also notified of changes to the ButtonModel, such as for the
keyboard mnemonic (KeyEvent.VK_S) of the check box. Because there are no ChangeListener
differences to demonstrate between a JToggleButton and a JCheckBox, you could just attach the
same listener from JToggleButton to the JCheckBox, and you’ll get the same selection responses.
The sample program in Listing 5-4 demonstrates all the listeners subscribed to the events
of a single JCheckBox. To demonstrate that the ChangeListener is notified of changes to other
button model properties, a keyboard mnemonic is associated with the component. Given that
the ChangeListener is registered before the mnemonic property is changed, the ChangeListener
is notified of the property change. Because the foreground and background colors and text
label aren’t button model properties, the ChangeListener isn’t told of these changes made by
the other listeners.
■Note If you did want to listen for changes to the foreground or background color properties, you would
need to attach a PropertyChangeListener to the JCheckBox.
Listing 5-4. Listening for JCheckBox Selection
import
import
import
import
javax.swing.*;
javax.swing.event.*;
java.awt.*;
java.awt.event.*;
public class SelectingCheckBox {
private static String DESELECTED_LABEL = "Deselected";
private static String SELECTED_LABEL = "Selected";
131
132
CHAPTER 5 ■ TOGGLE BUTTONS
public static void main(String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("Selecting CheckBox");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JCheckBox checkBox = new JCheckBox(DESELECTED_LABEL);
// Define ActionListener
ActionListener actionListener = new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
AbstractButton abstractButton =
(AbstractButton)actionEvent.getSource();
boolean selected = abstractButton.getModel().isSelected();
String newLabel = (selected ? SELECTED_LABEL : DESELECTED_LABEL);
abstractButton.setText(newLabel);
}
};
// Define ChangeListener
ChangeListener changeListener = new ChangeListener() {
public void stateChanged(ChangeEvent changeEvent) {
AbstractButton abstractButton =
(AbstractButton)changeEvent.getSource();
ButtonModel buttonModel = abstractButton.getModel();
boolean armed = buttonModel.isArmed();
boolean pressed = buttonModel.isPressed();
boolean selected = buttonModel.isSelected();
System.out.println("Changed: " + armed + "/" + pressed + "/" +
selected);
}
};
// Define ItemListener
ItemListener itemListener = new ItemListener() {
public void itemStateChanged(ItemEvent itemEvent) {
AbstractButton abstractButton =
(AbstractButton)itemEvent.getSource();
Color foreground = abstractButton.getForeground();
Color background = abstractButton.getBackground();
int state = itemEvent.getStateChange();
if (state == ItemEvent.SELECTED) {
abstractButton.setForeground(background);
abstractButton.setBackground(foreground);
}
}
};
// Attach Listeners
checkBox.addActionListener(actionListener);
checkBox.addChangeListener(changeListener);
checkBox.addItemListener(itemListener);
CHAPTER 5 ■ TOGGLE BUTTONS
checkBox.setMnemonic(KeyEvent.VK_S);
frame.add(checkBox, BorderLayout.NORTH);
frame.setSize(300, 100);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
The SelectingCheckBox class produces the screen shown in Figure 5-8, after selecting and
deselecting the JCheckBox.
Figure 5-8. SelectingCheckBox program screen
Customizing a JCheckBox Look and Feel
Each installable Swing look and feel provides a different JCheckBox appearance and set of
default UIResource values. Figure 5-9 shows the appearance of the JCheckBox component for
the preinstalled set of look and feel types: Motif, Windows, and Ocean. The first, third, and fifth
check boxes are selected; the third has the input focus.
-OTIF
7INDOWS
/CEAN
Figure 5-9. JCheckBox under different look and feel types
133
134
CHAPTER 5 ■ TOGGLE BUTTONS
Table 5-4 shows the set of available UIResource-related properties for a JCheckBox. The
JCheckBox component has 20 different properties.
Table 5-4. JCheckBox UIResource Elements
Property String
Object Type
CheckBox.background
Color
CheckBox.border
Border
CheckBox.darkShadow
Color
CheckBox.disabledText
Color
CheckBox.focus
Color
CheckBox.focusInputMap
Object[ ]
CheckBox.font
Font
CheckBox.foreground
Color
CheckBox.gradient
List
CheckBox.highlight
Color
CheckBox.icon
Icon
CheckBox.interiorBackground
Color
CheckBox.light
Color
CheckBox.margin
Insets
CheckBox.rollover
Boolean
Checkbox.select*
Color
CheckBox.shadow
Color
CheckBox.textIconGap
Integer
CheckBox.textShiftOffset
Integer
CheckBoxUI
String
* Lowercase b is correct.
JRadioButton Class
You use JRadioButton when you want to create a mutually exclusive group of toggleable
components. Although, technically speaking, you could place a group of JCheckBox components
into a ButtonGroup and only one would be selectable at a time, they wouldn’t look quite right.
At least with the predefined look and feel types, JRadioButton and JCheckBox components look
different, as Figure 5-10 shows. This difference in appearance tells the end user to expect
specific behavior from the components.
CHAPTER 5 ■ TOGGLE BUTTONS
Figure 5-10. Comparing JRadioButton to JCheckBox appearance
The JRadioButton is made up of several pieces. Like JToggleButton and JCheckBox, the
JRadioButton uses a ToggleButtonModel to represent its data model. It uses a ButtonGroup
through AbstractButton to provide the mutually exclusive grouping, and the user interface
delegate is the RadioButtonUI.
Let’s now explore how to use the different pieces of a JRadioButton.
Creating JRadioButton Components
As with JCheckBox and JToggleButton, there are eight constructors for JRadioButton:
public JRadioButton()
JRadioButton aRadioButton = new JRadioButton();
public JRadioButton(Icon icon)
JRadioButton aRadioButton = new JRadioButton(new DiamondIcon(Color.CYAN, false));
aRadioButton.setSelectedIcon(new DiamondIcon(Color.BLUE, true));
public JRadioButton(Icon icon, boolean selected)
JRadioButton aRadioButton = new JRadioButton(new DiamondIcon(Color.CYAN, false),
true);
aRadioButton.setSelectedIcon(new DiamondIcon(Color.BLUE, true));
public JRadioButton(String text)
JRadioButton aRadioButton = new JRadioButton("4 slices");
public JRadioButton(String text, boolean selected)
JRadioButton aRadioButton = new JRadioButton("8 slices", true);
public JRadioButton(String text, Icon icon)
JRadioButton aRadioButton = new JRadioButton("12 slices",
new DiamondIcon(Color.CYAN, false));
aRadioButton.setSelectedIcon(new DiamondIcon(Color.BLUE, true));
public JRadioButton(String text, Icon icon, boolean selected)
JRadioButton aRadioButton = new JRadioButton("16 slices",
new DiamondIcon(Color.CYAN, false), true);
aRadioButton.setSelectedIcon(new DiamondIcon(Color.BLUE, true));
135
136
CHAPTER 5 ■ TOGGLE BUTTONS
public JRadioButton(Action action)
Action action = ...;
JRadioButton aRadioButton = new JRadioButton(action);
■Note As with a JCheckBox, configuring a JRadioButton from an Action sets the label, state, and
tooltip text, but not the icon.
Each allows you to customize one or more of the label, icon, or initial selection state properties. Unless specified otherwise, there’s no text in the label, and the default selected/unselected
icon for the check box appears unselected. After creating a group of radio button components,
you need to place each into a single ButtonGroup so that they work as expected, with only
one button in the group selectable at a time. If you do initialize the icon in the constructor,
it’s the icon for the unselected state of the check box, with the same icon displayed when
the check box is selected. You must also either initialize the selected icon with the
setSelectedIcon(Icon newValue) method, described with JCheckBox, or make sure the icon
is state-aware and updates itself.
JRadioButton Properties
JRadioButton has two properties that override the behavior of its parent JToggleButton, as
listed in Table 5-5.
Table 5-5. JRadioButton Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
UIClassID
String
Read-only
Grouping JRadioButton Components in a ButtonGroup
The JRadioButton is the only JToggleButton subclass that should be placed in a ButtonGroup
in order to work properly. Merely creating a bunch of radio buttons and placing them on the
screen isn’t enough to make them behave appropriately. In addition to adding each radio
button to a container, you need to create a ButtonGroup and add each radio button to the same
ButtonGroup. Once all the JRadioButton items are in a group, whenever an unselected radio
button is selected, the ButtonGroup causes the currently selected radio button to be deselected.
Placing a set of JRadioButton components within a ButtonGroup on the screen is basically
a four-step process:
1. Create a container for the group.
JPanel aPanel = new JPanel(new GridLayout(0, 1));
CHAPTER 5 ■ TOGGLE BUTTONS
■Note The Box class described in Chapter 11 serves as a good container for a group of JRadioButton
components.
2. Place a border around the container, to label the grouping. This is an optional step, but
you’ll frequently want to add a border to label the group for the user. You can read more
about borders in Chapter 7.
Border border = BorderFactory.createTitledBorder("Slice Count");
aPanel.setBorder(border);
3. Create a ButtonGroup.
ButtonGroup aGroup = new ButtonGroup();
4. For each selectable option, create a JRadioButton, add it to a container, and then add it
to the group.
JRadioButton aRadioButton = new JRadioButton(...);
aPanel.add(aRadioButton);
aGroup.add(aRadioButton);
You might find the whole process, especially the fourth step, a bit tedious after a while,
especially when you add another step for handling selection events. The helper class shown
in Listing 5-5, with its static createRadioButtonGrouping(String elements[], String title)
method, could prove useful. It takes a String array for the radio button labels as well as the
border title, and then it creates a set of JRadioButton objects with a common ButtonGroup in a
JPanel with a titled border.
Listing 5-5. Initial Support Class for Working with JRadioButton
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
public class RadioButtonUtils {
private RadioButtonUtils() {
// Private constructor so you can't create instances
}
public static Container createRadioButtonGrouping (String elements[],
String title) {
JPanel panel = new JPanel(new GridLayout(0, 1));
// If title set, create titled border
if (title != null) {
Border border = BorderFactory.createTitledBorder(title);
panel.setBorder(border);
}
137
138
CHAPTER 5 ■ TOGGLE BUTTONS
// Create group
ButtonGroup group = new ButtonGroup();
JRadioButton aRadioButton;
// For each String passed in:
// Create button, add to panel, and add to group
for (int i=0, n=elements.length; i getSelectedElements(Container container) {
Vector selections = new Vector();
Component components[] = container.getComponents();
for (int i=0, n=components.length; i " + selected.nextElement());
}
}
};
JButton button = new JButton ("Order Pizza");
button.addActionListener(buttonActionListener);
It may be necessary for getSelectedElements() to return more than one value, because if
the same ButtonModel is shared by multiple buttons in the container, multiple components of the
ButtonGroup will be selected. Sharing a ButtonModel between components isn’t the norm. If
you’re sure your button model won’t be shared, then you may want to provide a similar
method that returns only a String.
141
142
CHAPTER 5 ■ TOGGLE BUTTONS
Listening to JRadioButton Events with an ItemListener
Depending on what you’re trying to do, using an ItemListener with a JRadioButton is usually
not the desired event-listening approach. When an ItemListener is registered, a new JRadioButton
selection notifies the listener twice: once for deselecting the old value and once for selecting
the new value. For reselections (selecting the same choice again), the listener is notified only once.
To demonstrate, the following listener will detect reselections, as the ActionListener did
earlier, and will report the selected (or deselected) element.
ItemListener itemListener = new ItemListener() {
String lastSelected;
public void itemStateChanged(ItemEvent itemEvent) {
AbstractButton aButton = (AbstractButton)itemEvent.getSource();
int state = itemEvent.getStateChange();
String label = aButton.getText();
String msgStart;
if (state == ItemEvent.SELECTED) {
if (label.equals(lastSelected)) {
msgStart = "Reselected -> ";
} else {
msgStart = "Selected -> ";
}
lastSelected = label;
} else {
msgStart = "Deselected -> ";
}
System.out.println(msgStart + label);
}
};
To work properly, some new methods will be needed for RadioButtonUtils to enable you
to attach the ItemListener to each JRadioButton in the ButtonGroup. They’re listed in the
following section with the source for the complete example.
Listening to JRadioButton Events with a ChangeListener
The ChangeListener responds to the JRadioButton just as it does with the JToggleButton and
JCheckBox. A subscribed listener is notified when the selected radio button is armed, pressed,
selected, or released and for various other properties of the button model. The only difference
with JRadioButton is that the ChangeListener is also notified of the state changes of the radio
button being deselected. The ChangeListener from the earlier examples could be attached to
the JRadioButton as well. It will just be notified more frequently.
The sample program shown in Listing 5-7 demonstrates all the listeners registered to the
events of two different JRadioButton objects. In addition, a JButton reports on the selected
elements of one of the radio buttons. Figure 5-12 shows the main window of the program.
CHAPTER 5 ■ TOGGLE BUTTONS
Listing 5-7. Radio Button Group Sample
import
import
import
import
import
javax.swing.*;
javax.swing.event.*;
java.awt.*;
java.awt.event.*;
java.util.Enumeration;
public class GroupActionRadio {
private static final String sliceOptions[] =
{"4 slices", "8 slices", "12 slices", "16 slices"};
private static final String crustOptions[] =
{"Sicilian", "Thin Crust", "Thick Crust", "Stuffed Crust"};
public static void main(String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("Grouping Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// Slice Parts
ActionListener sliceActionListener = new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
AbstractButton aButton = (AbstractButton)actionEvent.getSource();
System.out.println("Selected: " + aButton.getText());
}
};
Container sliceContainer =
RadioButtonUtils.createRadioButtonGrouping(sliceOptions,
"Slice Count", sliceActionListener);
// Crust Parts
ActionListener crustActionListener = new ActionListener() {
String lastSelected;
public void actionPerformed(ActionEvent actionEvent) {
AbstractButton aButton = (AbstractButton)actionEvent.getSource();
String label = aButton.getText();
String msgStart;
if (label.equals(lastSelected)) {
msgStart = "Reselected: ";
} else {
msgStart = "Selected: ";
}
lastSelected = label;
System.out.println(msgStart + label);
}
};
143
144
CHAPTER 5 ■ TOGGLE BUTTONS
ItemListener itemListener = new ItemListener() {
String lastSelected;
public void itemStateChanged(ItemEvent itemEvent) {
AbstractButton aButton = (AbstractButton)itemEvent.getSource();
int state = itemEvent.getStateChange();
String label = aButton.getText();
String msgStart;
if (state == ItemEvent.SELECTED) {
if (label.equals(lastSelected)) {
msgStart = "Reselected -> ";
} else {
msgStart = "Selected -> ";
}
lastSelected = label;
} else {
msgStart = "Deselected -> ";
}
System.out.println(msgStart + label);
}
};
ChangeListener changeListener = new ChangeListener() {
public void stateChanged(ChangeEvent changEvent) {
AbstractButton aButton = (AbstractButton)changEvent.getSource();
ButtonModel aModel = aButton.getModel();
boolean armed = aModel.isArmed();
boolean pressed = aModel.isPressed();
boolean selected = aModel.isSelected();
System.out.println("Changed: " + armed + "/" + pressed + "/" +
selected);
}
};
final Container crustContainer =
RadioButtonUtils.createRadioButtonGrouping(crustOptions,
"Crust Type", crustActionListener, itemListener, changeListener);
// Button Parts
ActionListener buttonActionListener = new ActionListener() {
public void actionPerformed(ActionEvent actionEvent) {
Enumeration selected =
RadioButtonUtils.getSelectedElements(crustContainer);
while (selected.hasMoreElements()) {
System.out.println ("Selected -> " + selected.nextElement());
}
}
};
CHAPTER 5 ■ TOGGLE BUTTONS
JButton button = new JButton ("Order Pizza");
button.addActionListener(buttonActionListener);
frame.add(sliceContainer, BorderLayout.WEST);
frame.add(crustContainer, BorderLayout.EAST);
frame.add(button, BorderLayout.SOUTH);
frame.setSize(300, 200);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
Figure 5-12. The GroupActionRadio program sample screen
A few more changes were made to the RadioButtonUtils class to deal with registering
ChangeListener objects to all the radio buttons in a ButtonGroup. The complete and final class
definition is shown in Listing 5-8.
Listing 5-8. Complete Support Class for Working with JRadioButton
import
import
import
import
import
import
import
javax.swing.*;
javax.swing.event.*;
javax.swing.border.*;
java.awt.*;
java.awt.event.*;
java.util.Enumeration;
java.util.Vector;
public class RadioButtonUtils {
private RadioButtonUtils() {
// Private constructor so you can't create instances
}
145
146
CHAPTER 5 ■ TOGGLE BUTTONS
public static Enumeration getSelectedElements(Container container) {
Vector selections = new Vector();
Component components[] = container.getComponents();
for (int i=0, n=components.length; iNew, N - Mnemonic
JMenuItem newMenuItem = new JMenuItem("New", KeyEvent.VK_N);
newMenuItem.addActionListener(menuListener);
fileMenu.add(newMenuItem);
// File->Open, O - Mnemonic
JMenuItem openMenuItem = new JMenuItem("Open", KeyEvent.VK_O);
openMenuItem.addActionListener(menuListener);
fileMenu.add(openMenuItem);
// File->Close, C - Mnemonic
JMenuItem closeMenuItem = new JMenuItem("Close", KeyEvent.VK_C);
closeMenuItem.addActionListener(menuListener);
fileMenu.add(closeMenuItem);
153
154
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
// Separator
fileMenu.addSeparator();
// File->Save, S - Mnemonic
JMenuItem saveMenuItem = new JMenuItem("Save", KeyEvent.VK_S);
saveMenuItem.addActionListener(menuListener);
fileMenu.add(saveMenuItem);
// Separator
fileMenu.addSeparator();
// File->Exit, X - Mnemonic
JMenuItem exitMenuItem = new JMenuItem("Exit", KeyEvent.VK_X);
exitMenuItem.addActionListener(menuListener);
fileMenu.add(exitMenuItem);
// Edit Menu, E - Mnemonic
JMenu editMenu = new JMenu("Edit");
editMenu.setMnemonic(KeyEvent.VK_E);
menuBar.add(editMenu);
// Edit->Cut, T - Mnemonic, CTRL-X - Accelerator
JMenuItem cutMenuItem = new JMenuItem("Cut", KeyEvent.VK_T);
cutMenuItem.addActionListener(menuListener);
KeyStroke ctrlXKeyStroke = KeyStroke.getKeyStroke("control X");
cutMenuItem.setAccelerator(ctrlXKeyStroke);
editMenu.add(cutMenuItem);
// Edit->Copy, C - Mnemonic, CTRL-C - Accelerator
JMenuItem copyMenuItem = new JMenuItem("Copy", KeyEvent.VK_C);
copyMenuItem.addActionListener(menuListener);
KeyStroke ctrlCKeyStroke = KeyStroke.getKeyStroke("control C");
copyMenuItem.setAccelerator(ctrlCKeyStroke);
editMenu.add(copyMenuItem);
// Edit->Paste, P - Mnemonic, CTRL-V - Accelerator, Disabled
JMenuItem pasteMenuItem = new JMenuItem("Paste", KeyEvent.VK_P);
pasteMenuItem.addActionListener(menuListener);
KeyStroke ctrlVKeyStroke = KeyStroke.getKeyStroke("control V");
pasteMenuItem.setAccelerator(ctrlVKeyStroke);
pasteMenuItem.setEnabled(false);
editMenu.add(pasteMenuItem);
// Separator
editMenu.addSeparator();
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
// Edit->Find, F - Mnemonic, F3 - Accelerator
JMenuItem findMenuItem = new JMenuItem("Find", KeyEvent.VK_F);
findMenuItem.addActionListener(menuListener);
KeyStroke f3KeyStroke = KeyStroke.getKeyStroke("F3");
findMenuItem.setAccelerator(f3KeyStroke);
editMenu.add(findMenuItem);
// Edit->Options Submenu, O - Mnemonic, at.gif - Icon Image File
JMenu findOptionsMenu = new JMenu("Options");
Icon atIcon = new ImageIcon ("at.gif");
findOptionsMenu.setIcon(atIcon);
findOptionsMenu.setMnemonic(KeyEvent.VK_O);
// ButtonGroup for radio buttons
ButtonGroup directionGroup = new ButtonGroup();
// Edit->Options->Forward, F - Mnemonic, in group
JRadioButtonMenuItem forwardMenuItem =
new JRadioButtonMenuItem("Forward", true);
forwardMenuItem.addActionListener(menuListener);
forwardMenuItem.setMnemonic(KeyEvent.VK_F);
findOptionsMenu.add(forwardMenuItem);
directionGroup.add(forwardMenuItem);
// Edit->Options->Backward, B - Mnemonic, in group
JRadioButtonMenuItem backwardMenuItem =
new JRadioButtonMenuItem("Backward");
backwardMenuItem.addActionListener(menuListener);
backwardMenuItem.setMnemonic(KeyEvent.VK_B);
findOptionsMenu.add(backwardMenuItem);
directionGroup.add(backwardMenuItem);
// Separator
findOptionsMenu.addSeparator();
// Edit->Options->Case Sensitive, C - Mnemonic
JCheckBoxMenuItem caseMenuItem =
new JCheckBoxMenuItem("Case Sensitive");
caseMenuItem.addActionListener(menuListener);
caseMenuItem.setMnemonic(KeyEvent.VK_C);
findOptionsMenu.add(caseMenuItem);
editMenu.add(findOptionsMenu);
155
156
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
frame.setJMenuBar(menuBar);
frame.setSize(350, 250);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
Menu Class Hierarchy
Now that you’ve seen an example of how to create the cascading menus for an application, you
should have an idea of what’s involved in using the Swing menu components. To help clarify,
Figure 6-2 illustrates how all the Swing menu components are interrelated.
Figure 6-2. Swing menu class hierarchy
The most important concept illustrated in Figure 6-2 is that all the Swing menu elements,
as subclasses of JComponent, are AWT components in their own right. You can place JMenuItem,
JMenu, and JMenuBar components anywhere that AWT components can go, not just on a frame.
In addition, because JMenuItem inherits from AbstractButton, JMenuItem and its subclasses
inherit support for various icons and for HTML text labels, as described in Chapter 5.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
■Note Although technically possible, placing menus in locations where users wouldn’t expect them to be
is poor user interface design.
In addition to being part of the basic class hierarchy, each of the selectable menu components
implements the MenuElement interface. The interface describes the menu behavior necessary to
support keyboard and mouse navigation. The predefined menu components already implement this behavior, so you don’t have to. But if you’re interested in how this interface works,
see the “MenuElement Interface” section later in this chapter.
Now let’s take a look at the different Swing menu components.
JMenuBar Class
Swing’s menu bar component is the JMenuBar. Its operation requires you to fill the menu bar
with JMenu elements that have JMenuItem elements. Then you add the menu bar to a JFrame or
some other user interface component requiring a menu bar. The menu bar then relies on the
assistance of a SingleSelectionModel to determine which JMenu to display or post after it’s selected.
Creating JMenuBar Components
JMenuBar has a single constructor of the no-argument variety: public JMenuBar(). Once you
create the menu bar, you can add it to a window with the setJMenuBar() method of JApplet,
JDialog, JFrame, JInternalFrame, or JRootPane. (Yes, even applets can have menu bars.)
JMenuBar menuBar = new JMenuBar();
// Add items to it
...
JFrame frame = new JFrame("MenuSample Example");
frame.setJMenuBar(menuBar);
With the system-provided look and feel types, the menu bar appears at the top of the
window, below any window title (if present), with setJMenuBar(). Other look and feel types, like
Aqua for the Macintosh, place the menu bar elsewhere.
You can also use the add() method of a Container to add a JMenuBar to a window. When
added with the add() method, a JMenuBar is arranged by the layout manager of the Container.
After you have a JMenuBar, the remaining menu classes all work together to fill the menu bar.
Adding Menus to and Removing Menus from Menu Bars
You need to add JMenu objects to a JMenuBar. Otherwise, the only thing displayed is the border
with nothing in it. There’s a single method for adding menus to a JMenuBar:
public JMenu add(JMenu menu)
157
158
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
By default, consecutively added menus are displayed from left to right. This makes the first
menu added the leftmost menu and the last menu added the rightmost menu. Menus added in
between are displayed in the order in which they’re added. For instance, in the sample program
from Listing 6-1, the menus were added as follows:
JMenu fileMenu = new JMenu("File");
menuBar.add(fileMenu);
JMenu editMenu = new JMenu("Edit");
menuBar.add(editMenu);
■Note Placing a JMenuBar in the EAST or WEST area of a BorderLayout does not make the menus
appear vertically, stacked one on top of another. You must customize the menu bar if you want menus to
appear this way. See Figure 6-4, later in this chapter, for one implementation of a top-down menu bar.
In addition to the add() method from JMenuBar, several overloaded varieties of the add()
method inherited from Container offer more control over menu positioning. Of particular
interest is the add(Component component, int index) method, which allows you to specify the
position in which the new JMenu is to appear. Using this second variety of add() allows you to
place the File and Edit JMenu components in a JMenuBar in a different order, but with the
same results:
menuBar.add(editMenu);
menuBar.add(fileMenu, 0);
If you’ve added a JMenu component to a JMenuBar, you can remove it with either the
remove(Component component) or remove(int index) method inherited from Container:
bar.remove(edit);
bar.remove(0);
■Tip Adding or removing menus from a menu bar is likely to confuse users. However, sometimes it’s necessary
to do so—especially if you want to have an expert mode that enables a certain functionality that a nonexpert
mode hides. A better approach is to disable/enable individual menu items or entire menus. If you do add or
remove menus, you must then revalidate() the menu bar to display the changes.
JMenuBar Properties
Table 6-1 shows the 11 properties of JMenuBar. Half the properties are read-only, allowing you
only to query the current state of the menu bar. The remaining properties allow you to alter the
appearance of the menu bar by deciding whether the border of the menu bar is painted and
selecting the size of the margin between menu elements. The selected property and selection
model control which menu on the menu bar, if any, is currently selected. When the selected
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
component is set to a menu on the menu bar, the menu components appear in a pop-up menu
within a window.
■Caution The helpMenu property, although available with a set-and-get method, is unsupported in the
Swing releases through 5.0. Calling either accessor method will throw an error. With some future release of
Swing, the helpMenu property will likely make a specific JMenu the designated help menu. Exactly what
happens when a menu is flagged as the help menu is specific to the installed look and feel. What tends to
happen is that the menu becomes the last, or rightmost, menu.
Table 6-1. JMenuBar Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
borderPainted
boolean
Read-write
component
Component
Read-only
helpMenu
JMenu
Read-write
margin
Insets
Read-write
menuCount
int
Read-only
selected
boolean/Component
Read-write
selectionModel
SingleSelectionModel
Read-write
subElements
MenuElement[ ]
Read-only
UI
MenuBarUI
Read-write
UIClassID
String
Read-only
■Note The selected property of JMenuBar is nonstandard. The getter method returns a boolean to
indicate if a menu component is selected on the menu bar. The setter method accepts a Component argument
to select a component on the menu bar.
Customizing a JMenuBar Look and Feel
Each predefined Swing look and feel provides a different appearance and set of default UIResource
values for the JMenuBar and each of the menu components. Figure 6-3 shows the appearance
of all these menu components for the preinstalled set of look and feel types: Motif, Windows,
and Ocean.
159
160
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Motif
Windows
Ocean
Figure 6-3. Menu components under different look and feel types
In regard to the specific appearance of the JMenuBar, the available set of UIResource-related
properties is shown in Table 6-2. There are 12 properties available for the JMenuBar component.
Table 6-2. JMenuBar UIResource Elements
Property String
Object Type
MenuBar.actionMap
ActionMap
MenuBar.background
Color
MenuBar.border
Border
MenuBar.borderColor
Color
MenuBar.darkShadow
Color
MenuBar.font
Font
MenuBar.foreground
Color
MenuBar.gradient
List
MenuBar.highlight
Color
MenuBar.shadow
Color
MenuBar.windowBindings
Object[ ]
MenuBarUI
String
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
If you want a vertical menu bar, instead of a horizontal one, simply change the LayoutManager
of the menu bar component. A setup such as a 0 row by 1 column GridLayout does the job, as
shown in the following example, because the number of rows will grow infinitely for each JMenu
added:
import java.awt.*;
import javax.swing.*;
public class VerticalMenuBar extends JMenuBar {
private static final LayoutManager grid = new GridLayout(0,1);
public VerticalMenuBar() {
setLayout(grid);
}
}
Moving the menu bar shown in Figure 6-1 to the east side of a BorderLayout and making it
a VerticalMenuBar instead of a JMenuBar produces the setup shown in Figure 6-4. Although the
vertical menu bar may look a little unconventional here, it’s more desirable to have menu items
appearing stacked vertically, rather than horizontally, on the right (or left) side of a window. You
may, however, want to change the MenuBar.border property to a more appropriate border.
Figure 6-4. Using the VerticalMenuBar
■Note Changing the layout manager of the JMenuBar has one negative side effect: Because top-level menus
are pull-down menus, open menus on a vertical bar will obscure the menu bar. If you want to correct this popup placement behavior, you must extend the JMenu class and override its protected getPopupMenuOrigin()
method in order to make the pop-up menu span out, rather than drop down.
SingleSelectionModel Interface
The SingleSelectionModel interface describes an index into an integer-indexed data structure
where an element can be selected. The data structure behind the interface facade is most likely
an array or vector in which repeatedly accessing the same position is guaranteed to return the
same object. The SingleSelectionModel interface is the selection model for a JMenuBar as well
as a JPopupMenu. In the case of a JMenuBar, the interface describes the currently selected JMenu
that needs to be painted. In the case of a JPopupMenu, the interface describes the currently
selected JMenuItem.
161
162
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
■Note SingleSelectionModel also serves as the selection model for JTabbedPane, a class described
in Chapter 11.
The interface definition for SingleSelectionModel follows:
public interface SingleSelectionModel {
// Listeners
public void addChangeListener(ChangeListener listener);
public void removeChangeListener(ChangeListener listener);
// Properties
public int getSelectedIndex();
public void setSelectedIndex(int index);
public boolean isSelected();
// Other Methods
public void clearSelection();
}
As you can see, in addition to the selection index, the interface requires maintenance of a
ChangeListener list to be notified when the selection index changes.
The default Swing-provided implementation of SingleSelectionModel is the
DefaultSingleSelectionModel class. For both JMenuBar and JPopupMenu, it’s very unlikely that
you will change their selection model from this default implementation.
The DefaultSingleSelectionModel implementation manages the list of ChangeListener
objects. In addition, the model uses a value of –1 to signify that nothing is currently selected.
When the selected index is –1, isSelected() returns false; otherwise, the method returns true.
When the selected index changes, any registered ChangeListener objects will be notified.
JMenuItem Class
The JMenuItem component is the predefined component that a user selects on a menu bar. As a
subclass of AbstractButton, JMenuItem acts as a specialized button component that behaves
similarly to a JButton. Besides being a subclass of AbstractButton, the JMenuItem class shares
the data model of JButton (ButtonModel interface and DefaultButtonModel implementation).
Creating JMenuItem Components
Six constructors for JMenuItem follow. They allow you to initialize the menu item’s string or icon
label and the mnemonic of the menu item. There’s no explicit constructor permitting you to
set all three options at creation time, unless you make them part of an Action.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
public JMenuItem()
JMenuItem jMenuItem = new JMenuItem();
public JMenuItem(Icon icon)
Icon atIcon = new ImageIcon("at.gif");
JMenuItem jMenuItem = new JMenuItem(atIcon);
public JMenuItem(String text)
JMenuItem jMenuItem = new JMenuItem("Cut");
public JMenuItem(String text, Icon icon)
Icon atIcon = new ImageIcon("at.gif");
JMenuItem jMenuItem = new JMenuItem("Options", atIcon);
public JMenuItem(String text, int mnemonic)
JMenuItem jMenuItem = new JMenuItem("Cut", KeyEvent.VK_T);
public JMenuItem(Action action)
Action action = ...;
JMenuItem jMenuItem = new JMenuItem(action);
The mnemonic allows you to select the menu through keyboard navigation. For instance,
you can simply press Alt-T on a Windows platform to select the Cut menu item if the item
appears on an Edit menu that is already open. The mnemonic for a menu item usually appears
underlined within the text label for the menu. However, if the letter doesn’t appear within the
text label or if there is no text label, the user will have no visual clue as to its setting. Letters are
specified by the different key constants within the java.awt.event.KeyEvent class.
Other platforms might offer other meta-keys for selecting mnemonics. On UNIX, the
meta-key is also an Alt key; on a Macintosh, it’s the Command key.
■Note Adding a JMenuItem with a label of “-” doesn’t create a menu separator as it did with AWT’s
MenuItem.
JMenuItem Properties
The JMenuItem class has many properties. Roughly 100 properties are inherited through its
various superclasses. The 10 properties specific to JMenuItem are shown in Table 6-3.
163
164
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Table 6-3. JMenuItem Properties
Property Name
Data Type
Access
accelerator
KeyStroke
Read-write bound
accessibleContext
AccessibleContext
Read-only
armed
boolean
Read-write
component
Component
Read-only
enabled
boolean
Write-only bound
menuDragMouseListeners
MenuDragMouseListener[ ]
Read-only
menuKeyListeners
MenuKeyListener[ ]
Read-only
subElements
MenuElement[ ]
Read-only
UI
MenuElementUI
Write-only bound
UIClassID
String
Read-only
One truly interesting property is accelerator. As explained in Chapter 2, KeyStroke is a
factory class that lets you create instances based on key and modifier combinations. For instance,
the following statements, from the example in Listing 6-1 earlier in this chapter, associate Ctrl-X as
the accelerator for one particular menu item:
KeyStroke ctrlXKeyStroke=KeyStroke.getKeyStroke("control X");
cutMenuItem.setAccelerator(ctrlXKeyStroke);
The read-only component and subElements properties are part of the MenuElement interface,
which JMenuItem implements. The component property is the menu item renderer (the JMenuItem
itself). The subElements property is empty (that is, an empty array, not null), because a JMenuItem
has no children.
■Note Swing menus don’t use AWT’s MenuShortcut class.
Handling JMenuItem Events
You can handle events within a JMenuItem in at least five different ways. The component inherits
the ability to allow you to listen for the firing of ChangeEvent and ActionEvent through the
ChangeListener and ActionListener registration methods of AbstractButton. In addition, the
JMenuItem component supports registering MenuKeyListener and MenuDragMouseListener
objects when MenuKeyEvent and MenuDragMouseEvent events happen. These techniques are
discussed in the following sections. A fifth way is to pass an Action to the JMenuItem constructor,
which is like a specialized way of listening with an ActionListener. For more on using Action,
see the discussion of using Action objects with menus, in the “JMenu Class” section a little later
in this chapter.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Listening to JMenuItem Events with a ChangeListener
Normally, you wouldn’t register a ChangeListener with a JMenuItem. However, demonstrating
one hypothetical case helps to clarify the data model changes of the JMenuItem with respect to
its ButtonModel. The changes with regard to arming, pressing, and selecting are the same as
with a JButton. However, their naming might be a little confusing because the selected property of
the model is never set.
A JMenuItem is armed when the mouse passes over the menu choice and it becomes selected.
A JMenuItem is pressed when the user releases the mouse button over it. Immediately after being
pressed, the menu item becomes unpressed and unarmed. Between the menu item being
pressed and unpressed, the AbstractButton is notified of the model changes, causing any registered ActionListener objects of the menu item to be notified. The button model for a plain
JMenuItem never reports being selected. If you move the mouse to another menu item without
selecting, the first menu item automatically becomes unarmed. To help you better visualize
the different changes, Figure 6-5 shows a sequence diagram.
ActionListener
List of
JMenuItem
JMenuItem
ChangeListener
List of
JMenuItem
ActionListener
List of
ButtonModel
ButtonModel
Registers with
Registers with
Mouse Enters/Menu Selection Changed
Armed
Mouse Released
Pressed
Notifies
Pressed
Notifies
No Longer Pressed
No Longer Armed
Figure 6-5. JMenuItem selection sequence diagram
■Note Subclasses of JMenuItem can have their button model selected property set, like a radio button—
but the predefined JMenuItem cannot.
165
166
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Listening to JMenuItem Events with an ActionListener
The better listener to attach to a JMenuItem is the ActionListener, or passing an Action to the
constructor. It allows you to find out when a menu item is selected. Any registered ActionListener
objects would be notified when a user releases the mouse button over a JMenuItem that is part
of an open menu. Registered listeners are also notified if the user employs the keyboard
(whether with arrow keys or mnemonics) or presses the menu item’s keyboard accelerator to
make a selection.
You must add an ActionListener to every JMenuItem for which you want an action to happen
when selected. There’s no automatic shortcut allowing you to register an ActionListener with
a JMenu or JMenuBar and have all their contained JMenuItem objects notify a single ActionListener.
The sample program shown in Listing 6-1 associates the same ActionListener with every
JMenuItem:
class MenuActionListener implements ActionListener {
public void actionPerformed(ActionEvent e) {
System.out.println("Selected: " + e.getActionCommand());
}
}
However, more frequently, you would associate a different action with each item, so that
each menu item can respond differently.
■Tip Instead of creating a custom ActionListener for the component, and adding it as a listener, you
can also create a custom Action and call setAction() on the component.
Listening to JMenuItem Events with a MenuKeyListener
The MenuKeyEvent is a special kind of KeyEvent used internally by the user interface classes for
a JMenu and JMenuItem, allowing the components to listen for when their keyboard mnemonic
is pressed. To listen for this keyboard input, each menu component registers a MenuKeyListener
to pay attention to the appropriate input. If the keyboard mnemonic is pressed, the event is
consumed and not passed to any registered listeners. If the keyboard mnemonic is not pressed,
any registered key listeners (instead of menu key listeners) are notified.
The MenuKeyListener interface definition follows:
public interface MenuKeyListener extends EventListener {
public void menuKeyPressed(MenuKeyEvent e);
public void menuKeyReleased(MenuKeyEvent e);
public void menuKeyTyped(MenuKeyEvent e);
}
Normally, you wouldn’t register objects as this type of listener yourself, although you could
if you wanted to. If you do, and if a MenuKeyEvent happens (that is, a key is pressed/released),
every JMenu on the JMenuBar will be notified, as will every JMenuItem (or subclass) on an open
menu with a registered MenuKeyListener. That includes disabled menu items so that they can
consume a pressed mnemonic. The definition of the MenuKeyEvent class follows:
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
public class MenuKeyEvent extends KeyEvent {
public MenuKeyEvent(Component source, int id, long when, int modifiers,
int keyCode, char keyChar, MenuElement path[], MenuSelectionManager mgr);
public MenuSelectionManager getMenuSelectionManager();
public MenuElement[] getPath();
}
It’s the job of the MenuSelectionManager to determine the current selection path. The
selection path is the set of menu elements from the top-level JMenu on the JMenuBar to the
selected components. For the most part, the manager works behind the scenes, and you never
need to worry about it.
Listening to JMenuItem Events with a MenuDragMouseListener
Like MenuKeyEvent, the MenuDragMouseEvent is a special kind of event used internally by the user
interface classes for JMenu and JMenuItem. As its name implies, the MenuDragMouseEvent is a
special kind of MouseEvent. By monitoring when a mouse is moved within an open menu, the
user interface classes use the listener to maintain the selection path, thus determining the
currently selected menu item. Its definition follows:
public interface MenuDragMouseListener extends EventListener {
public void menuDragMouseDragged(MenuDragMouseEvent e);
public void menuDragMouseEntered(MenuDragMouseEvent e);
public void menuDragMouseExited(MenuDragMouseEvent e);
public void menuDragMouseReleased(MenuDragMouseEvent e);
}
As with the MenuKeyListener, normally you don’t listen for this event yourself. If you’re
interested in when a menu or submenu is about to be displayed, the better listener to register
is the MenuListener, which can be registered with the JMenu, but not with an individual JMenuItem.
You’ll look at this in the next section, which describes JMenu.
The definition of the MenuDragMouseEvent class, the argument to each of the
MenuDragMouseListener methods, is as follows:
public class MenuDragMouseEvent extends MouseEvent {
public MenuDragMouseEvent(Component source, int id, long when, int modifiers,
int x, int y, int clickCount, boolean popupTrigger, MenuElement path[],
MenuSelectionManager mgr);
public MenuSelectionManager getMenuSelectionManager();
public MenuElement[] getPath();
}
Customizing a JMenuItem Look and Feel
As with the JMenuBar, the predefined look and feel types each provide a different JMenuItem
appearance and set of default UIResource values. Figure 6-3 showed the appearance of the
JMenuItem component for the preinstalled set: Motif, Windows, and Ocean.
The available set of UIResource-related properties for a JMenuItem are shown in Table 6-4.
The JMenuItem component offers 20 different properties.
167
168
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Table 6-4. JMenuItem UIResource Elements
Property String
Object Type
MenuItem.acceleratorDelimiter
String
MenuItem.acceleratorFont
Font
MenuItem.acceleratorForeground
Color
MenuItem.acceleratorSelectionForeground
Color
MenuItem.actionMap
ActionMap
MenuItem.arrowIcon
Icon
MenuItem.background
Color
MenuItem.border
Border
MenuItem.borderPainted
Boolean
MenuItem.checkIcon
Icon
MenuItem.commandSound
String
MenuItem.disabledForeground
Color
MenuItem.font
Font
MenuItem.foreground
Color
MenuItem.margin
Insets
MenuItem.opaque
Boolean
MenuItem.selectionBackground
Color
MenuItem.selectionForeground
Color
MenuItem.textIconGap
Integer
MenuItemUI
String
JMenu Class
The JMenu component is the basic menu item container that is placed on a JMenuBar. When a
JMenu is selected, the menu displays the contained menu items within a JPopupMenu. As with
JMenuItem, the data model for the JMenu is an implementation of ButtonModel, or more specifically,
DefaultButtonModel.
Creating JMenu Components
Four constructors for JMenu allow you to initialize the string label of the menu if desired:
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
public JMenu()
JMenu jMenu = new JMenu();
public JMenu(String label)
JMenu jMenu = new JMenu("File");
public JMenu(String label, boolean useTearOffs)
public JMenu(Action action)
Action action = ...;
JMenu jMenu = new JMenu(action);
One constructor is for using a tear-off menu. However, tear-off menus aren’t currently
supported; therefore, the argument is ignored. The fourth constructor pulls the properties of
the menu from an Action.
■Note Tear-off menus are menus that appear in a window and remain open after selection, instead of
automatically closing.
Adding Menu Items to a JMenu
Once you have a JMenu, you need to add JMenuItem objects to it; otherwise, the menu will not
display any choices. There are five methods for adding menu items defined within JMenu and
one for adding a separator:
public
public
public
public
public
public
JMenuItem add(JMenuItem menuItem);
JMenuItem add(String label);
Component add(Component component);
Component add(Component component, int index);
JMenuItem add(Action action);
void addSeparator();
In Listing 6-1 earlier in this chapter, all the JMenuItem components were added to JMenu
components with the first add() method. As a shortcut, you can pass the text label for a JMenuItem
to the add() method of JMenu. This will create the menu item, set its label, and pass back the
new menu item component. You can then bind a menu item event handler to this newly
obtained menu item. The third add() method shows that you can place any Component on a
JMenu, not solely a JMenuItem. The fourth add() lets you position the component. The last add()
variety, with the Action argument, will be discussed in the next section of this chapter.
You can add separator bars with the addSeparator() method of JMenu. For instance, in
Listing 6-1, the File menu was created with code similar to the following:
169
170
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
JMenu fileMenu = new JMenu("File");
JMenuItem newMenuItem = new JMenuItem("New");
fileMenu.add(newMenuItem);
JMenuItem openMenuItem = new JMenuItem("Open");
fileMenu.add(openMenuItem);
JMenuItem closeMenuItem = new JMenuItem("Close");
fileMenu.add(closeMenuItem);
fileMenu.addSeparator();
JMenuItem saveMenuItem = new JMenuItem("Save");
fileMenu.add(saveMenuItem);
fileMenu.addSeparator();
JMenuItem exitMenuItem = new JMenuItem("Exit");
fileMenu.add(exitMenuItem);
Notice the addSeparator() calls wrapped around the call to add the Save menu item.
In addition to adding menu items at the end of a menu, you can insert them at specific
positions or insert a separator at a specific position, as follows:
public JMenuItem insert(JMenuItem menuItem, int pos);
public JMenuItem insert(Action a, int pos);
public void insertSeparator(int pos);
When a menu item is added to a JMenu, it’s added to an internal JPopupMenu.
Using Action Objects with Menus
The Action interface and its associated classes are described in Chapter 2. An Action is an
extension of the ActionListener interface and contains some special properties for customizing
components associated with its implementations.
With the help of the AbstractAction implementation, you can easily define text labels,
icons, mnemonics, tooltip text, enabled status, and an ActionListener apart from a component.
Then you can create a component with an associated Action and not need to give the component
a text label, icon, mnemonics, tooltip text, enabled status, or ActionListener, because those
attributes would come from the Action. For a more complete description, refer to Chapter 2.
To demonstrate, Listing 6-2 creates a specific implementation of AbstractAction and adds
it to a JMenu multiple times. Once the Action is added to a JMenu, selecting the JMenuItem will
display a pop-up dialog box with the help of the JOptionPane class, a topic covered in Chapter 9.
Listing 6-2. About Action Definition
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ShowAction extends AbstractAction {
Component parentComponent;
public ShowAction(Component parentComponent) {
super("About");
putValue(Action.MNEMONIC_KEY, new Integer(KeyEvent.VK_A));
this.parentComponent = parentComponent;
}
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
public void actionPerformed(ActionEvent actionEvent) {
Runnable runnable = new Runnable() {
public void run() {
JOptionPane.showMessageDialog(
parentComponent, "About Swing",
"About Box V2.0", JOptionPane.INFORMATION_MESSAGE);
}
};
EventQueue.invokeLater(runnable);
}
}
The next source creates a ShowAction and a JMenuItem for the File and Edit menus in the
sample program (Listing 6-1). Without explicitly setting the menu item properties, it will then
have an “About” text label and an A mnemonic, and will perform the defined actionPerformed()
method as its ActionListener. In fact, you can create the Action once, and then associate it
with as many places as necessary (or other components that support adding Action objects).
Action showAction = new ShowAction(aComponent);
JMenuItem fileAbout = new JMenuItem(showAction);
fileMenu.add(fileAbout);
JMenuItem editAbout = new JMenuItem(showAction);
editMenu.add(editAbout);
One complexity-busting side effect when using AbstractAction is that it lets you disable
the Action with setEnabled(false), which, in turn, will disable all components created from it.
JMenu Properties
Besides the 100-plus inherited properties of JMenu, 16 properties are available from JMenu-specific
methods, as shown in Table 6-5. Several of the properties override the behavior of the inherited
properties. For instance, the setter method for the accelerator property throws an error if you
try to assign such a property. In other words, accelerators aren’t supported within JMenu objects.
The remaining properties describe the current state of the JMenu object and its contained menu
components.
Table 6-5. JMenu Properties
Property Name
Data Type
Access
accelerator
KeyStroke
Write-only
accessibleContext
AccessibleContext
Read-only
component
Component
Read-only
delay
int
Read-write
itemCount
int
Read-only
menuComponentCount
int
Read-only
menuComponents
Component[ ]
Read-only
171
172
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Table 6-5. JMenu Properties (Continued)
Property Name
Data Type
Access
menuListeners
MenuListener[ ]
Read-only
model
ButtonModel
Write-only bound
popupMenu
JPopupMenu
Read-only
popupMenuVisible
boolean
Read-write
selected
boolean
Read-write
subElements
MenuElement[ ]
Read-only
tearOff
boolean
Read-only
topLevelMenu
boolean
Read-only
UIClassID
String
Read-only
■Tip Keep in mind that many property methods are inherited and that the parent class might offer a getter
method where the current class defines only a new setter method, or vice versa.
The delay property represents the value for the time that elapses between selection
of a JMenu and posting of the JPopupMenu. By default, this value is zero, meaning that the
submenu will appear immediately. Trying to set the value to a negative setting will throw
an IllegalArgumentException.
■Caution Since there is no support for tear-off menus, if you try to access the tearOff property, an error
will be thrown.
Selecting Menu Components
Normally, you don’t need to listen for the selection of JMenu components. You listen for only
selection of individual JMenuItem components. Nevertheless, you may be interested in the
different ways that ChangeEvent works with a JMenu as compared with a JMenuItem. In addition,
a MenuEvent can notify you whenever a menu is posted or canceled.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Listening to JMenu Events with a ChangeListener
As with a JMenuItem, you can register a ChangeListener with a JMenu if you’re interested in making
changes to the underlying ButtonModel. Surprisingly, the only possible state change to the
ButtonModel with a JMenu is with the selected property. When selected, the JMenu displays its
menu items. When not selected, the pop-up goes away.
Listening to JMenu Events with a MenuListener
The better way to listen for when a pop-up is displayed or hidden is by registering MenuListener
objects with your JMenu objects. Its definition follows:
public interface MenuListener extends EventListener {
public void menuCanceled(MenuEvent e);
public void menuDeselected(MenuEvent e);
public void menuSelected(MenuEvent e);
}
With a registered MenuListener, you’re notified when a JMenu is selected before the pop-up
menu is opened with the menu’s choices. This allows you to customize its menu choices on the
fly at runtime, with some potential interaction performance penalties. Besides being told when
the associated pop-up menu is to be posted, you’re also notified when the menu has been
deselected and when the menu has been canceled. As the following MenuEvent class definition
shows, the only piece of information that comes with the event is the source (the menu):
public class MenuEvent extends EventObject {
public MenuEvent(Object source);
}
■Tip If you choose to customize the items on a JMenu dynamically, be sure to call revalidate(),
because the component waits until you are done before updating the display.
Customizing a JMenu Look and Feel
As with the JMenuBar and JMenuItem, the predefined look and feel classes provide a different
JMenu appearance and set of default UIResource values. Figure 6-3 shows the appearance of the
JMenu object for the preinstalled set of look and feel types.
The available set of UIResource-related properties for a JMenu is shown in Table 6-6. For the
JMenu component, there are 30 different properties.
173
174
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Table 6-6. JMenu UIResource Elements
Property String
Object Type
menu
Color
Menu.acceleratorDelimiter
String
Menu.acceleratorFont
Font
Menu.acceleratorForeground
Color
Menu.acceleratorSelectionForeground
Color
Menu.ActionMap
ActionMap
Menu.arrowIcon
Icon
Menu.background
Color
Menu.border
Border
Menu.borderPainted
Boolean
Menu.checkIcon
Icon
Menu.delay
Integer
Menu.disabledForeground
Color
Menu.font
Font
Menu.foreground
Color
Menu.margin
Insets
Menu.menuPopupOffsetX
Integer
Menu.menuPopupOffsetY
Integer
Menu.opaque
Boolean
Menu.selectionBackground
Color
Menu.selectionForeground
Color
Menu.shortcutKeys
int[ ]
Menu.submenuPopupOffsetX
Integer
Menu.submenuPopupOffsetY
Integer
Menu.textIconGap
Integer
Menu.useMenuBarBackgroundForTopLevel
Boolean
menuPressedItemB
Color
menuPressedItemF
Color
menuText
Color
MenuUI
String
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
JSeparator Class
The JSeparator class is a special component that acts as a separator on a JMenu. The JPopupMenu
and JToolBar classes also support separators, but each uses its own subclass of JSeparator. In
addition to being placed on a menu, the JSeparator can be used anywhere you want to use a
horizontal or vertical line to separate different areas of a screen.
The JSeparator is strictly a visual component; therefore, it has no data model.
Creating JSeparator Components
To create a separator for a JMenu, you don’t directly create a JSeparator, although you can.
Instead, you call the addSeparator() method of JMenu, and the menu will create the separator
and add the separator as its next item. The fact that it’s a JSeparator (which isn’t a JMenuItem
subclass) is hidden. There’s also an insertSeparator(int index) method of JMenu that allows
you to add a separator at a specific position on the menu, that isn’t necessarily the next slot.
If you plan to use a JSeparator away from a menu (for example, to visually separate two
panels in a layout), you should use one of the two constructors for JSeparator:
public JSeparator()
JSeparator jSeparator = new JSeparator();
public JSeparator(int orientation)
JSeparator jSeparator = new JSeparator(JSeparator.VERTICAL);
These constructors allow you to create a horizontal or vertical separator. If an orientation
isn’t specified, the orientation is horizontal. If you want to explicitly specify an orientation, you
use either of the JSeparator constants of HORIZONTAL and VERTICAL.
JSeparator Properties
After you have a JSeparator, you add it to the screen like any other component. The initial
dimensions of the component are empty (zero width and height), so if the layout manager of
the screen asks the component what size it would like to be, the separator will reply that it
needs no space. On the other hand, if the layout manager offers a certain amount of space, the
separator will use the space if the orientation is appropriate. For instance, adding a horizontal
JSeparator to the north side of a BorderLayout panel draws a separator line across the screen.
However, adding a horizontal JSeparator to the east side of the same panel would result in
nothing being drawn. For a vertical JSeparator, the behavior is reversed: The north side would
be empty and a vertical line would appear on the east side.
The four properties of JSeparator are listed in Table 6-7.
Table 6-7. JSeparator Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
orientation
int
Read-write bound
UI
SeparatorUI
Read-write bound
UIClassID
String
Read-only
175
176
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
■Caution If the orientation property isn’t set to a value equivalent to either JSeparator.HORIZONTAL or
JSeparator.VERTICAL, an IllegalArgumentException is thrown.
Customizing a JSeparator Look and Feel
The appearance of the JSeparator under the preinstalled set of look and feel types is shown
with the other menu components in Figure 6-3.
The available set of UIResource-related properties for a JSeparator is shown in Table 6-8.
For the JSeparator component, five different properties are available.
Table 6-8. JSeparator UIResource Elements
Property String
Object Type
Separator.background
Color
Separator.foreground
Color
Separator.insets
Insets
Separator.thickness
Integer
SeparatorUI
String
■Caution Two additional properties, highlight and shadow, are present but deprecated and should not
be used.
JPopupMenu Class
The JPopupMenu component is the container for pop-up menu components, displayable anywhere
and used for support by JMenu. When a programmer-defined triggering event happens, you
display the JPopupMenu, and the menu displays the contained menu components. Like JMenuBar,
JPopupMenu uses the SingleSelectionModel to manage the currently selected element.
Creating JPopupMenu Components
There are two constructors for JPopupMenu:
public JPopupMenu()
JPopupMenu jPopupMenu = new JPopupMenu();
public JPopupMenu(String title)
JPopupMenu jPopupMenu = new JPopupMenu("Welcome");
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Only one allows you to initialize the title for the menu, if desired. What happens with the
title depends on the installed look and feel. The currently installed look and feel may ignore
the title.
Adding Menu Items to a JPopupMenu
As with a JMenu, once you have a JPopupMenu, you need to add menu item objects to it; otherwise, the menu will be empty. There are three JPopupMenu methods for adding menu items and
one for adding a separator:
public
public
public
public
JMenuItem add(JMenuItem menuItem);
JMenuItem add(String label);
JMenuItem add(Action action);
void addSeparator();
In addition, an add() method is inherited from Container for adding regular AWT
components:
public Component add(Component component);
■Note It generally isn’t wise to mix lightweight Swing components with heavyweight AWT components.
However, because pop-up menus are more apt to be on top, it’s less of an issue in this case.
The natural way of adding menu items is with the first add() method. You create the menu
item independently of the pop-up menu, including defining its behavior, and then you attach it
to the menu. With the second variety of add(), you must attach an event handler to the menu
item returned from the method; otherwise, the menu choice won’t respond when selected. The
following source demonstrates the two approaches. Which you use depends entirely on your
preference. A visual programming environment like JBuilder will use the first. Because the
first approach is inherently less complex, most, if not all, programmers should also use
the first approach.
JPopupMenu popupenu = new JPopupMenu();
ActionListener anActionListener = ...;
// The first way
JMenuItem firstItem = new JMenuItem("Hello");
firstItem.addActionListener(anActionListener);
popupMenu.add(firstItem);
// The second way
JMenuItem secondItem = popupMenu.add("World");
secondItem.addActionListener(anActionListener);
Using an Action to create a menu item works the same with JPopupMenu as it does with
JMenu. However, according to the Javadoc for the JPopupMenu class, using the Action variety of
the add() method is discouraged. Instead, pass the Action to the constructor for JMenuItem, or
177
178
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
configure it with setAction(), and then add that to the JPopupMenu. Why the method isn’t just
deprecated isn’t clear.
Lastly, you can add a menu separator with the addSeparator() method.
As well as adding menu items at the end of a menu, you can insert them at specific positions or
insert a separator at a specific position:
public JMenuItem insert(Component component, int position);
public JMenuItem insert(Action action, int position);
There’s no insertSeparator() method as there is with JMenu. But you can use the
add(Component component, int position) method inherited from Container. If you want to
remove components, use the remove(Component component) method specific to JPopupMenu.
■Note Accelerators on attached JMenuItem objects are ignored. Mnemonics might also be ignored
depending on the currently installed look and feel.
Displaying the JPopupMenu
Unlike the JMenu, simply populating the pop-up menu isn’t sufficient to use it. You need to
associate the pop-up menu with an appropriate component. Prior to the 5.0 release of Swing,
you needed to add event-handling code to trigger the display of the pop-up menu. Now, all you
need to do is call the setComponentPopupMenu() method for the Swing component you wish to
associate the pop-up menu with. When the platform-specific triggering event happens, the
pop-up menu is automatically displayed.
■Note Why change the way pop-up menu display is triggered? The old code was very tightly tied to mouse
events. It didn’t connect well with the accessibility framework. And the same code was being added everywhere to just show the pop-up menu at the x, y coordinates of the invoker.
You simply need to create an instance of JPopupMenu and attach it to any component you
want to have display the pop-up menu, as follows:
JPopupMenu popupMenu = ...;
aComponent.setComponentPopupMenu(popupMenu);
The methods of JComponent that are important to pop-up menus are
getComponentPopupMenu(), setComponentPopupMenu(), getInheritsPopupMenu(),
setInheritsPopupMenu(), and getPopupLocation(). The setInheritsPopupMenu() method
accepts a boolean argument. When true, and no component pop-up menu has been directly
set for the component, the parent container will be explored for a pop-up.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
JPopupMenu Properties
The 16 properties of JPopupMenu are listed in Table 6-9. Many more properties are also inherited
through JComponent, Container, and Component.
Table 6-9. JPopupMenu Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
borderPainted
boolean
Read-write
component
Component
Read-only
invoker
Component
Read-only
label
String
Read-write bound
lightWeightPopupEnabled
boolean
Read-write
margin
Insets
Read-only
menuKeyListeners
MenuKeyListener[ ]
Read-only
popupMenuListeners
PopupMenuListener[ ]
Read-only
popupSize
Dimension
Write-only
selected
Component
Write-only
selectionModel
SingleSelectionModel
Read-write
subElements
MenuElement[ ]
Read-only
UI
PopupMenuUI
Read-write bound
UIClassID
String
Read-only
visible
boolean
Read-write
The most interesting property of JPopupMenu is lightWeightPopupEnabled. Normally, the
JPopupMenu tries to avoid creating new heavyweight components for displaying its menu items.
Instead, the pop-up menu uses a JPanel when the JPopupMenu can be displayed completely
within the outermost window boundaries. Otherwise, if the menu items don’t fit, the JPopupMenu
uses a JWindow. If, however, you’re mixing lightweight and heavyweight components on different
window layers, displaying the pop-up within a JPanel might not work, because a heavyweight
component displayed in the layer of the menu will appear in front of the JPanel. To correct this
behavior, the pop-up menu can use a Panel for displaying the menu choices. By default, the
JPopupMenu never uses a Panel.
179
180
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
■Note When the JPopupMenu is displayed in either a JPanel or a Panel, the outermost window relies on
the layering effect of the JRootPane to ensure that the pop-up panel is displayed at the appropriate position
in front of the other components. Chapter 8 describes the JRootPane class in more detail.
If you need to enable the display of a Panel, you can configure it at the individual JPopupMenu
level or for your entire applet or application. At the individual pop-up level, just set the
lightWeightPopupEnabled property to false. At the system level, this is done as follows:
// From now on, all JPopupMenus will be heavyweight
JPopupMenu.setDefaultLightWeightPopupEnabled(false);
The method must be called before creating the pop-up menu. JPopupMenu objects created
before the change will have the original value (the default is true).
Watching for Pop-Up Menu Visibility
Like the JMenu, the JPopupMenu has a special event/listener combination to watch for when the
pop-up menu is about to become visible, invisible, or canceled. The event is PopupMenuEvent,
and the listener is PopupMenuListener. The event class simply references the source pop-up
menu of the event.
public class PopupMenuEvent extends EventObject {
public PopupMenuEvent(Object source);
}
When a JPopupMenu fires the event, any registered PopupMenuListener objects are notified
through one of its three interface methods. This lets you customize the current menu items
based on the system state or who/what the pop-up menu invoker happens to be. The
PopupMenuListener interface definition follows:
public interface PopupMenuListener extends EventListener {
public void popupMenuCanceled(PopupMenuEvent e);
public void popupMenuWillBecomeInvisible(PopupMenuEvent e);
public void popupMenuWillBecomeVisible(PopupMenuEvent e);
}
Customizing a JPopupMenu Look and Feel
Each installable Swing look and feel provides a different JPopupMenu appearance and set of
default UIResource values. Figure 6-6 shows the appearance of the JPopupMenu component for
the preinstalled set of look and feel types: Motif, Windows, and Ocean. Notice that of the
predefined look and feel classes, only Motif uses the title property of the JPopupMenu.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Motif
Windows
Ocean
Figure 6-6. JPopupMenu under different look and feel types
The available set of UIResource-related properties for a JPopupMenu is shown in Table 6-10.
For the JPopupMenu component, there are five different properties.
Table 6-10. JPopupMenu UIResource Elements
Property String
Object Type
PopupMenu.actionMap
ActionMap
PopupMenu.background
Color
PopupMenu.border
Border
PopupMenu.consumeEventOnClose
Boolean
PopupMenu.font
Font
PopupMenu.foreground
Color
PopupMenu.popupSound
String
PopupMenu.selectedWindowInputMapBindings
Object[ ]
PopupMenu.selectedWindowInputMapBindings.RightToLeft
Object[ ]
PopupMenuSeparatorUI
String
PopupMenuUI
String
181
182
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
JPopupMenu.Separator Class
The JPopupMenu class maintains its own separator to permit a custom look and feel for the separator
when it’s on a JPopupMenu. This custom separator is an inner class to the JPopupMenu.
When you call the addSeparator() of JPopupMenu, an instance of this class is automatically
created and added to the pop-up menu. In addition, you can create this separator by calling its
no-argument constructor:
JSeparator popupSeparator = new JPopupMenu.Separator();
Both methods create a horizontal separator.
■Note If you want to change the orientation of the separator, you must call the setOrientation()
method inherited from JSeparator with an argument of JPopupMenu.Separator.VERTICAL. However,
having a vertical separator on a pop-up menu is inappropriate.
A Complete Pop-Up Menu Usage Example
The program in Listing 6-3 puts together all the pieces of using a JPopupMenu, including listening
for selection of all the items on the menu, as well as listening for when it’s displayed. The output for
the program is shown in Figure 6-7, with the pop-up visible.
Figure 6-7. JPopupMenu usage example output
Listing 6-3. PopupSample Class Definition
import
import
import
import
java.awt.*;
java.awt.event.*;
javax.swing.*;
javax.swing.event.*;
public class PopupSample {
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
// Define ActionListener
static class PopupActionListener implements ActionListener {
public void actionPerformed(ActionEvent actionEvent) {
System.out.println("Selected: " + actionEvent.getActionCommand());
}
}
// Define PopupMenuListener
static class MyPopupMenuListener implements PopupMenuListener {
public void popupMenuCanceled(PopupMenuEvent popupMenuEvent) {
System.out.println("Canceled");
}
public void popupMenuWillBecomeInvisible(PopupMenuEvent popupMenuEvent) {
System.out.println("Becoming Invisible");
}
public void popupMenuWillBecomeVisible(PopupMenuEvent popupMenuEvent) {
System.out.println("Becoming Visible");
}
}
public static void main(final String args[]) {
Runnable runner = new Runnable() {
public void run() {
// Create frame
JFrame frame = new JFrame("PopupSample Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ActionListener actionListener = new PopupActionListener();
PopupMenuListener popupMenuListener = new MyPopupMenuListener();
// Create popup menu, attach popup menu listener
JPopupMenu popupMenu = new JPopupMenu("Title");
popupMenu.addPopupMenuListener(popupMenuListener);
// Cut
JMenuItem cutMenuItem = new JMenuItem("Cut");
cutMenuItem.addActionListener(actionListener);
popupMenu.add(cutMenuItem);
// Copy
JMenuItem copyMenuItem = new JMenuItem("Copy");
copyMenuItem.addActionListener(actionListener);
popupMenu.add(copyMenuItem);
183
184
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
// Paste
JMenuItem pasteMenuItem = new JMenuItem("Paste");
pasteMenuItem.addActionListener(actionListener);
pasteMenuItem.setEnabled(false);
popupMenu.add(pasteMenuItem);
// Separator
popupMenu.addSeparator();
// Find
JMenuItem findMenuItem = new JMenuItem("Find");
findMenuItem.addActionListener(actionListener);
popupMenu.add(findMenuItem);
JButton label = new JButton();
frame.add(label);
label.setComponentPopupMenu(popupMenu);
frame.setSize(350, 250);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
JCheckBoxMenuItem Class
Swing’s JCheckBoxMenuItem component behaves as if you have a JCheckBox on a menu as a
JMenuItem. The data model for the menu item is the ToggleButtonModel, described in Chapter 5.
It allows the menu item to have a selected or unselected state, while showing an appropriate
icon for the state. Because the data model is the ToggleButtonModel, when JCheckBoxMenuItem
is placed in a ButtonGroup, only one component in the group is ever selected. However, this
isn’t the natural way to use a JCheckBoxMenuItem and is likely to confuse users. If you need this
behavior, use JRadioButtonMenuItem, as described later in this chapter.
Creating JCheckBoxMenuItem Components
There are seven constructors for JCheckBoxMenuItem. They allow you to initialize the text label,
icon, and initial state.
public JCheckBoxMenuItem()
JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem();
public JCheckBoxMenuItem(String text)
JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem("Boy");
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
public JCheckBoxMenuItem(Icon icon)
Icon boyIcon = new ImageIcon("boy-r.jpg");
JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem(boyIcon);
public JCheckBoxMenuItem(String text, Icon icon)
JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem("Boy", boyIcon);
public JCheckBoxMenuItem(String text, boolean state)
JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem("Girl", true);
public JCheckBoxMenuItem(String text, Icon icon, boolean state)
Icon girlIcon = new ImageIcon("girl-r.jpg");
JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem("Girl", girlIcon, true);
public JCheckBoxMenuItem(Action action)
Action action = ...;
JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem(action);
Unlike the JCheckBox, the icon is part of the label and not a separate device to indicate
whether something is checked. If either the text label or the icon isn’t passed to the constructor,
that part of the item label will be set to its default value of empty. By default, a JCheckBoxMenuItem is
unselected.
■Note Creating a JCheckBoxMenuItem with an icon has no effect on the appearance of the check box
next to the menu item. It’s strictly part of the label for the JCheckBoxMenuItem.
JCheckBoxMenuItem Properties
Most of the JCheckBoxMenuItem properties are inherited from the many superclasses of
JCheckBoxMenuItem. Table 6-11 lists the four properties defined by JCheckBoxMenuItem.
Table 6-11. JCheckBoxMenuItem Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
selectedObjects
Object[ ]
Read-only
state
boolean
Read-write
UIClassID
String
Read-only
185
186
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Handling JCheckBoxMenuItem Selection Events
With a JCheckBoxMenuItem, you can attach many different listeners for a great variety of events:
• MenuDragMouseListener and MenuKeyListener from JMenuItem
• ActionListener, ChangeListener, and ItemListener from AbstractButton
• AncestorListener and VetoableChangeListener from JComponent
• ContainerListener and PropertyChangeListener from Container
• ComponentListener, FocusListener, HierarchyBoundsListener, HierarchyListener,
InputMethodListener, KeyListener, MouseListener, MouseMotionListener, and
MouseWheelListener from Component
Although you can listen for 18 different types of events, the most interesting are
ActionEvent and ItemEvent, described next.
Listening to JCheckBoxMenuItem Events with an ActionListener
Attaching an ActionListener to a JCheckBoxMenuItem allows you to find out when the menu
item is selected. The listener is told of the selection, but not of the new state. To find out the
selected state, you must get the model for the event source and query the selection state, as the
following sample ActionListener source shows. This listener modifies both the check box text
and the icon label, based on the current selection state.
ActionListener aListener = new ActionListener() {
public void actionPerformed(ActionEvent event) {
Icon girlIcon = new ImageIcon("girl-r.jpg");
Icon boyIcon = new ImageIcon("boy-r.jpg");
AbstractButton aButton = (AbstractButton)event.getSource();
boolean selected = aButton.getModel().isSelected();
String newLabel;
Icon newIcon;
if (selected) {
newLabel = "Girl";
newIcon = girlIcon;
} else {
newLabel = "Boy";
newIcon = boyIcon;
}
aButton.setText(newLabel);
aButton.setIcon(newIcon);
}
};
■Note Keep in mind that you can also associate an Action from the constructor that can do the
same thing.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Listening to JCheckBoxMenuItem with an ItemListener
If you listen for JCheckBoxMenuitem selection with an ItemListener, you don’t need to query the
event source for the selection state—the event already carries that information. Based on this
state, you respond accordingly. Re-creating the ActionListener behavior with an ItemListener
requires just a few minor changes to the previously listed source, as follows:
ItemListener iListener = new ItemListener() {
public void itemStateChanged(ItemEvent event) {
Icon girlIcon = new ImageIcon("girl-r.jpg");
Icon boyIcon = new ImageIcon("boy-r.jpg");
AbstractButton aButton = (AbstractButton)event.getSource();
int state = event.getStateChange();
String newLabel;
Icon newIcon;
if (state == ItemEvent.SELECTED) {
newLabel = "Girl";
newIcon = girlIcon;
} else {
newLabel = "Boy";
newIcon = boyIcon;
}
aButton.setText(newLabel);
aButton.setIcon(newIcon);
}
};
Customizing a JCheckBoxMenuItem Look and Feel
The appearance of the JCheckBoxMenuItem under the preinstalled set of look and feel types is
shown with the other menu components in Figure 6-3.
The available set of UIResource-related properties for a JCheckBoxMenuItem is shown in
Table 6-12. The JCheckBoxMenuItem component has 19 different properties.
Table 6-12. JCheckBoxMenuItem UIResource Elements
Property String
Object Type
CheckBoxMenuItem.acceleratorFont
Font
CheckBoxMenuItem.acceleratorForeground
Color
CheckBoxMenuItem.acceleratorSelectionForeground
Color
CheckBoxMenuItem.actionMap
ActionMap
CheckBoxMenuItem.arrowIcon
Icon
CheckBoxMenuItem.background
Color
CheckBoxMenuItem.border
Border
CheckBoxMenuItem.borderPainted
Boolean
187
188
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Table 6-12. JCheckBoxMenuItem UIResource Elements (Continued)
Property String
Object Type
CheckBoxMenuItem.checkIcon
Icon
CheckBoxMenuItem.commandSound
String
CheckBoxMenuItem.disabledForeground
Color
CheckBoxMenuItem.font
Font
CheckBoxMenuItem.foreground
Color
CheckBoxMenuItem.gradient
List
CheckBoxMenuItem.margin
Insets
CheckBoxMenuItem.opaque
Boolean
CheckBoxMenuItem.selectionBackground
Color
CheckBoxMenuItem.selectionForeground
Color
CheckBoxMenuItemUI
String
The Icon associated with the CheckBoxMenuItem.checkIcon property key is the one displayed on
the JCheckBoxMenuItem. If you don’t like the default icon, you can change it with the following line
of source, assuming the new icon has already been defined and created:
UIManager.put("CheckBoxMenuItem.checkIcon", someIcon);
For this new icon to display an appropriate selected image, the Icon implementation
must check the state of the associated menu component within its paintIcon() method. The
DiamondIcon created in Chapter 4 wouldn’t work for this icon because it doesn’t ask the component for its state. Instead, the state is fixed at constructor time. Listing 6-4 shows a class that
represents one icon that could be used.
Listing 6-4. State-Aware Icon Definition
import java.awt.*;
import javax.swing.*;
public class DiamondAbstractButtonStateIcon implements Icon {
private final int width = 10;
private final int height = 10;
private Color color;
private Polygon polygon;
public DiamondAbstractButtonStateIcon(Color color) {
this.color = color;
initPolygon();
}
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
private void initPolygon() {
polygon = new Polygon();
int halfWidth = width/2;
int halfHeight = height/2;
polygon.addPoint (0, halfHeight);
polygon.addPoint (halfWidth, 0);
polygon.addPoint (width, halfHeight);
polygon.addPoint (halfWidth, height);
}
public int getIconHeight() {
return width;
}
public int getIconWidth() {
return height;
}
public void paintIcon(Component component, Graphics g, int x, int y) {
boolean selected = false;
g.setColor (color);
g.translate (x, y);
if (component instanceof AbstractButton) {
AbstractButton abstractButton = (AbstractButton)component;
selected = abstractButton.isSelected();
}
if (selected) {
g.fillPolygon (polygon);
} else {
g.drawPolygon (polygon);
}
g.translate (-x, -y);
}
}
■Note If the DiamondAbstractButtonStateIcon icon were used with a component that isn’t an
AbstractButton type, the icon would always be deselected, because the selection state is a property
of AbstractButton.
JRadioButtonMenuItem Class
The JRadioButtonMenuItem component has the longest name of all the Swing components. It
works like a JRadioButton, but resides on a menu. When placed with other JRadioButtonMenuItem
components within a ButtonGroup, only one component will be selected at a time. As with
the JRadioButton, the button model for the JRadioButtonMenuItem is the JToggleButton.
ToggleButtonModel.
189
190
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Creating JRadioButtonMenuItem Components
The JRadioButtonMenuItem has seven constructors. They allow you to initialize the text label,
icon, and initial state.
public JCheckBoxMenuItem()
JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem();
public JCheckBoxMenuItem(String text)
JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem("Boy");
public JCheckBoxMenuItem(Icon icon)
Icon boyIcon = new ImageIcon("boy-r.jpg");
JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem(boyIcon);
public JCheckBoxMenuItem(String text, Icon icon)
JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem("Boy", boyIcon);
public JCheckBoxMenuItem(String text, boolean state)
JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem("Girl", true);
public JCheckBoxMenuItem(String text, Icon icon, boolean state)
Icon girlIcon = new ImageIcon("girl-r.jpg");
JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem("Girl", girlIcon, true);
public JCheckBoxMenuItem(Action action)
Action action = ...;
JCheckBoxMenuItem jCheckBoxMenuItem = new JCheckBoxMenuItem(action);
Similar to the JCheckBoxMenuItem component, the icon for the JRadioButtonMenuItem is
part of the label. This is unlike the JRadioButton, in which the icon indicates whether the radio
button is selected. If either the text label or icon isn’t part of the constructor, that part of the
item label will be empty. By default, a JRadioButtonMenuItem is unselected. If you create a
JRadioButtonMenuItem that is selected and then add it to a ButtonGroup, the button group will
deselect the menu item if the group already has a selected item in the group.
■Note After creating JRadioButtonMenuItem instances, remember to add them to a ButtonGroup,
so they will work as a mutually exclusive group.
Handling JRadioButtonMenuItem Selection Events
The JRadioButtonMenuItem shares the same 18 different event/listener pairs with
JCheckBoxMenuItem. To listen for selection, attaching an ActionListener is the normal
approach. In addition, you might want to attach the same listener to all the JRadioButtonMenuItem
objects in a ButtonGroup—after all, they’re in a group for a reason. If you use the same listener,
that listener can employ the current selection to perform some common operation. In other
cases, such as that in Figure 6-1, selection of any JRadioButtonMenuItem option does nothing.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Only when someone selects the Find menu element would the current selection of the ButtonGroup
for the set of JRadioButtonMenuItem components have any meaning.
Configuring JRadioButtonMenuItem Properties
As with JCheckBoxMenuItem, most of the JRadioButtonMenuItem properties are inherited. The
two shown in Table 6-13 merely override the behavior from the superclass.
Table 6-13. JRadioButtonMenuItem Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
UIClassID
String
Read-only
Customizing a JRadioButtonMenuItem Look and Feel
The appearance of the JRadioButtonMenuItem under the preinstalled set of look and feel types
is shown with the other menu components in Figure 6-3.
The available set of UIResource-related properties for a JRadioButtonMenuItem is shown in
Table 6-14. For the JRadioButtonMenuItem component, there are 19 different properties.
Table 6-14. JRadioButtonMenuItem UIResource Elements
Property String
Object Type
RadioButtonMenuItem.acceleratorFont
Font
RadioButtonMenuItem.acceleratorForeground
Color
RadioButtonMenuItem.acceleratorSelectionForeground
Color
RadioButtonMenuItem.actionMap
ActionMap
RadioButtonMenuItem.arrowIcon
Icon
RadioButtonMenuItem.background
Color
RadioButtonMenuItem.border
Border
RadioButtonMenuItem.borderPainted
Boolean
RadioButtonMenuItem.checkIcon
Icon
RadioButtonMenuItem.commandSound
String
RadioButtonMenuItem.disabledForeground
Color
RadioButtonMenuItem.font
Font
RadioButtonMenuItem.foreground
Color
RadioButtonMenuItem.gradient
List
RadioButtonMenuItem.margin
Insets
RadioButtonMenuItem.opaque
Boolean
191
192
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Table 6-14. JRadioButtonMenuItem UIResource Elements (Continued)
Property String
Object Type
RadioButtonMenuItem.selectionBackground
Color
RadioButtonMenuItem.selectionForeground
Color
RadioButtonMenuItemUI
String
A Complete JRadioButtonMenuItem Usage Example
To help you understand the JRadioButtonMenuItem usage, the program shown in Listing 6-5
demonstrates how to put everything together, including listening for selection of all the items
on the menu, from either an ActionListener or an ItemListener. The output for the program is
shown in Figure 6-8.
Figure 6-8. JRadioButtonMenuItem usage example output
Listing 6-5. The RadioButtonSample Class Definition
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class RadioButtonSample {
static Icon threeIcon = new ImageIcon("3.gif");
static Icon fourIcon = new ImageIcon("4.gif");
static Icon fiveIcon = new ImageIcon("5.gif");
static Icon sixIcon = new ImageIcon("6.gif");
public static class ButtonActionListener implements ActionListener {
public void actionPerformed (ActionEvent actionEvent) {
AbstractButton aButton = (AbstractButton)actionEvent.getSource();
boolean selected = aButton.getModel().isSelected();
System.out.println (actionEvent.getActionCommand() +
" - selected? " + selected);
}
}
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
public static class ButtonItemListener implements ItemListener {
public void itemStateChanged(ItemEvent itemEvent) {
AbstractButton aButton = (AbstractButton)itemEvent.getSource();
int state = itemEvent.getStateChange();
String selected =
((state == ItemEvent.SELECTED) ? "selected" : "not selected");
System.out.println (aButton.getText() + " - selected? " + selected);
}
}
public static void main(String args[]) {
Runnable runner = new Runnable() {
public void run() {
final ActionListener actionListener = new ButtionActionListener();
final ItemListener itemListener = new ButtonItemListener();
JFrame frame = new JFrame("Radio Menu Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JMenuBar menuBar = new JMenuBar();
JMenu menu = new JMenu("Menu");
ButtonGroup buttonGroup = new ButtonGroup();
menu.setMnemonic(KeyEvent.VK_M);
JRadioButtonMenuItem emptyMenuItem =
new JRadioButtonMenuItem();
emptyMenuItem.setActionCommand("Empty");
emptyMenuItem.addActionListener(actionListener);
buttonGroup.add(emptyMenuItem);
menu.add(emptyMenuItem);
JRadioButtonMenuItem oneMenuItem =
new JRadioButtonMenuItem("Partridge");
oneMenuItem.addActionListener(actionListener);
buttonGroup.add(oneMenuItem);
menu.add(oneMenuItem);
JRadioButtonMenuItem twoMenuItem =
new JRadioButtonMenuItem("Turtle Doves", true);
twoMenuItem.addActionListener(actionListener);
buttonGroup.add(twoMenuItem);
menu.add(twoMenuItem);
193
194
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
JRadioButtonMenuItem threeMenuItem =
new JRadioButtonMenuItem("French Hens", threeIcon);
threeMenuItem.addItemListener(itemListener);
buttonGroup.add(threeMenuItem);
menu.add(threeMenuItem);
JRadioButtonMenuItem fourMenuItem =
new JRadioButtonMenuItem("Calling Birds", fourIcon, true);
fourMenuItem.addActionListener(actionListener);
buttonGroup.add(fourMenuItem);
menu.add(fourMenuItem);
JRadioButtonMenuItem fiveMenuItem =
new JRadioButtonMenuItem(fiveIcon);
fiveMenuItem.addActionListener(actionListener);
fiveMenuItem.setActionCommand("Rings");
buttonGroup.add(fiveMenuItem);
menu.add(fiveMenuItem);
JRadioButtonMenuItem sixMenuItem =
new JRadioButtonMenuItem(sixIcon, true);
sixMenuItem.addActionListener(actionListener);
sixMenuItem.setActionCommand("Geese");
buttonGroup.add(sixMenuItem);
menu.add(sixMenuItem);
menuBar.add(menu);
frame.setJMenuBar(menuBar);
frame.setSize(350, 250);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
■Note Notice that the actionCommand property is set for those menu items lacking text labels. This allows
registered ActionListener objects to determine the selected object. This is only necessary when listeners
are shared across components.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Creating Custom MenuElement Components:
The MenuElement Interface
One thing all the selectable menu components have in common is that they implement the
MenuElement interface. The JSeparator doesn’t implement the interface, but that’s okay because
it isn’t selectable. The purpose of the MenuElement interface is to allow the MenuSelectionManager
to notify the different menu elements as a user moves around a program’s menu structure.
As the following interface definition shows, the MenuElement interface is made up of
five methods:
public interface MenuElement {
public Component getComponent();
public MenuElement[] getSubElements();
public void menuSelectionChanged(boolean isInclude);
public void processKeyEvent(KeyEvent event, MenuElement path[],
MenuSelectionManager mgr);
public void processMouseEvent(MouseEvent event, MenuElement path[],
MenuSelectionManager mgr);
}
The getComponent() method returns the menu’s rendering component. This is usually the
menu component itself, although that isn’t a requirement. The getSubElements() method
returns an array of any menu elements contained within this element. If this menu element
isn’t the top of a submenu, the method should return a zero-length array of MenuElement
objects, not null.
The menuSelectionChanged() method is called whenever the menu item is placed in or
taken out of the selection path for the menu selection manager.
The two processKeyEvent() and processMouseEvent() methods are for processing a key
event or mouse event that’s generated over a menu. How your menu item processes events
depends on what the component supports. For instance, unless you support accelerators, you
probably want to respond to key events only when your menu item is in the current selection
path.
■Note If, for example, your new menu element was something like a JComboBoxMenuItem, where the
MenuElement acted like a JComboBox, the processKeyEvent() might pass along the key character to
the KeySelectionManager. See Chapter 13 for more on the KeySelectionManager.
To demonstrate the MenuElement interface, Listing 6-6 creates a new menu component
called a JToggleButtonMenuItem. This component will look and act like a JToggleButton, although
it can be on a menu. It’s important to ensure that the menu goes away once the item is selected
and that the component is displayed differently when in the current selection path.
195
196
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
■Note Although you can add any component to a menu, if the component doesn’t implement the MenuElement
interface, it won’t act properly when a mouse moves over the component or when the component is selected.
Listing 6-6. Toggle Button As Menu Item Class Definition
import
import
import
import
java.awt.*;
java.awt.event.*;
javax.swing.*;
javax.swing.event.*;
public class JToggleButtonMenuItem extends JToggleButton implements MenuElement {
Color savedForeground = null;
private static MenuElement NO_SUB_ELEMENTS[] = new MenuElement[0];
public JToggleButtonMenuItem() {
init();
}
public JToggleButtonMenuItem(String label) {
super(label);
init();
}
public JToggleButtonMenuItem(String label, Icon icon) {
super(label, icon);
init();
}
public JToggleButtonMenuItem(Action action) {
super(action);
init();
}
private void init() {
updateUI();
setRequestFocusEnabled(false);
// Borrows heavily from BasicMenuUI
MouseInputListener mouseInputListener = new MouseInputListener() {
// If mouse released over this menu item, activate it
public void mouseReleased(MouseEvent mouseEvent) {
MenuSelectionManager menuSelectionManager =
MenuSelectionManager.defaultManager();
Point point = mouseEvent.getPoint();
if ((point.x >= 0) &&
(point.x < getWidth()) &&
(point.y >= 0) &&
(point.y < getHeight())) {
menuSelectionManager.clearSelectedPath();
// Component automatically handles "selection" at this point
// doClick(0); // not necessary
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
} else {
menuSelectionManager.processMouseEvent(mouseEvent);
}
}
// If mouse moves over menu item, add to selection path, so it becomes armed
public void mouseEntered(MouseEvent mouseEvent) {
MenuSelectionManager menuSelectionManager =
MenuSelectionManager.defaultManager();
menuSelectionManager.setSelectedPath(getPath());
}
// When mouse moves away from menu item, disarm it and select something else
public void mouseExited(MouseEvent mouseEvent) {
MenuSelectionManager menuSelectionManager =
MenuSelectionManager.defaultManager();
MenuElement path[] = menuSelectionManager.getSelectedPath();
if (path.length > 1) {
MenuElement newPath[] = new MenuElement[path.length-1];
for(int i=0, c=path.length-1; i= 0;
newPathPosition--) {
if (oldPath[newPathPosition].getComponent() == parent) {
break;
}
}
newPath = new MenuElement[newPathPosition+2];
System.arraycopy(oldPath, 0, newPath, 0, newPathPosition+1);
newPath[newPathPosition+1] = this;
}
return newPath;
}
}
■Note The MouseInputListener defined in the init() method and the getPath() method borrow
heavily from the system BasicMenuUI class. Normally, the user interface delegate deals with what happens
when the mouse moves over a menu component. Because the JToggleButton isn’t a predefined menu
component, its UI class doesn’t deal with it. For better modularity, these two methods should be moved into
an extended ToggleButtonUI.
Once you’ve created this JToggleButtonMenuItem class, you can use it like any other
menu item:
JToggleButtonMenuItem toggleItem = new JToggleButtonMenuItem("Balloon Help");
editMenu.add(toggleItem);
199
200
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Working with Pop-Ups: The Popup Class
Not everything you want to pop up needs to be a menu. Through the Popup and PopupFactory
classes, you can pop up any component over another. This is different from tooltips, which are
in a read-only, unselectable label. You can pop up selectable buttons, trees, or tables.
Creating Pop-Up Components
Popup is a simple class with two methods—hide() and show()—with two protected constructors. Instead of creating Popup objects directly, you acquire them from the PopupFactory class.
PopupFactory factory = PopupFactory.getSharedInstance();
Popup popup = factory.getPopup(owner, contents, x, y);
The Popup with the contents component created by PopupFactory will thus be “above”
other components within the owner component.
A Complete Popup/PopupFactory Usage Example
Listing 6-7 demonstrates the usage of Popup and PopupFactory to show a JButton above another
JButton. Selecting the initial JButton will cause the second one to be created above the first, at
some random location. When the second button is visible, each is selectable. Selecting the initially
visible button multiple times will cause even more pop-up buttons to appear, as shown in
Figure 6-9. Each pop-up button will disappear after three seconds. In this example, selecting
the pop-up button just displays a message to the console.
Figure 6-9. Popup/PopupFactory example
Listing 6-7. The ButtonPopupSample Class Definition
import
import
import
import
java.awt.*;
java.awt.event.*;
javax.swing.*;
java.util.Random;
public class ButtonPopupSample {
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
static final Random random = new Random();
// Define ActionListener
static class ButtonActionListener implements ActionListener {
public void actionPerformed(ActionEvent actionEvent) {
System.out.println("Selected: " + actionEvent.getActionCommand());
}
}
// Define Show Popup ActionListener
static class ShowPopupActionListener implements ActionListener {
private Component component;
ShowPopupActionListener(Component component) {
this.component = component;
}
public synchronized void actionPerformed(ActionEvent actionEvent) {
JButton button = new JButton("Hello, World");
ActionListener listener = new ButtonActionListener();
button.addActionListener(listener);
PopupFactory factory = PopupFactory.getSharedInstance();
int x = random.nextInt(200);
int y = random.nextInt(200);
final Popup popup = factory.getPopup(component, button, x, y);
popup.show();
ActionListener hider = new ActionListener() {
public void actionPerformed(ActionEvent e) {
popup.hide();
}
};
// Hide popup in 3 seconds
Timer timer = new Timer(3000, hider);
timer.start();
}
}
public static void main(final String args[]) {
Runnable runner = new Runnable() {
public void run() {
// Create frame
JFrame frame = new JFrame("Button Popup Sample");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ActionListener actionListener = new ShowPopupActionListener(frame);
JButton start = new JButton("Pick Me for Popup");
start.addActionListener(actionListener);
frame.add(start);
201
202
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
frame.setSize(350, 250);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
Working with Toolbars: The JToolBar Class
Toolbars are an integral part of the main application windows in a modern user interface.
Toolbars provide users with easy access to the more commonly used commands, which are
usually buried within a hierarchical menuing structure. The Swing component that supports
this capability is the JToolBar.
The JToolBar is a specialized Swing container for holding components. This container can
then be used as a toolbar within your Java applet or application, with the potential for it to
be floating or draggable, outside the main window of the program. JToolBar is a very simple
component to use and understand.
Creating JToolBar Components
There are four constructors for creating JToolBar components:
public JToolBar()
JToolBar jToolBar = new JToolBar();
public JToolBar(int orientation)
JToolBar jToolBar = new JToolBar(JToolBar.VERTICAL);
public JToolBar(String name)
JToolBar jToolBar = new JToolBar("Window Title");
public JToolBar(String name,int orientation)
JToolBar jToolBar = new JToolBar("Window Title", ToolBar.VERTICAL);
By default, a toolbar is created in a horizontal direction. However, you can explicitly set the
orientation by using either of the JToolBar constants of HORIZONTAL and VERTICAL.
Also by default, toolbars are floatable. Therefore, if you create the toolbar with one orientation, the user could change its orientation while dragging the toolbar around outside the
window. When floating, the title will be visible on the toolbar’s frame.
Adding Components to a JToolBar
Once you have a JToolBar, you need to add components to it. Any Component can be added to
the toolbar. When dealing with horizontal toolbars, for aesthetic reasons, it’s best if the toolbar
components are all roughly the same height. For a vertical toolbar, it’s best if they’re roughly
the same width. There’s only one method defined by the JToolBar class for adding toolbar
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
items; the remaining methods, such as add(Component), are inherited from Container. In addition,
you can add a separator to a toolbar.
public JButton add(Action action);
public void addSeparator();
public void addSeparator(Dimension size);
When using the add(Action) method of JToolBar, the added Action is encapsulated within
a JButton object. This is different from adding actions to JMenu or JPopupMenu components, in
which JMenuItem objects are added instead. As with JMenu and JPopupMenu, adding an Action in
this fashion is discouraged in the Javadoc for the class. For separators, if you don’t specify the
size, the installed look and feel forces a default size setting.
■Note For more information about dealing with the Action interface, see Chapter 2 or the section “Using
Action Objects with Menus” earlier in this chapter.
To remove components from a toolbar, use the following method:
public void remove(Component component)
JToolBar Properties
The JToolBar class defines nine properties, which are listed in Table 6-15.
Table 6-15. JToolBar Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
borderPainted
boolean
Read-write bound
floatable
boolean
Read-write bound
layout
LayoutManager
Write-only
margin
Insets
Read-write bound
orientation
int
Read-write bound
rollover
boolean
Read-write bound
UI
ToolBarUI
Read-write
UIClassID
String
Read-only
By default, the border of a JToolBar is painted. If you don’t want the border painted, you
can set the borderPainted property to false. Without using the borderPainted property,
you would need to change the setting of the border property (inherited from the superclass
JComponent).
203
204
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
The orientation property can be set to only one of the HORIZONTAL or VERTICAL constants
of JToolBar. If another nonequivalent value is used, an IllegalArgumentException is thrown.
Changing the orientation changes the layout manager of the toolbar. If you directly change the
layout manager with setLayout(), changing the orientation will undo your layout change.
Consequently, it’s best not to manually change the layout manager of a JToolBar.
As previously mentioned, a toolbar is floatable by default. This means that a user can drag
the toolbar from where you place it and move it elsewhere. To drag a toolbar, the user selects
an empty part of it. The toolbar can than be left outside the original program window, floating
above the main window in its own window, or dropped onto another area of the original
program window. If the layout manager of the original window is BorderLayout, the droppable
areas are the edges of the layout manager without any components. (You can’t drop the toolbar
in the center of the window.) Otherwise, the toolbar would be dropped into the last spot of the
container. Figure 6-10 shows the different phases of the dragging and docking process.
Dragging Hot Spot
Docked in a Different Area
Figure 6-10. JToolBar phases
Floating
Undocked Floating Toolbar
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
The rollover property defines a behavior specific to the look and feel for when the user
moves the mouse over the different components within the toolbar. This behavior could
involve coloration or border differences.
Handling JToolBar Events
There are no events specific to the JToolBar. You need to attach listeners to each item on the
JToolBar that you want to respond to user interaction. Of course, JToolBar is a Container, so
you could listen to its events.
Customizing a JToolBar Look and Feel
Each installable Swing look and feel provides its own JToolBar appearance and set of default
UIResource values. Most of this appearance is controlled by the components actually within
the toolbar. Figure 6-11 shows the appearance of the JToolBar component for the preinstalled
set of look and feel types: Motif, Windows, and Ocean. Each toolbar has five JButton components, with a separator between the fourth and fifth.
Motif
Windows
Ocean
Figure 6-11. JToolBar under different look and feel types
The available set of UIResource-related properties for a JToolBar is shown in Table 6-16.
For the JToolBar component, there are 22 different properties.
Table 6-16. JToolBar UIResource Elements
Property String
Object Type
ToolBar.actionMap
ActionMap
ToolBar.ancestorInputMap
InputMap
ToolBar.background
Color
205
206
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Table 6-16. JToolBar UIResource Elements (Continued)
Property String
Object Type
ToolBar.border
Border
ToolBar.borderColor
Color
ToolBar.darkShadow
Color
ToolBar.dockingBackground
Color
ToolBar.dockingForeground
Color
ToolBar.floatingBackground
Color
ToolBar.floatingForeground
Color
ToolBar.font
Font
ToolBar.foreground
Color
ToolBar.handleIcon
Icon
ToolBar.highlight
Color
ToolBar.isRollover
Boolean
ToolBar.light
Color
ToolBar.nonrolloverBorder
Border
ToolBar.rolloverBorder
Border
ToolBar.separatorSize
Dimension
ToolBar.shadow
Color
ToolBarSeparatorUI
String
ToolBarUI
String
A Complete JToolBar Usage Example
The program in Listing 6-8 demonstrates a complete JToolBar example that results in a toolbar
with a series of diamonds on the buttons. The program also reuses the ShowAction defined for
the menuing example, presented in Listing 6-2 earlier in this chapter.
The rollover property is enabled to demonstrate the difference for the current look and
feel. See Figure 6-12 for the output as you move your mouse over the different buttons.
Figure 6-12. JToolBar example with isRollover enabled
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Listing 6-8. The ToolBarSample Class Definition
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ToolBarSample {
private static final int COLOR_POSITION = 0;
private static final int STRING_POSITION = 1;
static Object buttonColors[][] = {
{Color.RED, "RED"},
{Color.BLUE, "BLUE"},
{Color.GREEN, "GREEN"},
{Color.BLACK, "BLACK"},
null, // separator
{Color.CYAN, "CYAN"}
};
public static class TheActionListener implements ActionListener {
public void actionPerformed (ActionEvent actionEvent) {
System.out.println(actionEvent.getActionCommand());
}
};
public static void main(final String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("JToolBar Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
ActionListener actionListener = new TheActionListener();
JToolBar toolbar = new JToolBar();
toolbar.setRollover(true);
for (Object[] color: buttonColors) {
if (color == null) {
toolbar.addSeparator();
} else {
Icon icon = new DiamondIcon((Color)color[COLOR_POSITION], true, 20, 20);
JButton button = new JButton(icon);
button.setActionCommand((String)color[STRING_POSITION]);
button.addActionListener(actionListener);
toolbar.add(button);
}
}
207
208
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Action action = new ShowAction(frame);
JButton button = new JButton(action);
toolbar.add(button);
Container contentPane = frame.getContentPane();
contentPane.add(toolbar, BorderLayout.NORTH);
JTextArea textArea = new JTextArea();
JScrollPane pane = new JScrollPane(textArea);
contentPane.add(pane, BorderLayout.CENTER);
frame.setSize(350, 150);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
JToolBar.Separator Class
The JToolBar class maintains its own separator to permit a custom look and feel for the
separator when on a JToolBar.
This separator is automatically created when you call the addSeparator() method of
JToolBar. In addition, there are two constructors for creating a JToolBar.Separator if you
want to manually create the component.
public JToolBar.Separator()
JSeparator toolBarSeparator = new JToolBar.Separator();
public JToolBar.Separator(Dimension size)
Dimension dimension = new Dimension(10, 10);
JSeparator toolBarSeparator = new JToolBar.Separator(dimension);
Both constructors create a horizontal separator. You can configure the size. If you don’t
specify this, the look and feel decides what size to make the separator.
As with JPopupMenu.Separator, if you want to change the orientation of the separator, you
must call the setOrientation() method inherited from JSeparator, this time with an argument
of JToolBar.Separator.VERTICAL.
Summary
This chapter introduced the many Swing menu-related classes and their interrelationships,
and Swing’s toolbar class. First, you learned about the JMenuBar and its selection model, and
learned how menu bars can be used within applets as well as applications.
Next, you explored the JMenuItem, which is the menu element the user selects, along with two
new event/listener pairs the system uses for dealing with events, MenuKeyEvent/MenuKeyListener
and MenuDragMouseEvent/MenuDragMouseListener. Then, you moved on to the JMenu component, upon which JMenuItem instances are placed, along with its new event/listener pair,
MenuEvent/MenuListener, which is used to determine when a menu is about to be posted.
CHAPTER 6 ■ SWING MENUS AND TOOLBARS
Next, you learned about the JSeparator component and how you can use it as a menu
separator or as a visual display separator outside of menus.
You then explored the JPopupMenu, which JMenu uses to display its set of JMenuItem components. For the JPopupMenu, you learned about the pop-up menu’s own event/listener pair,
PopupMenuEvent/PopupMenuListener.
Then the selectable menu elements in JCheckBoxMenuItem and JRadioButtonMenuItem were
explored with their MenuElement interface, and you saw how to create a custom menu component.
Menus aren’t the only things that might pop up, so you explored Popup and PopupFactory.
Finally, the chapter covered the JToolBar class, a close cousin of Swing’s menu classes.
In Chapter 7, you’ll look at the different classes Swing provides for customizing the border
around a Swing component.
209
CHAPTER 7
■■■
Borders
S
wing components offer the option of customizing the border area surrounding that component.
With great ease, you can use any one of the eight predefined borders (including one compound
border that is a combination of any of the other seven), or you can create your own individualized borders. In this chapter, you’ll learn how to best use each of the existing borders and how
to fashion your own.
Some Basics on Working with Borders
A border is a JComponent property with the standard setBorder() and getBorder() property
methods. Therefore, every Swing component that is a subclass of JComponent can have a border.
By default, a component doesn’t have a custom border associated with it. (The getBorder()
method of JComponent returns null.) Instead, the default border displayed for a component is
the border appropriate for its state, based on the current look and feel. For instance, with a
JButton, the border could appear pressed, unpressed, or disabled, with specific different borders
for each look and feel (Metal, Windows, and so on).
Although the initial border property setting for every component is null, you can change
the border of a component by calling the setBorder(Border newValue) method of JComponent.
Once set, the changed value overrides the border for the current look and feel, and it draws the
new border in the area of the component’s insets. If at a later time, you want to reset the border
back to a border that’s appropriate for the state as well as the look and feel, change the border
property to null, using setBorder(null), and call updateUI() for the component. The updateUI()
call notifies the look and feel to reset the border. If you don’t call updateUI(), the component
will have no border.
■Note Those Swing components that aren’t subclasses of JComponent, such as JApplet and JFrame,
lack a setBorder() method to change their border. If you want them to have a border, you must add a
JPanel or other Swing component to the container, and then change the border of that component.
211
212
CHAPTER 7 ■ BORDERS
Examine Figure 7-1 to see a sampling of the various border configurations around a JLabel,
with a text label designating the border type. How to create the different borders will be discussed
in later sections of this chapter.
Figure 7-1. Border examples, using a 4-by-2 GridLayout with 5-pixel horizontal and vertical gaps
Exploring the Border Interface
The Border interface can be found in the javax.swing.border package. This interface forms the
basis of all the border classes. The interface is directly implemented by the AbstractBorder
class, which is the parent class of all the predefined Swing border classes: BevelBorder,
CompoundBorder, EmptyBorder, EtchedBorder, LineBorder, MatteBorder, SoftBevelBorder, and
TitledBorder. Of additional interest is the BorderFactory class, found in the javax.swing
package. This class uses the Factory design pattern to create borders, hiding the details of the
concrete implementations and caching various operations to optimize shared usages.
The Border interface shown here consists of three methods: paintBorder(),
getBorderInsets(), and isBorderOpaque(). These methods are described in the following
sections.
paintBorder()
The paintBorder() method is the key method of the interface. It has the following definition:
public void paintBorder(Component c, Graphics g, int x, int y, int
width, int height)
The actual drawing of the border is done in this method. Frequently, the Border implementation will ask for the Insets dimensions first, and then draw the border in the four
rectangular outer regions, as shown in Figure 7-2. If a border is opaque, the paintBorder()
implementation must fill the entire insets area. If a border is opaque and doesn’t fill the area,
then it’s a bug and needs to be corrected.
CHAPTER 7 ■ BORDERS
Figure 7-2. Areas of border insets
Listing 7-1 shows a simple paintBorder() implementation that fills in the left and right
sides with a brighter color than the top and bottom.
Listing 7-1. Filled-in Border Inset Areas
public void paintBorder(Component c, Graphics g, int x, int y, int width,
int height) {
Insets insets = getBorderInsets(c);
Color color = c.getForeground();
Color brighterColor = color.brighter();
// Translate coordinate space
g.translate(x, y);
// Top
g.setColor(color);
g.fillRect(0, 0, width, insets.top);
// Left
g.setColor(brighterColor);
g.fillRect(0, insets.top, insets.left, height-insets.top-insets.bottom);
// Bottom
g.setColor(color);
g.fillRect(0, height-insets.bottom, width, insets.bottom);
213
214
CHAPTER 7 ■ BORDERS
// Right
g.setColor(brighterColor);
g.fillRect(width-insets.right, insets.top, insets.right,
height-insets.top-insets.bottom);
// Translate coordinate space back
g.translate(-x, -y);
}
When creating your own borders, you’ll frequently find yourself filling in the same nonoverlapping rectangular regions. The use of the translate() method of Graphics simplifies the
specification of the drawing coordinates. Without translating the coordinates, you would need
to offset the drawing by the origin (x, y).
■Caution You cannot take a shortcut by inserting g.fillRect(x, y, width, height), because this
would fill in the entire component area, not just the border area.
getBorderInsets()
The getBorderInsets() method returns the space necessary to draw a border around the given
component c as an Insets object. It has the following definition:
public Insets getBorderInsets(Component c)
These inset areas, shown in Figure 7-2, define the only legal area in which a border can be
drawn. The Component argument allows you to use some of its properties to determine the size
of the insets area.
■Caution You can ask the component argument for font-sizing information to determine the insets’ size,
but if you ask about the size of the component, a StackOverflowError occurs because the size of the
component is dependent on the size of the border insets.
isBorderOpaque()
Borders can be opaque or transparent. The isBorderOpaque() method returns true or false, to
indicate which form the border is. It has the following definition:
public boolean isBorderOpaque()
CHAPTER 7 ■ BORDERS
When this method returns true, the border needs to be opaque, filling its entire insets
area. When it returns false, any area not drawn will retain the background of the component
in which the border is installed.
Introducing BorderFactory
Now that you have a basic understanding of how the Border interface works, let’s take a quick
look at the BorderFactory class as a means to create borders swiftly and easily. Found in the
javax.swing package, the BorderFactory class offers a series of static methods for creating
predefined borders. Instead of laboriously calling the specific constructors for different borders,
you can create almost all the borders through this factory class. The factory class also caches
the creation of some borders to avoid re-creating commonly used borders multiple times. The
class definition follows.
public class BorderFactory {
public static Border createBevelBorder(int
public static Border createBevelBorder(int
Color shadow);
public static Border createBevelBorder(int
Color highlightInner, Color shadowOuter,
type);
type, Color highlight,
type, Color highlightOuter,
Color shadowInner);
public static CompoundBorder createCompoundBorder();
public static CompoundBorder createCompoundBorder(Border outside,
Border inside);
public static Border createEmptyBorder();
public static Border createEmptyBorder(int top, int left, int bottom,
int right);
public static Border
public static Border
public static Border
public static Border
Color shadow);
createEtchedBorder();
createEtchedBorder(Color highlight, Color shadow);
createEtchedBorder(int type);
createEtchedBorder(int type, Color highlight,
public static Border createLineBorder(Color color);
public static Border createLineBorder(Color color, int thickness);
public static Border createLoweredBevelBorder();
public static MatteBorder createMatteBorder(int top, int left, int bottom,
int right, Color color);
public static MatteBorder createMatteBorder(int top, int left, int bottom,
int right, Icon icon);
215
216
CHAPTER 7 ■ BORDERS
public static Border createRaisedBevelBorder();
public static TitledBorder createTitledBorder(Border border);
public static TitledBorder createTitledBorder(Border border, String
public static TitledBorder createTitledBorder(Border border, String
int justification, int position);
public static TitledBorder createTitledBorder(Border border, String
int justification, int position, Font font);
public static TitledBorder createTitledBorder(Border border, String
int justification, int position, Font font, Color color);
public static TitledBorder createTitledBorder(String title);
title);
title,
title,
title,
}
I’ll describe the different methods of this class during the process of describing the specific
border types they create. For instance, to create a border with a red line, you can use the following
statement, and then attach the border to a component.
Border lineBorder = BorderFactory.createLineBorder(Color.RED);
■Note Interestingly enough, no factory method exists for creating a SoftBevelBorder.
Starting with AbstractBorder
Before looking at the individual borders available within the javax.swing.border package,
one system border deserves special attention: AbstractBorder. As previously mentioned, the
AbstractBorder class is the parent border of all the other predefined borders.
■Tip When creating your own borders, you should create a subclass of AbstractBorder and just override
the necessary methods, instead of implementing the Border interface directly yourself. There are some
internal optimizations in place for subclasses.
Creating Abstract Borders
There is one constructor for AbstractBorder:
public AbstractBorder()
Because AbstractBorder is the parent class of all the other standard borders, this constructor is
eventually called automatically for all of them.
CHAPTER 7 ■ BORDERS
■Note Borders are not meant to be used as JavaBean components. Some border classes even lack a
no-argument (“no-arg” for short) constructor. Nevertheless, those border classes still call this constructor.
Examining AbstractBorder Methods
The AbstractBorder class provides implementations for the three methods of the Border
interface.
public Insets getBorderInsets(Component c)
The insets of an AbstractBorder are zero all around. Each of the predefined subclasses
overrides the getBorderInsets() method.
public boolean isBorderOpaque()
The default opaque property setting of an abstract border is false. This means that if you
were to draw something like dashed lines, the component background would show through.
Many predefined subclasses override the isBorderOpaque() method.
public void paintBorder(Component c, Graphics g, int x, int y,
int width, int height)
The painted border for an AbstractBorder is empty. All subclasses should override this
behavior to actually draw a border, except perhaps EmptyBorder.
In addition to providing default implementations of the Border methods, AbstractBorder
adds two other capabilities that you can take advantage of, or just let the system use. First,
there’s an additional version of getBorderInsets() available that takes two arguments:
Component and Insets:
public Insets getBorderInsets(Component c, Insets insets)
In this version of the method, instead of creating and returning a new Insets object, the
Insets object passed in is first modified and then returned. Use of this method avoids the creation
and later destruction of an additional Insets object each time the border insets is queried.
The second new method available is getInteriorRectangle(), which has both a static and
a nonstatic version. Given the Component, Border, and four integer parameters (for x, y, width,
and height), the method will return the inner Rectangle such that a component can paint itself
only in the area within the border insets. (See the piece labeled “Component” in Figure 7-2,
shown earlier in the chapter.)
■Note Currently, getBorderInsets() is used only once in Sun’s Swing source. That place is the
MotifButtonUI class found in the com.sun.java.swing.plaf.motif package.
217
218
CHAPTER 7 ■ BORDERS
Examining the Predefined Borders
Now that the basics have been described, let’s look at the specifics of each of the predefined
border classes, somewhat in order of complexity.
EmptyBorder Class
The empty border, logically enough, is a border with nothing drawn in it. You can use
EmptyBorder where you might have otherwise overridden insets() or getInsets() with a
regular AWT container. It allows you to reserve extra space around a component to spread your
screen components out a little or to alter centering or justification somewhat. Figure 7-3 shows
both an empty border and one that is not empty.
Figure 7-3. EmptyBorder sample, with insets of 20 for top and left, 0 for right and bottom
EmptyBorder has two constructors and two factory methods of BorderFactory:
public static Border createEmptyBorder()
Border emptyBorder = BorderFactory.createEmptyBorder();
public static Border createEmptyBorder(int top, int left, int bottom, int right)
Border emptyBorder = BorderFactory.createEmptyBorder(5, 10, 5, 10);
public EmptyBorder(Insets insets)
Insets insets = new Insets(5, 10, 5, 10);
Border EmptyBorder = new EmptyBorder(insets);
public EmptyBorder(int top, int left, int bottom, int right)
Border EmptyBorder = new EmptyBorder(5, 10, 5, 10);
Each allows you to customize the border insets in its own manner. The no-argument
version creates a truly empty border with zero insets all around; otherwise, you can specify the
insets as either an AWT Insets instance or as the inset pieces. The EmptyBorder is transparent
by default.
CHAPTER 7 ■ BORDERS
■Note When creating an empty border, with zeros all around, you should use the factory method to create
the border, avoiding the direct constructors. This allows the factory to create one truly empty border to be
shared by all. If all you want to do is hide the border, and the component is an AbstractButton subclass,
just call setBorderPainted(false).
LineBorder Class
The line border is a single-color line of a user-defined thickness that surrounds a component.
It can have squared-off or rounded corners. If you want to alter the thickness on different sides,
you’ll need to use MatteBorder, which is described in the section “Matte Border Class” later in
this chapter. Figure 7-4 shows a sampling of using LineBorder, with 1- and 12-pixel line thicknesses, with and without rounded corners.
Figure 7-4. LineBorder sample
Creating Line Borders
The LineBorder class has three constructors, two factory methods within it, and two factory
methods of BorderFactory:
public LineBorder(Color color)
Border lineBorder = new LineBorder (Color.RED);
public LineBorder(Color color, int thickness)
Border lineBorder = new LineBorder (Color.RED, 5);
public LineBorder (Color color, int thickness, boolean roundedCorners)
Border lineBorder = new LineBorder (Color.RED, 5, true);
public static Border createBlackLineBorder()
Border blackLine = LineBorder.createBlackLineBorder();
219
220
CHAPTER 7 ■ BORDERS
public static Border createGrayLineBorder()
Border grayLine = LineBorder.createGrayLineBorder();
public static Border createLineBorder(Color color)
Border lineBorder = BorderFactory.createLineBorder(Color.RED);
public static Border createLineBorder(Color color, int thickness)
Border lineBorder = BorderFactory.createLineBorder(Color.RED, 5);
■Note The LineBorder factory methods work as follows: If you create the same border twice, the same
LineBorder object will be returned. However, as with all object comparisons, you should always use the
equals() method for checking object equality.
Each allows you to customize the border color and line thickness. If a thickness isn’t specified, a default value of 1 is used. The two factory methods of LineBorder are for the commonly
used colors of black and gray. Because the border fills in the entire insets area, the LineBorder
is opaque, unless there are rounded corners. So, the opacity of the border is the opposite of the
rounded-corner setting.
Configuring Line Border Properties
Table 7-1 lists the inherited borderOpaque property from AbstractBorder and the immutable
properties of LineBorder.
Table 7-1. LineBorder Properties
Property Name
Data Type
Access
borderOpaque
boolean
Read-only
lineColor
Color
Read-only
roundedCorners
boolean
Read-only
thickness
int
Read-only
BevelBorder Class
A bevel border draws a border with a three-dimensional appearance, which can appear to be
raised or lowered. When the border is raised, a shadow effect appears along the bottom and
right side of the border. When lowered, the position of the shading is reversed. Figure 7-5
shows raised and lowered bevel borders with default and custom colors.
CHAPTER 7 ■ BORDERS
Figure 7-5. Raised and lowered BevelBorder sample
Drawing two different pairs of 1-pixel-wide lines around the component produces a simulated three-dimensional appearance. The border sides that aren’t shaded are drawn with what
is called a highlight color, and the other two sides are drawn with a shadow color. The highlight
color and shadow color are each drawn in two different shades for the outer and inner edges of
the bevel. As such, a drawn bevel border uses four different colors in all. Figure 7-6 shows how
these four colors fit together.
Figure 7-6. Bevel color analysis
There are three constructors and one factory method of BevelBorder, as well as five factory
methods by which BorderFactory creates BevelBorder objects:
public BevelBorder(int bevelType)
Border bevelBorder = new BevelBorder(BevelBorder.RAISED);
public static Border createBevelBorder(int bevelType)
Border bevelBorder = BorderFactory.createBevelBorder(BevelBorder.RAISED);
public static Border createLoweredBevelBorder()
Border bevelBorder = BorderFactory.createLoweredBevelBorder();
221
222
CHAPTER 7 ■ BORDERS
public static Border createRaisedBevelBorder()
Border bevelBorder = BorderFactory.createRaisedBevelBorder();
public BevelBorder(int bevelType, Color highlight, Color shadow)
Border bevelBorder = new BevelBorder(BevelBorder.RAISED, Color.PINK, Color.RED);
public static Border createBevelBorder(int bevelType, Color highlight, Color shadow)
Border bevelBorder = BorderFactory.createBevelBorder(BevelBorder.RAISED,
Color.PINK, Color.RED);
public BevelBorder(int bevelType, Color highlightOuter, Color highlightInner,
Color shadowOuter, Color shadowInner)
Border bevelBorder = new BevelBorder(BevelBorder.RAISED, Color.PINK,
Color.PINK.brighter(), Color.RED, Color.RED.darker());
public static Border createBevelBorder(int bevelType, Color highlightOuter,
Color highlightInner, Color shadowOuter, Color shadowInner)
Border bevelBorder = BorderFactory.createBevelBorder(BevelBorder.RAISED,
Color.PINK, Color.PINK.brighter(), Color.RED, Color.RED.darker());
Each allows you to customize both the bevel type and the coloration of the highlighting
and shadowing within the border. The bevel type is specified by one of two values:
BevelBorder.RAISED or BevelBorder.LOWERED. If highlight and shadow colors aren’t specified,
the appropriate colors are generated by examining the background of the component for the
border. If you do specify them, remember that the highlight color should be brighter, possibly
done by calling theColor.brighter(). A BevelBorder is opaque, by default.
SoftBevelBorder Class
The soft bevel border is a close cousin of the bevel border. It rounds out the corners so that
their edges aren’t as sharp, and it draws only one line, using the appropriate outer color for the
bottom and right sides. As Figure 7-7 shows, the basic appearance of the raised and lowered
SoftBevelBorder is roughly the same as that of the BevelBorder.
Figure 7-7. Raised and lowered SoftBevelBorder sample
CHAPTER 7 ■ BORDERS
SoftBevelBorder has three constructors:
public SoftBevelBorder(int bevelType)
Border softBevelBorder = new SoftBevelBorder(SoftBevelBorder.RAISED);
public SoftBevelBorder(int bevelType, Color highlight, Color shadow)
Border softBevelBorder = new SoftBevelBorder(SoftBevelBorder.RAISED, Color.RED,
Color.PINK);
public SoftBevelBorder(int bevelType, Color highlightOuter, Color highlightInner,
Color shadowOuter, Color shadowInner)
Border softBevelBorder = new SoftBevelBorder(SoftBevelBorder.RAISED, Color.RED,
Color.RED.darker(), Color.PINK, Color.PINK.brighter());
Each allows you to customize both the bevel type and the coloration of the highlighting
and shadowing within the border. The bevel type is specified by one of two values:
SoftBevelBorder.RAISED or SoftBevelBorder.LOWERED. As with BevelBorder, the default coloration is derived from the background color. A soft bevel border doesn’t completely fill in the
given insets area, so a SoftBevelBorder is created to be transparent (not opaque).
There are no static BorderFactory methods to create these borders.
EtchedBorder Class
An EtchedBorder is a special case of a BevelBorder, but it’s not a subclass. When the outer highlight color of a BevelBorder is the same color as the inner shadow color and the outer shadow
color is the same color as the inner highlight color, you have an EtchedBorder. (See Figure 7-6
earlier in this chapter for a depiction of bevel colors.) Figure 7-8 shows what a raised and
lowered etched border might look like.
Figure 7-8. EtchedBorder samples
There are four constructors for EtchedBorder, as well as four factory methods of
BorderFactory for creating EtchedBorder objects:
223
224
CHAPTER 7 ■ BORDERS
public EtchedBorder()
Border etchedBorder = new EtchedBorder();
public EtchedBorder(int etchType)
Border etchedBorder = new EtchedBorder(EtchedBorder.RAISED);
public EtchedBorder(Color highlight, Color shadow)
Border etchedBorder = new EtchedBorder(Color.RED, Color.PINK);
public EtchedBorder(int etchType, Color highlight, Color shadow)
Border etchedBorder = new EtchedBorder(EtchedBorder.RAISED, Color.RED,
Color.PINK);
public static Border createEtchedBorder()
Border etchedBorder = BorderFactory.createEtchedBorder();
public static Border createEtchedBorder(Color highlight, Color shadow)
Border etchedBorder = BorderFactory.createEtchedBorder(Color.RED, Color.PINK);
public static Border createEtchedBorder(EtchedBorder.RAISED)
Border etchedBorder = BorderFactory.createEtchedBorder(Color.RED, Color.PINK);
public static Border createEtchedBorder(int type, Color highlight, Color shadow)
Border etchedBorder = BorderFactory.createEtchedBorder(EtchedBorder.RAISED,
Color.RED, Color.PINK);
Each allows you to customize both the etching type and the coloration of the highlighting
and shadowing within the border. If no etching type is specified, the border is lowered. As with
BevelBorder and SoftBevelBorder, you can specify the etching type through one of two constants:
EtchedBorder.RAISED or EtchedBorder.LOWERED. Again, if no colors are specified, they’re derived
from the background color of the component passed into paintBorder(). By default, all
EtchedBorder objects are created to be opaque.
MatteBorder Class
MatteBorder is one of the more versatile borders available. It comes in two varieties. The first is
demonstrated in Figure 7-9 and shows a MatteBorder used like a LineBorder to fill the border
with a specific color, but with a different thickness on each side (something a plain LineBorder
cannot handle).
CHAPTER 7 ■ BORDERS
Figure 7-9. MatteBorder color sample
The second variety uses an Icon tiled throughout the border area. This Icon could be an
ImageIcon, if created from an Image object, or it could be one you create yourself by implementing the Icon interface. Figure 7-10 demonstrates both implementations.
Figure 7-10. MatteBorder icon samples
■Tip When tiling an icon, the right and bottom areas may not look very attractive if the border size,
component size, and icon size fail to mesh well.
There are seven constructors and two factory methods of BorderFactory for creating
MatteBorder objects:
public MatteBorder(int top, int left, int bottom, int right, Color color)
Border matteBorder = new MatteBorder(5, 10, 5, 10, Color.GREEN);
public MatteBorder(int top, int left, int bottom, int right, Icon icon)
Icon diamondIcon = new DiamondIcon(Color.RED);
Border matteBorder = new MatteBorder(5, 10, 5, 10, diamondIcon);
public MatteBorder(Icon icon)
Icon diamondIcon = new DiamondIcon(Color.RED);
Border matteBorder = new MatteBorder(diamondIcon);
225
226
CHAPTER 7 ■ BORDERS
public MatteBorder(Insets insets, Color color)
Insets insets = new Insets(5, 10, 5, 10);
Border matteBorder = new MatteBorder(insets, Color.RED);
public MatteBorder(Insets insets, Icon icon)
Insets insets = new Insets(5, 10, 5, 10);
Icon diamondIcon = new DiamondIcon(Color.RED);
Border matteBorder = new MatteBorder(insets, diamondIcon);
public static MatteBorder createMatteBorder(int top, int left, int bottom,
int right, Color color)
Border matteBorder = BorderFactory.createMatteBorder(5, 10, 5, 10, Color.GREEN);
public static MatteBorder createMatteBorder(int top, int left, int bottom,
int right, Icon icon)
Icon diamondIcon = new DiamondIcon(Color.RED);
Border matteBorder = BorderFactory.createMatteBorder(5, 10, 5, 10, diamondIcon);
Each allows you to customize what will be matted within the border area. When tiling an
Icon, if you don’t specify the border insets size, the actual icon dimensions will be used.
CompoundBorder Class
After EmptyBorder, the compound border is probably one of the simplest predefined borders to
use. It takes two existing borders and combines them, using the Composite design pattern, into
a single border. A Swing component can have only one border associated with it, therefore, the
CompoundBorder allows you to combine borders before associating them with a component.
Figure 7-11 shows two examples of CompoundBorder in action. The border on the left is a beveled,
line border. The one on the right is a six-line border, with several borders combined together.
Figure 7-11. CompoundBorder samples
Creating Compound Borders
There are two constructors for CompoundBorder and two factory methods that BorderFactory
offers for creating CompoundBorder objects (the no-argument constructor and factory methods
are completely useless here, because there are no setter methods to later change the compounded
borders, so no source examples are shown for them):
CHAPTER 7 ■ BORDERS
public CompoundBorder()
public static CompoundBorder createCompoundBorder()
public CompoundBorder(Border outside, Border inside)
Border compoundBorder = new CompoundBorder(lineBorder, matteBorder);
public static CompoundBorder createCompoundBorder(Border outside, Border inside)
Border compoundBorder = BorderFactory.createCompoundBorder(lineBorder,
matteBorder);
■Tip Keep in mind that CompoundBorder is itself a Border, so you can combine multiple borders into one
border many levels deep.
The opacity of a compound border depends on the opacity of the contained borders.
If both contained borders are opaque, so is the compound border. Otherwise, a compound
border is considered transparent.
Configuring Properties
In addition to the borderOpaque property inherited from AbstractBorder, Table 7-2 lists the two
read-only properties CompoundBorder adds.
Table 7-2. CompoundBorder Properties
Property Name
Data Type
Access
borderOpaque
boolean
Read-only
insideBorder
Border
Read-only
outsideBorder
Border
Read-only
TitledBorder Class
Probably the most interesting border, TitledBorder can also be the most complicated to use. The
titled border allows you to place a text string around a component. In addition to surrounding a
single component, you can place a titled border around a group of components, like JRadioButton
objects, as long as they’re placed within a container such as a JPanel. The TitledBorder can be
difficult to use, but there are several ways to simplify its usage. Figure 7-12 shows both a simple
titled border and one that’s a little more complex.
227
228
CHAPTER 7 ■ BORDERS
Figure 7-12. TitledBorder samples
Creating Titled Borders
Six constructors and six BorderFactory factory methods exist for creating TitledBorder objects.
Each allows you to customize the text, position, and appearance of a title within a specified
border. When unspecified, the current look and feel controls the border, title color, and title
font. The default location for the title is the upper-left corner, while the default title is the
empty string. A titled border is always at least partially transparent because the area beneath
the title text shows through. Therefore, isBorderOpaque() reports false.
If you look at each of the following methods, shown in pairs, this will be easier to understand.
First shown is the constructor method; next shown is the equivalent BorderFactory method.
public TitledBorder(Border border)
Border titledBorder = new TitledBorder(lineBorder);
public static TitledBorder createTitledBorder(Border border)
Border titledBorder = BorderFactory.createTitledBorder(lineBorder);
public TitledBorder(String title)
Border titledBorder = new TitledBorder("Hello");
public static TitledBorder createTitledBorder(String title)
Border titledBorder = BorderFactory.createTitledBorder("Hello");
public TitledBorder(Border border, String title)
Border titledBorder = new TitledBorder(lineBorder, "Hello");
public static TitledBorder createTitledBorder(Border border, String title)
Border titledBorder = BorderFactory.createTitledBorder(lineBorder, "Hello");
public TitledBorder(Border border, String title, int justification, int position)
Border titledBorder = new TitledBorder(lineBorder, "Hello", TitledBorder.LEFT,
TitledBorder.BELOW_BOTTOM);
CHAPTER 7 ■ BORDERS
public static TitledBorder createTitledBorder(Border border, String title,
int justification, int position)
Border titledBorder = BorderFactory.createTitledBorder(lineBorder, "Hello",
TitledBorder.LEFT, TitledBorder.BELOW_BOTTOM);
public TitledBorder(Border border, String title, int justification, int position,
Font font)
Font font = new Font("Serif", Font.ITALIC, 12);
Border titledBorder = new TitledBorder(lineBorder, "Hello", TitledBorder.LEFT,
TitledBorder.BELOW_BOTTOM, font);
public static TitledBorder createTitledBorder(Border border, String title,
int justification, int position, Font font)
Font font = new Font("Serif", Font.ITALIC, 12);
Border titledBorder = BorderFactory.createTitledBorder(lineBorder, "Hello",
TitledBorder.LEFT, TitledBorder.BELOW_BOTTOM, font);
public TitledBorder(Border border, String title, int justification, int position,
Font font, Color color)
Font font = new Font("Serif", Font.ITALIC, 12);
Border titledBorder = new TitledBorder(lineBorder, "Hello", TitledBorder.LEFT,
TitledBorder.BELOW_BOTTOM, font, Color.RED);
public static TitledBorder createTitledBorder(Border border, String title,
int justification, int position, Font font, Color color)
Font font = new Font("Serif", Font.ITALIC, 12);
Border titledBorder = BorderFactory.createTitledBorder(lineBorder, "Hello",
TitledBorder.LEFT, TitledBorder.BELOW_BOTTOM, font, Color.RED);
Configuring Properties
Unlike all the other predefined borders, titled borders have six setter methods to modify their
attributes after border creation. As shown in Table 7-3, you can modify a titled border’s underlying border, title, drawing color, font, text justification, and text position.
Table 7-3. TitledBorder Properties
Property Name
Data Type
Access
border
Border
Read-write
borderOpaque
boolean
Read-only
title
String
Read-write
titleColor
Color
Read-write
titleFont
Font
Read-write
titleJustification
int
Read-write
titlePosition
int
Read-write
229
230
CHAPTER 7 ■ BORDERS
■Tip To reduce screen redrawing, it’s better to modify the properties of a titled border prior to placing the
border around a component.
Text justification of the title string within a TitledBorder is specified by one of four class
constants:
• CENTER: Place the title in the center.
• DEFAULT_JUSTIFICATION: Use the default setting to position the text. The value is equivalent
to LEFT.
• LEFT: Place the title on the left edge.
• RIGHT: Place the title on the right edge.
Figure 7-13 shows the same TitledBorder with three different justifications.
Figure 7-13. Title justifications
You can position title strings in any one of six different locations, as specified by one of
seven class constants:
• ABOVE_BOTTOM: Place the title above the bottom line.
• ABOVE_TOP: Place the title above the top line.
• BELOW_BOTTOM: Place the title below the bottom line.
• BELOW_TOP: Place the title below the top line.
• BOTTOM: Place the title on the bottom line.
• DEFAULT_POSITION: Use the default setting to place the text. This value is equivalent to TOP.
• TOP: Place the title on the top line.
Figure 7-14 shows the six different positions available for the title on a TitledBorder.
CHAPTER 7 ■ BORDERS
Figure 7-14. Title positioning
Because a TitledBorder contains another Border, you can combine more than one border
to place multiple titles along a single border. For example, Figure 7-15 shows a title along the
top and bottom of the border.
Figure 7-15. Showing multiple titles on a TitledBorder
The program used to generate Figure 7-15 is shown in Listing 7-2.
Listing 7-2. Multiple Titles on a TitledBorder
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
public class DoubleTitle {
public static void main(String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("Double Title");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
TitledBorder topBorder = BorderFactory.createTitledBorder("Top");
topBorder.setTitlePosition(TitledBorder.TOP);
TitledBorder doubleBorder = new TitledBorder(topBorder, "Bottom",
TitledBorder.RIGHT, TitledBorder.BOTTOM);
JButton doubleButton = new JButton();
doubleButton.setBorder(doubleBorder);
frame.add(doubleButton, BorderLayout.CENTER);
231
232
CHAPTER 7 ■ BORDERS
frame.setSize(300, 100);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
Customizing TitledBorder Look and Feel
The available set of UIResource-related properties for a TitledBorder is shown in Table 7-4.
It has three different properties.
Table 7-4. TitledBorder UIResource Elements
Property String
Object Type
TitledBorder.font
Font
TitledBorder.titleColor
Color
TitledBorder.border
Border
Creating Your Own Borders
When you want to create your own distinctive border, you can either create a new class that
implements the Border interface directly or you can extend the AbstractBorder class. As previously mentioned, extending the AbstractBorder class is the better way to go, because optimizations
are built in to certain Swing classes to take advantage of some of the AbstractBorder-specific
methods. For instance, if a border is an AbstractBorder, JComponent will reuse an Insets object
when getting the Insets of a border. Thus, one fewer object will need to be created and destroyed
each time the insets are fetched.
In addition to thinking about subclassing AbstractBorder versus implementing the Border
interface yourself, you need to consider whether or not you want a static border. If you attach
a border to a button, you want that button to be able to signal selection. You must examine the
component passed into the paintBorder() method and react accordingly. In addition, you
should also draw a disabled border to indicate when the component isn’t selectable. Although
setEnabled(false) disables the selection of the component, if the component has a border
associated with it, the border still must be drawn, even when disabled. Figure 7-16 shows one
border in action that looks at all these options for the component passed into the border’s
paintBorder() method.
Figure 7-16. Active custom border examples
CHAPTER 7 ■ BORDERS
The source for the custom border and the sample program is shown in Listing 7-3.
Listing 7-3. Custom Colorized Border
import javax.swing.*;
import javax.swing.border.*;
import java.awt.*;
public class RedGreenBorder extends AbstractBorder {
public boolean isBorderOpaque() {
return true;
}
public Insets getBorderInsets(Component c) {
return new Insets(3, 3, 3, 3);
}
public void paintBorder(Component c, Graphics g, int x, int y, int width,
int height) {
Insets insets = getBorderInsets(c);
Color horizontalColor;
Color verticalColor;
if (c.isEnabled()) {
boolean pressed = false;
if (c instanceof AbstractButton) {
ButtonModel model = ((AbstractButton)c).getModel();
pressed = model.isPressed();
}
if (pressed) {
horizontalColor = Color.RED;
verticalColor = Color.GREEN;
} else {
horizontalColor = Color.GREEN;
verticalColor = Color.RED;
}
} else {
horizontalColor = Color.LIGHT_GRAY;
verticalColor = Color.LIGHT_GRAY;
}
g.setColor(horizontalColor);
g.translate(x, y);
// Top
g.fillRect(0, 0, width, insets.top);
// Bottom
g.fillRect(0, height-insets.bottom, width, insets.bottom);
233
234
CHAPTER 7 ■ BORDERS
g.setColor(verticalColor);
// Left
g.fillRect(0, insets.top, insets.left, height-insets.top-insets.bottom);
// Right
g.fillRect(width-insets.right, insets.top, insets.right,
height-insets.top-insets.bottom);
g.translate(-x, -y);
}
public static void main(String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("My Border");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Border border = new RedGreenBorder();
JButton helloButton = new JButton("Hello");
helloButton.setBorder(border);
JButton braveButton = new JButton("Brave New");
braveButton.setBorder(border);
braveButton.setEnabled(false);
JButton worldButton = new JButton("World");
worldButton.setBorder(border);
frame.add(helloButton, BorderLayout.NORTH);
frame.add(braveButton, BorderLayout.CENTER);
frame.add(worldButton, BorderLayout.SOUTH);
frame.setSize(300, 100);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
■Note Another interesting custom border is one that displays an active component instead of a text title in
a TitledBorder. Imagine a border that has a JCheckBox or JRadioButton instead of a text string for the
title. You can also use a JLabel and pass in HTML for the text.
Summary
In this chapter, you learned about the use of the Border interface and its many predefined
implementations. You also learned how to create predefined borders using the Factory design
pattern provided by the BorderFactory class. Lastly, you saw how to define your own borders
and why subclassing AbstractBorder is beneficial.
In Chapter 8, you’ll move beyond low-level components and examine the window-like
container objects available in Swing.
CHAPTER 8
■■■
Root Pane Containers
I
n Chapter 7, you looked at working with borders around Swing components. In this chapter,
you’ll explore the high-level Swing containers and discover how they differ from their AWT
counterparts.
Working with top-level containers in Swing is a bit different from working with top-level
AWT containers. With the AWT containers of Frame, Window, Dialog, and Applet, you added
components directly to the container, and there was only one place you could add them. In
the Swing world, the top-level containers of JFrame, JWindow, JDialog, and JApplet, plus the
JInternalFrame container, rely on something called a JRootPane. Instead of adding components
directly to the container, you add them to a part of the root pane. The root pane then manages
them all internally.
Why was this indirect layer added? Believe it or not, it was done to simplify things. The root
pane manages its components in layers so that elements such as tooltip text will always appear
above components, and you don’t need to worry about dragging some components around
behind others.
The one container without an AWT counterpart, JInternalFrame, also provides some additional capabilities when placed within a desktop (within a JDesktopPane to be specific). The
JInternalFrame class can be used as the basis for creating a Multiple Document Interface (MDI)
application architecture within a Swing program. You can manage a series of internal frames
within your program, and they’ll never go beyond the bounds of your main program window.
Let’s begin by exploring the new JRootPane class, which manages the internals of all the
top-level containers.
JRootPane Class
The JRootPane class acts as a container delegate for the top-level Swing containers. Because
the container holds only a JRootPane when you add or remove components from a top-level
container, instead of directly altering the components in the container, you indirectly add or
remove components from its JRootPane instance. In effect, the top-level containers are acting
as proxies, with the JRootPane doing all the work.
The JRootPane container relies on its inner class RootLayout for layout management and
takes up all the space of the top-level container that holds it. There are only two components
within a JRootPane: a JLayeredPane and a glass pane (Component). The glass pane is in front, can
be any component, and tends to be invisible. The glass pane ensures that elements such as
235
CHAPTER 8 ■ ROOT PANE CONTAINERS
tooltip text appear in front of any other Swing components. In the back is the JLayeredPane,
which contains an optional JMenuBar on top and a content pane (Container) below it in another
layer. It is within the content pane that you would normally place components in the
JRootPane. Figure 8-1 should help you visualize how the RootLayout lays out the components.
*-ENU"AR
#ONTENT 0ANE #ONTAINER
'LASS 0ANE #OMPONENT
*,AYERED0ANE
236
Figure 8-1. JRootPane containment diagram
■Note A JLayeredPane is just another Swing container (it’s described later in this chapter). It can contain
any components and has a special layering characteristic. The default JLayeredPane used within the
JRootPane pane contains only a JMenuBar and a Container as its content pane. The content pane has
its own layout manager, which is BorderLayout by default.
Creating a JRootPane
Although the JRootPane has a public no-argument constructor, a JRootPane isn’t something
you would normally create yourself. Instead, a class that implements the RootPaneContainer
interface creates the JRootPane. Then, you can get the root pane from that component, through
the RootPaneContainer interface, described shortly.
JRootPane Properties
As Table 8-1 shows, there are 11 properties of JRootPane. In most cases, when you get or set one
of these properties for a top-level container, like JFrame, the container simply passes along the
request to its JRootPane.
The glass pane for a JRootPane must not be opaque. Because the glass pane takes up the
entire area in front of the JLayeredPane, an opaque glass pane would render the menu bar and
content pane invisible. And, because the glass pane and content pane share the same bounds,
the optimizedDrawingEnabled property returns the visibility of the glass pane as its setting.
CHAPTER 8 ■ ROOT PANE CONTAINERS
Table 8-1. JRootPane Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
contentPane
Container
Read-write
defaultButton
JButton
Read-write bound
glassPane
Component
Read-write
jMenuBar
JMenuBar
Read-write
layeredPane
JLayeredPane
Read-write
optimizedDrawingEnabled
boolean
Read-only
UI
RootPaneUI
Read-write
UIClassID
String
Read-only
validateRoot
boolean
Read-only
windowDecorationStyle
int
Read-write bound
The windowDecorationStyle property is meant to describe the window adornments
(border, title, buttons for closing window) for the window containing the JRootPane. It can
be set to one of the following JRootPane class constants:
• COLOR_CHOOSER_DIALOG
• ERROR_DIALOG
• FILE_CHOOSER_DIALOG
• FRAME
• INFORMATION_DIALOG
• NONE
• PLAIN_DIALOG
• QUESTION_DIALOG
• WARNING_DIALOG
What exactly happens with the windowDecorationStyle setting depends on the current
look and feel. It is just a hint. By default, this setting is NONE. If this setting is not NONE, the
setUndecorated() method of JDialog or JFrame has been called with a value of true, and the
getSupportsWindowDecorations() method of the current look and feel reports true, then the
look and feel, rather than the window manager, will provide the window adornments. This
allows you to have programs with top-level windows that look like they do not come from the
platform the user is working on but from your own environment, though still providing iconify,
maximize, minimize, and close buttons.
237
238
CHAPTER 8 ■ ROOT PANE CONTAINERS
For the Metal look and feel (and Ocean theme), getSupportsWindowDecorations() reports
true. The other system-provided look and feel types report false. Figure 8-2 demonstrates
what a frame looks like with the window adornments provided by the Metal look and feel.
Figure 8-2. Metal window adornments for a JFrame
The source to produce Figure 8-2 is shown in Listing 8-1.
Listing 8-1. Setting the Window Decoration Style
import java.awt.*;
import javax.swing.*;
public class AdornSample {
public static void main(final String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame frame = new JFrame("Adornment Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setUndecorated(true);
frame.getRootPane().setWindowDecorationStyle(JRootPane.FRAME);
frame.setSize(300, 100);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
Customizing a JRootPane Look and Feel
Table 8-2 shows the 12 UIResource-related properties for a JRootPane. Most of these settings have
to do with the default border to use when configuring the window decoration style.
Table 8-2. JRootPane UIResource Elements
Property String
Object Type
RootPane.actionMap
ActionMap
RootPane.ancestorInputMap
InputMap
RootPane.colorChooserDialogBorder
Border
CHAPTER 8 ■ ROOT PANE CONTAINERS
Table 8-2. JRootPane UIResource Elements (Continued)
Property String
Object Type
RootPane.defaultButtonWindowKeyBindings
Object[ ]
RootPane.errorDialogBorder
Border
RootPane.fileChooserDialogBorder
Border
RootPane.frameBorder
Border
RootPane.informationDialogBorder
Border
RootPane.plainDialogBorder
Border
RootPane.questionDialogBorder
Border
RootPane.warningDialogBorder
Border
RootPaneUI
String
RootPaneContainer Interface
The RootPaneContainer interface defines the setter/getter methods for accessing the different
panes within the JRootPane, as well as accessing the JRootPane itself.
public interface RootPaneContainer {
// Properties
public Container getContentPane();
public void setContentPane(Container contentPane);
public Component getGlassPane();
public void setGlassPane(Component glassPane);
public JLayeredPane getLayeredPane();
public void setLayeredPane(JLayeredPane layeredPane);
public JRootPane getRootPane();
}
Among the predefined Swing components, the JFrame, JWindow, JDialog, JApplet, and
JInternalFrame classes implement the RootPaneContainer interface. For the most part, these
implementations simply pass along the request to a JRootPane implementation for the highlevel container. The following source code is one such implementation for the glass pane of a
RootPaneContainer implementer:
public Component getGlassPane() {
return getRootPane().getGlassPane();
}
public void setGlassPane(Component glassPane) {
getRootPane().setGlassPane(glassPane);
}
JLayeredPane Class
The JLayeredPane serves as the main component container of a JRootPane. The JLayeredPane
manages the z-order, or layering, of components within itself. This ensures that the correct
239
240
CHAPTER 8 ■ ROOT PANE CONTAINERS
component is drawn on top of other components for tasks such as creating tooltip text, pop-up
menus, and dragging for drag-and-drop. You can use the system-defined layers, or you can
create your own layers.
Although initially a JLayeredPane container has no layout manager, there’s nothing to stop
you from setting the layout property of the container, defeating the layering aspect of the
container.
Creating a JLayeredPane
As with the JRootPane, you’ll almost never create an instance of the JLayeredPane class yourself.
When the default JRootPane is created for one of the predefined classes that implement
RootPaneContainer, the JRootPane creates a JLayeredPane for its main component area, adding
an initial content pane.
Adding Components in Layers
A layer setting for each added component manages the z-order of components within a
JLayeredPane. The higher the layer setting, the closer to the top the component will be drawn.
You can set the layer with the layout manager constraints when you add a component to a
JLayeredPane:
Integer layer = new Integer(20);
aLayeredPane.add(aComponent, layer);
You can also call the public void setLayer(Component comp, int layer) or public void
setLayer(Component comp, int layer, int position) method before adding the component
to the JLayeredPane.
aLayeredPane.setLayer(aComponent, 10);
aLayeredPane.add(aComponent);
The JLayeredPane class predefines six constants for special values. In addition, you can
find out the topmost current layer with public int c and the bottom layer with public int
lowestLayer(). Table 8-3 lists the six predefined layer constants.
Table 8-3. JLayeredPane Layer Constants
Constant
Description
FRAME_CONTENT_LAYER
Level –30,000 for holding the menu bar and content pane;
not normally used by developers
DEFAULT_LAYER
Level 0 for the normal component level
PALETTE_LAYER
Level 100 for holding floating toolbars and the like
MODAL_LAYER
Level 200 for holding pop-up dialog boxes that appear on top
of components on the default layer, on top of palettes, and
below pop-ups
POPUP_LAYER
Level 300 for holding pop-up menus and tooltips
DRAG_LAYER
Level 400 for ensuring that dragged objects remain on top
CHAPTER 8 ■ ROOT PANE CONTAINERS
Although you can use your own constants for layers, use them with care—because the
system will use the predefined constants for its needs. If your constants don’t fit in properly,
the components may not work as you intended.
To visualize how the different layers fit in, see Figure 8-3.
Figure 8-3. JLayeredPane layers
Working with Component Layers and Positions
Components in a JLayeredPane have both a layer and a position. When a single component is
on a layer, it’s at position 0. When multiple components are on the same layer, components
added later have higher position numbers. The lower the position setting, the closer to the top
the component will appear. (This is the reverse of the layering behavior.) Figure 8-4 shows the
positions for four components on the same layer.
To rearrange components on a single layer, you can use either the public void
moveToBack(Component component) or public void moveToFront(Component component)
method. When you move a component to the front, it goes to position 0 for the layer. When
you move a component to the back, it goes to the highest position number for the layer. You
can also manually set the position with public void setPosition(Component component, int
position). A position of –1 is automatically the bottom layer with the highest position (see
Figure 8-4).
241
242
CHAPTER 8 ■ ROOT PANE CONTAINERS
Figure 8-4. JLayeredPane positions
JLayeredPane Properties
Table 8-4 shows the two properties of JLayeredPane. The optimizedDrawingEnabled property
determines whether components within the JLayeredPane can overlap. By default, this setting
is true because in the standard usage with JRootPane the JMenuBar and content pane can’t
overlap. However, the JLayeredPane automatically validates the property setting to reflect the
current state of the contents of the pane.
Table 8-4. JLayeredPane Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
optimizedDrawingEnabled
boolean
Read-only
JFrame Class
The JFrame class is the Swing high-level container that uses a JRootPane and implements the
RootPaneContainer interface. In addition, it uses the WindowConstants interface to help manage
closing operations.
CHAPTER 8 ■ ROOT PANE CONTAINERS
Creating a JFrame
The JFrame class provides two primary constructors: one for creating a frame without a title
and one for creating a frame with a title. There are two additional constructors for creating
frames with a specialized GraphicsConfiguration.
public JFrame()
JFrame frame = new JFrame();
public JFrame(String title)
JFrame frame = new JFrame("Title Bar");
public JFrame(GraphicsConfiguration config)
GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
GraphicsDevice gsd[] = ge.getScreenDevices();
GraphicsConfiguration gc[] = gsd[0].getConfigurations();
JFrame frame = new JFrame(gc[0]);
public JFrame(String title, GraphicsConfiguration config)
GraphicsConfiguration gc = ...;
JFrame frame = new JFrame("Title Bar", gc);
JFrame Properties
Table 8-5 shows the nine properties of the JFrame.
Table 8-5. JFrame Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
contentPane
Container
Read-write
defaultCloseOperation
int
Read-write
glassPane
Component
Read-write
iconImage
Image
Write-only
jMenuBar
JMenuBar
Read-write
layeredPane
JLayeredPane
Read-write
layout
LayoutManager
Write-only
rootPane
JRootPane
Read-only
243
244
CHAPTER 8 ■ ROOT PANE CONTAINERS
Although most properties are the result of implementing the RootPaneContainer interface,
two properties are special: defaultCloseOperation and layout. (You first looked at the
defaultCloseOperation property in Chapter 2.) By default, a JFrame hides itself when the user
closes the window. To change the setting, you can use one of the constants listed in Table 8-6
as arguments when setting the default close operation. The first comes from JFrame directly;
the others are part of the WindowConstants interface.
aFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
Table 8-6. Close Operation Constants
Constant
Description
EXIT_ON_CLOSE
Call System.exit(0).
DISPOSE_ON_CLOSE
Call dispose() on the frame.
DO_NOTHING_ON_CLOSE
Ignore the request.
HIDE_ON_CLOSE
Call setVisible(false) on the frame; this is the default.
The layout property is odd. By default, setting the layout manager of the JFrame passes the
call along to the content pane. You can’t change the default layout manager of the JFrame.
■Tip You can use the state property (inherited from Frame) to say whether the JFrame is currently iconified. When using the property, be sure to use one of the additional Frame constants of NORMAL or ICONIFIED to
set its state.
There is an additional static property of JFrame: defaultLookAndFeelDecorated. This works
with the windowDecorationStyle property of JRootPane. When set to true, newly created frames
will be adorned with decorations from the look and feel instead of the window manager. Of
course, this happens only if the current look and feel supports window decorations. Listing 8-2
shows an alternate way to generate the same screen (with the window adornments provided by
the Metal look and feel) as the one shown earlier in Figure 8-2.
Listing 8-2. Alternative Way of Setting the Window Decoration Style
import java.awt.*;
import javax.swing.*;
public class AdornSample2 {
CHAPTER 8 ■ ROOT PANE CONTAINERS
public static void main(final String args[]) {
Runnable runner = new Runnable() {
public void run() {
JFrame.setDefaultLookAndFeelDecorated(true);
JFrame frame = new JFrame("Adornment Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(300, 100);
frame.setVisible(true);
}
};
EventQueue.invokeLater(runner);
}
}
Adding Components to a JFrame
Because JFrame implements the RootPaneContainer interface and uses a JRootPane, you don’t
add components directly to the JFrame. Instead, you add them to the JRootPane contained
within the JFrame. Prior to J2SE 5.0, you needed to add components like this:
JRootPane rootPane = aJFrame.getRootPane();
Container contentPane = rootPane.getContentPane();
contentPane.add(...);
This can be shortened to the following form:
aJFrame.getContentPane().add(...);
If you tried to add components directly to the JFrame, it resulted in a runtime error being
thrown.
Due to many suggestions (complaints?), Sun finally decided to change the add() method
into a proxy:
// J2SE 5.0
aJFrame.add(...);
With J2SE 5.0, when you add components to the JFrame, they actually are added to the
content pane of the RootPaneContainer.
Handling JFrame Events
The JFrame class supports the registration of eleven different listeners:
• ComponentListener: To find out when the frame moves or is resized.
• ContainerListener: Normally not added to a JFrame because you add components to the
content pane of its JRootPane.
• FocusListener: To find out when the frame gets or loses input focus.
245
246
CHAPTER 8 ■ ROOT PANE CONTAINERS
• HierarchyBoundsListener: To find out when the frame moves or is resized. This works
similarly to ComponentListener, since the frame is the top-level container of component.
• HierarchyListener: To find out when the frame is shown or hidden.
• InputMethodListener: To work with input methods for internationalization.
• KeyListener: Normally not added to a JFrame. Instead, you register a keyboard action for
its content pane, like this:
JPanel content = (JPanel)frame.getContentPane();
KeyStroke stroke = KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0);
content.registerKeyboardAction(actionListener, stroke,
JComponent.WHEN_IN_FOCUSED_WINDOW);
• MouseListener and MouseMotionListener: To listen for mouse and mouse motion events.
• PropertyChangeListener: To listen for changes to bound properties.
• WindowListener: To find out when a window is iconified or deiconified or a user is trying
to open or close the window.
With the help of the defaultCloseOperation property, you typically don’t need to add a
WindowListener to help with closing the frame or stopping the application.
Extending JFrame
If you need to extend JFrame, this class has two important protected methods:
protected void frameInit()
protected JRootPane createRootPane()
By overriding either of these methods in a subclass, you can customize the initial appearance and behavior of the frame or that of its JRootPane. For example, in the ExitableJFrame
class shown in Listing 8-3, the default close operation is initialized to the EXIT_ON_CLOSE state.
Instead of calling setDefaultCloseOperation() for every frame created, you can use this class
instead. Because JFrame was subclassed, you don’t need to add a call to the frameInit()
method in either of the constructors. The parent class automatically calls the method.
Listing 8-3. Closing Frames by Default
import javax.swing.JFrame;
public class ExitableJFrame extends JFrame {
public ExitableJFrame () {
}
public ExitableJFrame (String title) {
super (title);
}
CHAPTER 8 ■ ROOT PANE CONTAINERS
protected void frameInit() {
super.frameInit();
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
}
■Caution If you do override the frameInit() method of JFrame, remember to call super.frameInit()
first, to initialize the default behaviors. If you forget and don’t reimplement all the default behaviors yourself,
your new frame will look and act differently.
JWindow Class
The JWindow class is similar to the JFrame class. It uses a JRootPane for component management
and implements the RootPaneContainer interface. Basically, it is a top-level window with no
adornments.
Creating a JWindow
The JWindow class has five constructors:
public JWindow()
JWindow window = new JWindow();
public JWindow(Frame owner)
JWindow window = new JWindow(aFrame);
public JWindow(GraphicsConfiguration config)
GraphicsConfiguration gc = ...;
JWindow window = new JWindow(gc);
public JWindow(Window owner)
JWindow window = new JWindow(anotherWindow);
public JWindow(Window owner, GraphicsConfiguration config)
GraphicsConfiguration gc = ...;
JWindow window = new JWindow(anotherWindow, gc);
You can create a window without specifying a parent or by specifying the parent as a Frame
or Window. If no parent is specified, an invisible one is used.
247
248
CHAPTER 8 ■ ROOT PANE CONTAINERS
JWindow Properties
Table 8-7 lists the six properties of JWindow. These are similar in nature to the JFrame properties,
except that JWindow has no property for a default close operation or a menu bar.
Table 8-7. JWindow Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
contentPane
Container
Read-write
glassPane
Component
Read-write
layeredPane
JLayeredPane
Read-write
layout
LayoutManager
Write-only
rootPane
JRootPane
Read-only
Handling JWindow Events
The JWindow class adds no additional event-handling capabilities beyond those of the JFrame
and Window classes. See the “Handling JFrame Events” section earlier in this chapter for a list of
listeners you can attach to a JWindow.
Extending JWindow
If you need to extend JWindow, the class has two protected methods of importance:
protected void windowInit()
protected JRootPane createRootPane()
JDialog Class
The JDialog class represents the standard pop-up window for displaying information related
to a Frame. It acts like a JFrame, whereby its JRootPane contains a content pane and an optional
JMenuBar, and it implements the RootPaneContainer and WindowConstants interfaces.
Creating a JDialog
There are 11 constructors for creating JDialog windows:
public JDialog()
JDialog dialog = new JDialog();
public JDialog(Dialog owner)
JDialog dialog = new JDialog(anotherDialog);
CHAPTER 8 ■ ROOT PANE CONTAINERS
public JDialog(Dialog owner, boolean modal)
JDialog dialog = new JDialog(anotherDialog, true);
public JDialog(Dialog owner, String title)
JDialog dialog = new JDialog(anotherDialog, "Hello");
public JDialog(Dialog owner, String title, boolean modal)
JDialog dialog = new JDialog(anotherDialog, "Hello", true);
public JDialog(Dialog owner, String title, boolean modal, GraphicsConfiguration gc)
GraphicsConfiguration gc = ...;
JDialog dialog = new JDialog(anotherDialog, "Hello", true, gc);
public JDialog(Frame owner)
JDialog dialog = new JDialog(aFrame);
public JDialog(Frame owner, String windowTitle)
JDialog dialog = new JDialog(aFrame, "Hello");
public JDialog(Frame owner, boolean modal)
JDialog dialog = new JDialog(aFrame, false);
public JDialog(Frame owner, String title, boolean modal)
JDialog dialog = new JDialog(aFrame, "Hello", true);
public JDialog(Frame owner, String title, boolean modal, GraphicsConfiguration gc)
GraphicsConfiguration gc = ...;
JDialog dialog = new JDialog(aFrame, "Hello", true, gc);
■Note Instead of manually creating a JDialog and populating it, you may find yourself having JOptionPane
automatically create and fill the JDialog for you. You’ll explore the JOptionPane component in Chapter 9.
Each constructor allows you to customize the dialog owner, the window title, and the
modality of the pop-up. When a JDialog is modal, it blocks input to the owner and the rest of
the application. When a JDialog is nonmodal, it allows a user to interact with the JDialog as
well as the rest of your application.
■Caution For modality to work properly among the different Java versions, avoid mixing heavyweight AWT
components with lightweight Swing components in a JDialog.
249
250
CHAPTER 8 ■ ROOT PANE CONTAINERS
JDialog Properties
Other than the settable icon image, the JDialog class has the same properties as JFrame. These
eight properties are listed in Table 8-8.
Table 8-8. JDialog Properties
Property Name
Data Type
Access
accessibleContext
AccessibleContext
Read-only
contentPane
Container
Read-write
defaultCloseOperation
int
Read-write
glassPane
Component
Read-write
jMenuBar
JMenuBar
Read-write
layeredPane
JLayeredPane
Read-write
layout
LayoutManager
Write-only
rootPane
JRootPane
Read-only
The constants to use for specifying the default close operation are the WindowConstants shown
earlier in Table 8-6 (basically all but EXIT_ON_CLOSE). By default, the defaultCloseOperation
property is set to HIDE_ON_CLOSE, which is the desirable default behavior for a dialog pop-up.
Like JFrame, JDialog also has a static defaultLookAndFeelDecorated property. This controls
whether or not dialogs are decorated by the look and feel, by default.
Handling JDialog Events
There are no special JDialog events for you to deal with; it has the same events as those for the
JFrame class.
One thing that you may want to do with a JDialog is specify that pressing the Escape key
cancels the dialog. The easiest way to do this is to register an Escape keystroke to a keyboard
action within the JRootPane of the dialog, causing the JDialog to become hidden when Escape
is pressed. Listing 8-4 demonstrates this behavior. Most of the source duplicates the constructors
of JDialog. The createRootPane() method maps the Escape key to the custom Action.
Listing 8-4. A JDialog That Closes When Escape Is Pressed
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class EscapeDialog extends JDialog {
public EscapeDialog() {
this((Frame)null, false);
}
CHAPTER 8 ■ ROOT PANE CONTAINERS
public EscapeDialog(Frame owner) {
this(owner, false);
}
public EscapeDialog(Frame owner, boolean modal) {
this(owner, null, modal);
}
public EscapeDialog(Frame owner, String title) {
this(owner, title, false);
}
public EscapeDialog(Frame owner, String title, boolean modal) {
super(owner, title, modal);
}
public EscapeDialog(Frame owner, String title, boolean modal,
GraphicsConfiguration gc) {
super(owner, title, modal, gc);
}
public EscapeDialog(Dialog owner) {
this(owner, false);
}
public EscapeDialog(Dialog owner, boolean modal) {
this(owner, null, modal);
}
public EscapeDialog(Dialog owner, String title) {
this(owner, title, false);
}
public EscapeDialog(Dialog owner, String title, boolean modal) {
super(owner, title, modal);
}
public EscapeDialog(Dialog owner, String title, boolean modal,
GraphicsConfiguration gc) {
super(owner, title, modal, gc);
}
protected JRootPane createRootPane() {
JRootPane rootPane = new JRootPane();
KeyStroke stroke = KeyStroke.getKeyStroke("ESCAPE");
Action actionListener = new AbstractAction() {
public void actionPerformed(ActionEvent actionEvent) {
setVisible(false);
}
} ;
InputMap inputMap = rootPane.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
inputMap.put(stroke, "ESCAPE");
rootPane.getActionMap().put("ESCAPE", actionListener);
return rootPane;
}
}
251
252
CHAPTER 8 ■ ROOT PANE CONTAINERS
■Note If you use the static creation methods of JOptionPane, the JDialog windows it creates automatically have the Escape key registered to close the dialog.
Extending JDialog
If you need to extend JDialog, the class has two protected methods of importance:
protected void dialogInit()
protected JRootPane createRootPane()
The latter method is demonstrated in the previous example in Listing 8-4.
JApplet Class
The JApplet class is an extension to the AWT Applet class. For event handling to work properly
within applets that use Swing components, your applets must subclass JApplet instead of
Applet.
The JApplet works the same as the other high-level containers by implementing the
RootPaneContainer interface. One important difference between JApplet and Applet is the default
layout manager. Because you add components to the content pane of a JApplet, its default
layout manager is BorderLayout. This is unlike the default layout manager of Applet, which is
FlowLayout. In addition, Swing applets can also have a menu bar, or more specifically a JMenuBar,
which is just another attribute of the JRootPane of the applet.
If you plan to deploy an applet that uses the Swing components, it is best to use the Java
Plug-in from Sun Microsystems, because that will install the Swing libraries with the runtime.
■Tip To make sure you are running the Java Plug-in under Internet Explorer, select Internet Options from
the Tools menu, and then choose the Advanced tab. Scroll down to the Java section immediately above
Microsoft VM and make sure Use JRE [VERSION] for
Source Exif Data:
File Type : PDF
File Type Extension : pdf
MIME Type : application/pdf
PDF Version : 1.6
Linearized : Yes
Page Mode : UseThumbs
XMP Toolkit : Adobe XMP Core 4.0-c316 44.253921, Sun Oct 01 2006 17:14:39
Producer : Acrobat Distiller 6.0 (Windows)
Modify Date : 2007:03:11 12:45:50-05:00
Create Date : 2006:10:27 10:13:51+08:00
Metadata Date : 2007:03:11 12:45:50-05:00
Creator Tool : Adobe Acrobat 6.0
Document ID : uuid:e357174f-fa2a-47ab-8cd7-0810e8ea1174
Instance ID : uuid:ffdc5b79-b778-4746-8b3b-1e74c634b56b
Format : application/pdf
Creator : John Zukowski
Title : The Definitive Guide to Java Swing: Creating Java-based graphical user interfaces using the J2SE 5 Swing component set, Third Edition
Has XFA : No
Page Count : 913
Author : John Zukowski