| ★ wanayoo — archive 1999 http://developer.java.sun.com/developer/Books/swing/index.html | Nouvelle recherche | Portail wanayoo |
|
|
|
The chapter presented here, Event Handling with the Swing Component Set, is an excerpt from John Zukowski's Definitive Guide to Swing for Java 2, recently published by Apress. The chapter covers event handling with the Swing Component Set. Swing is a set of graphical components used to create graphical user interfaces (GUIs). It is a component of the JavaTM Foundation Classes (JFC) and serves as a replacement to the components that are found with the Abstract Window Toolkit (AWT). In this new book, Zukowski, a long-time contributor to the Java Developer ConnectionSM (JDC), concentrates on what the professional needs to know to make a successful transition to programming with the JFC Swing Component Set. The book shows programmers those parts of the Swing Component Set that they will use on a daily basis to create graphical user interfaces, and serves as a resource for the essential parts of the JFC/Swing Component Set.
Readers will learn about the Model-View-Controller architecture that lies behind
all Swing components, and how to customize the components for specific
environments. In addition, they will find an overview of the Swing architecture,
event handling with the Swing Component Set, core Swing components, toggleable
components, and Swing menus and toolbars. Zukowski also provides custom editors
and renderers for use with tables, trees, and list components.
Chapter 2: Event Handling with the Swing Component Set
In Chapter 1, you were provided with a brief overview of everything that's
covered in this book. In this chapter, we start to look at the details of one
specific part of that NOTE: To explain the event-handling capabilities used by the Swing component set, we'll need to look at some actual Swing components. In this chapter, we'll be using the components in the simplest manner possible. Feel free to first read up on the components covered in later chapters of this book, and then come back to this chapter for a general discussion of event handling. The later chapters of this book also contain specific details on event handling for each component.
Delegation-Based Event Handling
Sun MicrosystemsTM introduced the
delegation-based event-handling mechanism into the Java libraries with the
release of JDK 1.1 and JavaBeans. Although the JavaTM 1.0 libraries included the
The delegation-based event-handling mechanism of JDK 1.1 and higher versions is
a specialized form of the
Figure 2-1 shows the structure of the modified
To make this a little clearer, let's take a second look at the delegation-based
event-handling mechanism without all the design pattern terms. GUI components
(and JavaBeans) manage lists of listeners with a pair of methods for each
listener type:
NOTE: Some users like to call the event delegation model a publish-subscribe model, in which components publish a set of available listeners for subscription, and others can subscribe to them.
Using Event Listeners as ObserversTo demonstrate the delegation-based event-handling model for Swing (and AWT) components, we'll create a button that responds to selection. When the button is selected, a message is printed. Using event listeners to handle an event is a three-step process. First, you must define a class that implements the appropriate listener interface (this includes providing implementations for all the methods of the interface). Second, you must create an instance of this listener. Third, you must register this listener to the component whose events you're interested in. Let's look at the three steps separately. 1. Define the listener.
In this example, we're going to create an
class AnActionListener implements ActionListener {
public void actionPerformed(
ActionEvent actionEvent) {
System.out.println("I was selected.");
}
}
Figure 2-2: Event delegation sequence diagram NOTE: Part of the problem of creating responsive user interfaces is figuring out which event listener to associate with a component to get the appropriate response for the event you're interested in. For the most part, this process becomes more natural with practice. Until then, you can examine the different component APIs for a pair of add/remove listener methods, or reference the appropriate component material in this book. 2. Create an instance of the listener. This simply involves creating an instance of the listener just defined. ActionListener actionListener = new AnActionListener();If you use anonymous inner classes for event listeners, you can combine steps 1 and 2:
ActionListener actionListener =
new ActionListener() {
public void actionPerformed(
ActionEvent actionEvent) {
System.out.println("I was selected.");
}
};
3. Register the listener with a component.
Once you've created the listener, you can associate it with the appropriate
component. Assuming the button.addActionListener( actionListener); If the class that you're currently defining is the class that implements the event listener interface, then you don't need to create a separate instance of the listener. You just need to associate your class as the listener for the component. The following source demonstrates this:
public class YourClass implements ActionListener {
... // other code for your class
public void actionPerformed(
ActionEvent actionEvent) {
System.out.println("I was selected.");
}
// code within some method
JButton button = new JButton(...);
button.addActionListener(this);
}
TIP Personally, I don't like this approach because it
doesn't scale well when the situation gets more complicated. For instance, as
soon as you add another button onto the screen and want the same event listener
to handle its selection, the actionPerformed() method must figure
out which button triggered the event before it can respond. Although creating a
separate event listener for each component adds another class to the set of
deliverables, creating separate listeners is more maintainable than sharing a
listener across multiple components. In addition, most Integrated Development
Environment (IDE) tools such as Inprise's JBuilder automatically create the
listener objects as separate classes.
Using event handlers such as creating a listener and associating it to a component, was really the only predefined way to respond to events with JDK 1.1. Working with the Swing components in this same manner is really no different. Whereas there are additional ways of responding to events, the basic event listener approach continues to work for all components.
TIP: The Swing event package
A Quick Look at the JFrame Class
Before examining the complete source for the button selection example, we need
to take a quick look at the
Whereas the The Close button of a frame is shown in Figure 2-3. The appearance of this button will differ depending on the operating system.
Figure 2-3: The Close button for a Windows NT frame
Your top-level frame source code changes from Frame frame = new Frame(...); frame.add(...); frame.setLayout(...);to the following: JFrame frame = new JFrame(...); Container contentPane = frame.getContentPane(); contentPane.add(...); contentPane.setLayout(...);When first transitioning to Swing from AWT, you will probably find this to be one of the most annoying adjustments because if you forget to use the content pane the program will still compile, but it won't run. At least the default layout manager of this content pane remained BorderLayout.
NOTE: The
Creating an ExitableJFrame Class
One last thing I'm going to do before showing you the source for the button
selection example is to create a frame that exits the Java runtime when the
Close button of the
The class being created is called
import javax.swing.*;
import java.awt.event.*;
public class ExitableJFrame extends JFrame {
public static final int EXIT_ON_CLOSE = -1;
private int returnCode = 0;
public ExitableJFrame () {
init();
}
public ExitableJFrame (String title) {
super(title);
init();
}
private void init() {
setDefaultCloseOperation(EXIT_ON_CLOSE);
}
public void setReturnCode(int newValue) {
returnCode = newValue;
}
protected void processWindowEvent(
WindowEvent windowEvent) {
super.processWindowEvent(windowEvent);
int defaultCloseOperation =
getDefaultCloseOperation();
if ((windowEvent.getID(
) == WindowEvent.WINDOW_CLOSING) &&
(defaultCloseOperation ==
EXIT_ON_CLOSE)) {
System.exit(returnCode);
}
}
protected String paramString() {
String returnValue = "";
int defaultCloseOperation =
getDefaultCloseOperation();
if (defaultCloseOperation ==
EXIT_ON_CLOSE) {
returnValue = ",EXIT_ON_CLOSE";
}
return super.paramString() +
returnValue;
}
}
Now that we have that class out of the way for all our examples, we can list the
source code for the first example: the button selection demonstration with an
event listener. When it runs, you'll see Figure 2-3 as the created frame. The
program creates the frame and button. Then the program creates the action
listener, which is associated with the button. The button is placed in the
frame's content pane and the frame is shown. You'll find yourself creating
listeners frequently when working with the user interface components.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ButtonSample {
public static void main(
String args[]) {
JFrame frame = new ExitableJFrame(
"Button Sample");
JButton button = new JButton(
"Select Me");
// Define ActionListener
ActionListener actionListener =
new ActionListener() {
public void actionPerformed(
ActionEvent actionEvent) {
System.out.println("I was selected.");
}
};
// Attach Listeners
button.addActionListener(
actionListener);
Container contentPane =
frame.getContentPane();
contentPane.add(button,
BorderLayout.SOUTH);
frame.setSize(300, 100);
frame.setVisible(true);
}
}
Using SwingUtilities for Mouse Button IdentificationThe Swing component set includes a utility class called SwingUtilities that provides a collection of generic helper methods. We'll look at this class periodically in this book when a particular set of methods for this class seems useful. In the following example, the methods we're interested in are related to determining which mouse button has been selected.
The
If you need to determine which buttons on the mouse were selected (or released)
when the event happened, check the modifiers property of
For instance, to check if a middle mouse button was pressed for a mouse press
event, you'd have the following code in your mouse listener's
public void mousePressed(
MouseEvent mouseEvent) {
int modifiers = mouseEvent.getModifiers(
);
if ((modifiers & InputEvent.BUTTON2_MASK) ==
InputEvent.BUTTON2_MASK) {
System.out.println("Middle button pressed.");
}
}
Although this works fine and dandy, the SwingUtilities class adds
three methods to make this process much simpler.
SwingUtilities.isLeftMouseButton( MouseEvent mouseEvent); SwingUtilities.isMiddleMouseButton( MouseEvent mouseEvent); SwingUtilities.isRightMouseButton( MouseEvent mouseEvent);Now, instead of having to manually get the modifiers and compare them against the mask, you can simply ask the SwingUtilities, as follows. This
makes your code much more readable, and more easily maintainable.
if (SwingUtilities.isMiddleMouseButton(
mouseEvent)) {
System.out.println(
"Middle button released.");
}
Using Property Change Listeners as Observers
Besides the basic event-delegation mechanism introduced with JDK 1.1 and
JavaBeans, JavaBeans introduced yet another incarnation of the Observer design
pattern, this time through the property change listener. The
In this particular case, the observable Subject has a set of add/remove property
change listener methods and a property (or properties) whose state is being
watched. With a
TIP: Although
By registering
NOTE: Although you can use a
To demonstrate a
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.*;
public class BoundSample {
public static void main(
String args[]) {
JFrame frame = new ExitableJFrame(
"Button Sample");
final JButton button1 = new JButton(
final JButton button2 = new JButton(
"No Select Me");
// Define ActionListener
ActionListener actionListener =
new ActionListener() {
public void actionPerformed(
ActionEvent actionEvent) {
JButton button = (
JButton)actionEvent.getSource();
int red = (int)(Math.random()*255);
int green = (int)(Math.random()*255);
int blue = (int)(Math.random()*255);
button.setBackground(new Color(
red, green, blue));
}
};
// Define PropertyChangeListener
PropertyChangeListener propertyChangeListener =
new PropertyChangeListener()
{
public void propertyChange(
PropertyChangeEvent propertyChangeEvent) {
String property =
propertyChangeEvent.getPropertyName();
if ("background".equals(property)) {
button2.setBackground((
Color)propertyChangeEvent.getNewValue());
}
}
};
// Attach Listeners
button1.addActionListener(actionListener);
button1.addPropertyChangeListener(
propertyChangeListener);
button2.addActionListener(actionListener);
Container contentPane =
frame.getContentPane();
contentPane.add(
button1, BorderLayout.NORTH);
contentPane.add(
button2, BorderLayout.SOUTH);
frame.setSize(300, 100);
frame.setVisible(true);
}
}
NOTE: Although the previous example only causes a color
change from button selection, imagine if the background color of the first
button could be changed from a couple of hundred different places other than
buttons! Without a property change listener, each of those places would be
required to also change the background color of the second button. With the
property change listener, it's only necessary to modify the background color of
the primary objectthe first button, in this case. The change would then
automatically propagate to the other components.
The Swing library also uses the
TIP: The Swing components use the
Managing Listener Lists
If you're creating your own components and want those components to fire off
events, you need to maintain a list of listeners to be notified. If the listener
list is for AWT 1.1 events, you can use the
Class AWTEventMulticaster
Whether you realize it or not, the
If you want to create your own component and manage a list of listeners for one
of the AWT event/listener pairs, you can use the
Because the component is meant to serve as the source for ActionListener
observers, it needs a pair of add/remove methods to handle the registration of
listeners. This is where the
private ActionListener actionListenerList =
null;
public void addActionListener(
ActionListener actionListener) {
actionListenerList =
AWTEventMulticaster.add(
actionListenerList, actionListener);
}
public void removeActionListener(
ActionListener actionListener) {
actionListenerList =
AWTEventMulticaster.remove(
actionListenerList, actionListener);
}
The remainder of the class definition describes how to handle the internal
events. An internal KeyListener needs to be registered in order to
send keystrokes to an ActionListener. In addition, the component
must be able to get the input focus, otherwise all keystrokes will go to other
components. The complete class definition follows. The line of source code for
notification of the listener list is in boldface. That one line
notifies all the listeners.
import java.awt.*;
import java.awt.event.*;
public class KeyTextComponent
extends Canvas {
private ActionListener actionListenerList
public KeyTextComponent() {
setBackground(Color.cyan);
KeyListener internalKeyListener
= new KeyAdapter() {
public void keyPressed(
KeyEvent keyEvent) {
if (actionListenerList != null) {
int keyCode = keyEvent.getKeyCode();
String keyText = KeyEvent.getKeyText(
keyCode);
ActionEvent actionEvent =
new ActionEvent(this,
ActionEvent.ACTION_PERFORMED,
keyText);
actionListenerList.actionPerformed(
actionEvent);
}
}
};
MouseListener internalMouseListener =
new MouseAdapter() {
public void mousePressed(
MouseEvent mouseEvent) {
requestFocus();
}
};
addKeyListener(internalKeyListener);
addMouseListener(i
nternalMouseListener);
}
public void addActionListener(
ActionListener actionListener) {
actionListenerList =
AWTEventMulticaster.add(
actionListenerList, actionListener);
}
actionListenerList =
AWTEventMulticaster.remove(
actionListenerList, actionListener);
}
public boolean isFocusTraversable() {
return true;
}
}
Figure 2-5: Demonstrating the The top portion of the figure is the component, and the
bottom is a text field. An
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class KeyTextTester {
public static void main(
String args[]) {
JFrame frame = new ExitableJFrame(
"Key Text Sample");
KeyTextComponent keyTextComponent =
new KeyTextComponent();
final JTextField textField =
new JTextField();
ActionListener actionListener =
new ActionListener() {
public void actionPerformed(
ActionEvent actionEvent) {
String keyText =
actionEvent.getActionCommand();
textField.setText(keyText);
}
};
keyTextComponent.addActionListener(
actionListener);
Container contentPane =
frame.getContentPane();
contentPane.add(
keyTextComponent, BorderLayout.CENTER);
contentPane.add(
textField, BorderLayout.SOUTH);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
Class EventListenerList
Although the
To help simplify this situation, the Swing component library includes a special
event-listener support class,
NOTE: In this particular example, using the
The adding and removing of listeners is similar to the technique you used with
the
EventListenerList actionListenerList =
new EventListenerList();
public void addActionListener(
ActionListener actionListener) {
actionListenerList.add(
ActionListener.class, actionListener);
}
public void removeActionListener(
ActionListener actionListener) {
actionListenerList.remove(
ActionListener.class, actionListener);
}
This leaves only the notification of the listeners to be done. No generic method
exists in the class to notify all the listeners of a particular type that an
event has happened, so you must create the code yourself. A call to the
following code (fireActionPerformed(actionEvent)) will replace the
one line of boldfaced source code
(actionListenerList.actionPerformed(actionEvent)) from the
example a few pages back. The code gets a copy of all the listeners from the
list as an array (in a thread-safe manner). You then need to loop through the
list and notify the appropriate listeners. Each listener in the array has a pair
of entries: the listener interface class object and the actual implementation.
Just having the listeners in the list isn't sufficient, however, because a
listener can implement multiple event-listener interfaces but have only one
registered.
protected void fireActionPerformed(
ActionEvent actionEvent) {
Object listenerList[] =
actionListenerList.getListenerList();
for (
int i=
listenerList.length-2; i>=0; i-=2) {
if (listenerList[i] ==
ActionListener.class) {
((ActionListener)
listenerList[i+1]).actionPerformed(
actionEvent);
}
}
}
The complete source for the new and improved class follows. When using the
EventListenerList class, don't forget that the class is in the
javax.swing.event package. Other than the class name, the testing
program doesn't change.
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;
import java.util.EventListener;
public class KeyTextComponent2
extends Canvas {
private EventListenerList a
ctionListenerList =
new EventListenerList();
public KeyTextComponent2() {
setBackground(Color.cyan);
KeyListener internalKeyListener =
new KeyAdapter() {
public void keyPressed(
KeyEvent keyEvent) {
if (actionListenerList != null) {
int keyCode = keyEvent.getKeyCode();
String keyText =
KeyEvent.getKeyText(keyCode);
ActionEvent actionEvent =
new ActionEvent(
this,
ActionEvent.ACTION_PERFORMED,
keyText);
fireActionPerformed(actionEvent);
}
}
};
MouseListener internalMouseListener =
new MouseAdapter() {
public void mousePressed(
MouseEvent mouseEvent) {
requestFocus();
}
};
addKeyListener(internalKeyListener);
addMouseListener(internalMouseListener);
}
public void addActionListener(
ActionListener actionListener) {
actionListenerList.add(
ActionListener.class, actionListener);
}
public void removeActionListener(
ActionListener actionListener) {
actionListenerList.remove(
ActionListener.class, actionListener);
}
protected void fireActionPerformed(
ActionEvent actionEvent) {
Object listenerList[] =
actionListenerList.getListenerList();
for (
int i=listenerList.length-2; i>=0; i-=2) {
if (listenerList[i] ==
ActionListener.class) {
((
ActionListener)listenerList[i+
1]).actionPerformed(
actionEvent);
}
}
}
public boolean isFocusTraversable() {
return true;
}
}
Swing-Specific Event HandlingBesides using all the familiar event-handling mechanisms from JDK 1.1, the Swing component library adds several improved capabilities to make event handling much easier. The capabilities improve on several of AWT's core event-handling features, from basic action listening to focus management.
To simplify event handling, the Swing library extends the original
The Swing library also adds a KeyStroke class that allows you to more easily
respond to key events. Instead of watching all key events for a specific key,
you can tell a component that when a specific key stroke sequence is pressed it
must respond with a particular action. The Swing text components rely on a
Keymap to store the mapping of keystrokes to actions. The Keymap interface and
the
If you found the focus management capabilities of AWT lacking, you'll be happy
to learn about Swing's enhancements through the abstract
When working with Swing components pay particular attention to thread safety.
Accessing visible Swing components must be done from the event dispatch thread.
You can use two methods from the Figure 2-6 shows a class hierarchy diagram of these classes and interfaces.
Interface Action
The Action interface is an extension to the
publicinterfaceActionimplementsActionListener{
// Constants
public final static String DEFAULT;
public final static String LONG_DESCRIPTION;
public final static String NAME;
public final static String SHORT_DESCRIPTION;
public final static String SMALL_ICON;
// Listeners
public void addPropertyChangeListener(
PropertyChangeListener listener);
public void removePropertyChangeListener(
PropertyChangeListener listener);
// Properties
public boolean isEnabled();
public void setEnabled(boolean newValue);
// Other Methods
public Object getValue(String key);
public void putValue(
String key, Object value);
}
![]() Figure 2-6: Swing-specific, event=related class hierarchy
Because
Class AbstractAction
The
Once you define an
NOTE: The
When the
import java.awt.event.*;
import javax.swing.*;
public class PrintHelloAction
extends AbstractAction {
private static final Icon printIcon =
new ImageIcon("Print.gif");
PrintHelloAction() {
super("Print", printIcon);
putValue(
Action.SHORT_DESCRIPTION, "Hello World");
}
public void actionPerformed(
ActionEvent actionEvent) {
System.out.println("Hello World");
}
}
Once the Action has been defined, you can create Action and associate it with as
many other components as you want.
Action printAction = /old?u=http%3A%2F%2Fdeveloper.java.sun.com%2Fdeveloper%2FBooks%2Fswing%2Fnew&y=1999 PrintHelloAction(); menu.add(printAction); toolbar.add(printAction);After the Action has been associated with the various objects, if you find that you need to modify the label, icon, or state of the Action, you only have to change the setting in one place. Because the properties are all bound, they propagate to anyone who uses the Action. For instance, disabling the Action
(printAction.setEnabled(false)) will disable the JMenuItem and JButton
created on the JMenu and JToolBar, respectively. In contrast, changing the name
of the Action with printAction.putValue(Action.NAME, "Hello
World") changes the text label of the associated components.
Figure 2-7: The The complete source code for this example follows. Don't worry just yet about the specifics of creating toolbars or menu bars. They'll be discussed in more detail later.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class ActionTester {
public static void main(
String args[]) {
JFrame frame = new ExitableJFrame(
"Action Sample");
final Action printAction =
/old?u=http%3A%2F%2Fdeveloper.java.sun.com%2Fdeveloper%2FBooks%2Fswing%2Fnew&y=1999 PrintHelloAction();
JMenuBar menuBar = new JMenuBar();
JMenu menu = new JMenu("File");
menuBar.add(menu);
menu.add(printAction);
JToolBar toolbar = new JToolBar();
toolbar.add(printAction);
JButton enableButton = new JButton(
"Enable");
ActionListener enableActionListener =
new ActionListener() {
public void actionPerformed(
ActionEvent actionEvent) {
printAction.setEnabled(true);
}
};
enableButton.addActionListener(
enableActionListener);
JButton disableButton = new JButton(
"Disable");
ActionListener disableActionListener =
new ActionListener() {
public void actionPerformed(
ActionEvent actionEvent) {
printAction.setEnabled(false);
}
};
disableButton.addActionListener(
disableActionListener);
JButton relabelButton = new JButton(
"Relabel");
ActionListener relabelActionListener =
new ActionListener() {
public void actionPerformed(
ActionEvent actionEvent) {
printAction.putValue(
Action.NAME, "Hello World");
}
};
relabelButton.addActionListener(
relabelActionListener);
JPanel buttonPanel = new JPanel();
buttonPanel.add(enableButton);
buttonPanel.add(disableButton);
buttonPanel.add(relabelButton);
frame.setJMenuBar(menuBar);
Container contentPane =
frame.getContentPane();
contentPane.add(
toolbar, BorderLayout.SOUTH);
contentPane.add(
buttonPanel, BorderLayout.NORTH);
frame.setSize(300, 200);
frame.setVisible(true);
}
}
AbstractAction Properties
As Table 2-1 shows, the
The remainder of the bound properties are placed in the lookup table with putValue(String key, Object value). Table 2-2 describes the predefined set of Action constants that can be used as the key. You're not limited to using only this set, though.
Table 2-2: AbstractAction Lookup Property Keys
Once a property has been placed in the lookup table, you can get it with
NOTE:There are many public subclasses of this class. Although you might never need to worry about any of them, this list is provided for those present in Swing 1.1: TextAction In some cases, you might find it useful to associate the preexisting Action with your own event trigger.
Creating an
To let you see what happens behind the scenes when using an Action with a
When extending a component to support an Action, you need to provide a constructor that accepts the Action as its sole argument. The constructor then maps all the Action attributes to the appropriate property of the component. We'll add a helper method to do the mapping, making the Action a full-fledged property for the button that can be changed later.
For a
To ensure that the button property values change when the Action changes, we
need to add our own
private Action action;
PropertyChangeListener propertyChangeListener;
public ActionButton(Action action) {
setAction(action);
}
public void setAction(Action newValue) {
// Disconnect current action;
if ((action != null) && (
propertyChangeListener != null)) {
action.removePropertyChangeListener(
propertyChangeListener);
removeActionListener(action);
}
action = /old?u=http%3A%2F%2Fdeveloper.java.sun.com%2Fdeveloper%2FBooks%2Fswing%2FnewValue&y=1999
if (action =/old?u=http%3A%2F%2Fdeveloper.java.sun.com%2Fdeveloper%2FBooks%2Fswing%2F%3D&y=1999 null) {
setText("");
setIcon(null);
} else {
setText((String)action.getValue(
Action.NAME));
setIcon((Icon)action.getValue(
Action.SMALL_ICON));
setEnabled(action.isEnabled());
String toolTipText = (
String)action.getValue(
Action.SHORT_DESCRIPTION);
if (toolTipText != null) {
setToolTipText(toolTipText);
}
addActionListener(action);
if (propertyChangeListener == null) {
propertyChangeListener =
new OurPropertyChangeListener();
}
action.addPropertyChangeListener(
propertyChangeListener);
}
}
The real workhorse here is the PropertyChangeListener. It directs
specific Action property changes in order to modify the appropriate property setting of the JButton. The source for the listener follows.
class OurPropertyChangeListener
implements PropertyChangeListener {
public void propertyChange(
PropertyChangeEvent propertyChangeEvent) {
String propertyName =
propertyChangeEvent.getPropertyName();
if (propertyName.equals(
Action.NAME)) {
String text = (
String)propertyChangeEvent.getNewValue(
);
setText(text);
} else if (propertyName.equals(
Action.SMALL_ICON)) {
Icon icon = (
Icon)propertyChangeEvent.getNewValue(
);
setIcon(icon);
} else if (propertyName.equals(
Action.SHORT_DESCRIPTION)) {
String text = (
String)propertyChangeEvent.getNewValue(
);
setToolTipText(text);
} else if (propertyName.equals(
"enabled")) {
Boolean enabledState = (
Boolean)propertyChangeEvent.getNewValue();
setEnabled(enabledState.booleanValue());
}
}
}
![]() Figure 2-8: The ActionButton in use
By adding the following line of code to the
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import java.beans.*;
public class ActionButton extends JButton {
class OurPropertyChangeListener
implements PropertyChangeListener {
public void propertyChange(
PropertyChangeEvent propertyChangeEvent) {
String propertyName =
propertyChangeEvent.getPropertyName();
if (propertyName.equals(Action.NAME)) {
String text = (
String)propertyChangeEvent.getNewValue();
setText(text);
} else if (propertyName.equals(
Action.SMALL_ICON)) {
Icon icon = (
Icon)propertyChangeEvent.getNewValue(
);
setIcon(icon);
} else if (propertyName.equals(
Action.SHORT_DESCRIPTION)) {
String text = (
String)propertyChangeEvent.getNewValue(
);
setToolTipText(text);
} else if (
propertyName.equals("enabled")) {
Boolean enabledState = (
Boolean)propertyChangeEvent.getNewValue(
);
setEnabled(enabledState.booleanValue(
));
}
}
}
private Action action;
PropertyChangeListener propertyChangeListener;
public ActionButton() {
}
public ActionButton(Action action) {
setAction(action);
}
public void setAction(Action newValue) {
// Disconnect current action;
if ((action != null) && (
propertyChangeListener != null)) {
removeActionListener(action);
}
action = /old?u=http%3A%2F%2Fdeveloper.java.sun.com%2Fdeveloper%2FBooks%2Fswing%2FnewValue&y=1999
if (action =/old?u=http%3A%2F%2Fdeveloper.java.sun.com%2Fdeveloper%2FBooks%2Fswing%2F%3D&y=1999 null) {
setText("");
setIcon(null);
} else {
setText((String)action.getValue(
Action.NAME));
setIcon((Icon)action.getValue(
Action.SMALL_ICON));
setEnabled(action.isEnabled());
String toolTipText = (String)action.getValue(
Action.SHORT_DESCRIPTION);
if (toolTipText != null) {
setToolTipText(toolTipText);
}
addActionListener(action);
if (propertyChangeListener == null) {
propertyChangeListener =
new OurPropertyChangeListener();
}
action.addPropertyChangeListener(
propertyChangeListener);
}
}
}
NOTE: Notice that the PropertyChangeListener is
attached to the Action, and not to the JButton.
Class KeyStroke
The
Before finding out how to create keystrokes, let's look at the different
conditions that can be activated. Three conditions can activate a registered
keystroke, and there are four constants in
Constants Table 2-3: Keystroke registration conditions Creating a KeyStroke
The public static KeyStroke getKeyStroke( char keyChar) public static KeyStroke getKeyStroke( String representation) public static KeyStroke getKeyStroke( int keyCode, int modifiers) public static KeyStroke getKeyStroke( int keyCode, int modifiers, boolean onKeyRelease) public static KeyStroke getKeyStrokeForEvent( KeyEvent keyEvent)The first version in this list, public static KeyStroke getKeyStroke(
charkeyChar), allows you to create a keystroke from a char variable,such a:
KeyStrokespace=KeyStroke.getKeyStroke('Z');
NOTE: I prefer to avoid the first method because as you don't
know whether to specify an uppercase or lowercase letter. There is also an
outdated, or deprecated, version of this constructor that adds a
The
KeyStrokecontrolAlt7=
KeyStroke.getKeyStroke("controlalt7");
NOTE:The method I just listed didn't work prior to Swing 1.1.1.
The KeyStroke enter = KeyStroke.getKeyStroke( KeyEvent.VK_ENTER, 0, true); KeyStroke shiftF4 = KeyStroke.getKeyStroke( KeyEvent.VK_F4, InputEvent.SHIFT_MASK );The last version listed, public static KeyStroke getKeyStrokeForEvent(KeyEvent
keyEvent), maps a specific KeyEvent directly to a KeyStroke. This is useful when you want to allow a user to supply the keystroke to activate an event. You ask the user to press a key for the event and then register the KeyEvent so that the next time it happens the event is activated.
KeyStroke fromKeyEvent = KeyStroke.getKeyStrokeForEvent(keyEvent);Registering a KeyStroke
After you've created the keystroke, you need to register it with a component. When you register a keystroke with a component, you provide an ActionListener to call when pressed. The following example creates four buttons, each with a different keystroke registered to it and a possibly different focus activation condition, as listed in Table 2-3. The button label signifies the keystroke activation conditions. The ActionListener simply prints out a message.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class KeyStrokeSample {
public static void main(String args[]) {
JFrame frame = new ExitableJFrame("KeyStroke Sample");
JButton buttonA = new JButton(
"<html><center>FOCUSED<br>control alt 7");
JButton buttonB =
new JButton(
"<html><center>FOCUS/RELEASE<br>VK_ENTER");
JButton buttonC =
new JButton(
"<html><center>ANCESTOR<br>VK_F4+SHIFT_MASK");
JButton buttonD =
new JButton(
"<html><center>WINDOW<br>' '");
// Define ActionListener
ActionListener actionListener =
new ActionListener() {
public void actionPerformed(
ActionEvent actionEvent) {
System.out.println(
"Activated");
}
};
KeyStroke controlAlt7 = KeyStroke.getKeyStroke(
"control alt 7");
buttonA.registerKeyboardAction(
actionListener, controlAlt7, JComponent.WHEN_
FOCUSED);
KeyStroke enter = KeyStroke.getKeyStroke(
KeyEvent.VK_ENTER, 0, true);
buttonB.registerKeyboardAction(
actionListener, enter, JComponent.WHEN_FOCUSED);
KeyStroke shiftF4 = KeyStroke.getKeyStroke(
KeyEvent.VK_F4, InputEvent.SHIFT_MASK);
buttonC.registerKeyboardAction(
actionListener, shiftF4,
JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
KeyStroke space = KeyStroke.getKeyStroke(' ');
buttonD.registerKeyboardAction(
actionListener, space,
JComponent.WHEN_IN_FOCUSED_WINDOW);
Container contentPane = frame.getContentPane();
contentPane.setLayout(new GridLayout(2,2));
contentPane.add(buttonA);
contentPane.add(buttonB);
contentPane.add(buttonC);
contentPane.add(buttonD);
frame.setSize(400, 200);
frame.setVisible(true);
}
}
NOTE: If you try to run the previous program with JFC/Swing
release 1.1, a NullPointerException will be generated.
Using Mnemonics and Accelerators
Besides your creating Menu accelerators activate menu items when they're not visible. For instance, pressing Ctrl-P would select the Print menu item in Figure 2-9 when the File menu isn't visible. You'll learn more about mnemonics and accelerators in later chapters of this book.
Figure 2-9: Mnemonics and menu shortcuts
Swing Focus ManagementThe 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-10. 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-10: A showing it has input focus
You can find out when the Swing component gets the input focus by registering a
With AWT components, you can explicitly set the focus to a component with the
Figure 2-11: Default focus ordering
NOTE: Besides your programmatically using
To demonstrate the use of the two methods I mentioned earlier in this section,
we'll create a
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.requestFocus();
}
}
}
For the ActionListener, you need to call
the transferFocus() method for the event source.
import java.awt.*;
import java.awt.event.*;
public class ActionFocusMover
implements ActionListener {
public void actionPerformed(
ActionEvent actionEvent) {
Object source = actionEvent.getSource();
if (source instanceof Component) {
Component component = (
Component)source;
component.transferFocus();
}
}
}
Because Swing components are built on top of AWT, these event handlers
would work for Swing components, too. However, there's one difference with
requestFocus() in Swing components: It can be turned off. Swing's
JComponent has a public void setRequestFocusEnabled(boolean newValue)
method that allows you to give components the power to accept focus
requests. This differs from the read-only focusTraversable property
of Component, which removes a component from the Tab focus cycle. When you set
the requestFocusEnabled property to false for a Swing component and
someone then calls requestFocus() on the component, it won't accept
the input focus. If you really want a component to get the input focus, Swing
provides a grabFocus() method that can't be disabled.
We'll now use the two event handlers with some components that deny requesting a focus. For example, we'll create a 3-by-3 grid of buttons in which each button has an attached mouse listener and a focus listener. The even-numbered components permit requesting focus, whereas the odd-numbered ones don't. Because the odd buttons don't permit requesting focus, pressing an even button doesn't move the focus to the next component, nor does moving the mouse over an odd button. The only way an odd button can get the input focus is if the user presses the Tab/Shift-Tab keys as part of the focus cycle. Figure 2-12 shows the main window of the program.
Figure 2-12: Focus management example
The source code follows:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class FocusSample {
public static void main(String args[]) {
JFrame frame = new ExitableJFrame(
"Focus Sample");
ActionListener actionListener =
new ActionFocusMover();
MouseListener mouseListener =
new MouseEnterFocusMover();
Container contentPane =
frame.getContentPane();
contentPane.setLayout(
new GridLayout(3,3));
for (int i=1; i<10; i++) {
JButton button = new JButton("" + i);
button.addActionListener(
actionListener);
button.addMouseListener(
mouseListener);
if ((i%2) != 0) { // odd - enabled
//by default
button.setRequestFocusEnabled(false);
}
contentPane.add(button);
}
frame.setSize(300, 200);
frame.setVisible(true);
}
}
In addition to the requestFocusEnabled property of
JComponent and FocusTraversable property of Component
that were just discussed, three additional focus-oriented properties of the
JComponent class exist.
NOTE: To change any of the read-only properties in Table 2-4, you must subclass the specific component you want to customize.
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 overriding the WARNING: There seems to be a bug in the Swing 1.1 release with Shift-Tab in a focus-cycle- constrained container. Sometimes, it lets you out of the inner container. Instead of having to recreate a focus-cycle-restricted container every time you need one, the following class definition is one you can keep handy for that use. Feel free to shorten the name.
import javax.swing.*;
public class FocusCycleConstrainedJPanel
extends JPanel {
public boolean isFocusCycleRoot() {
return true;
}
}
Using the new FocusCycleConstrainedJPanel class, the following program
demonstrates the behavior illustrated in Figure 2-13. The on-screen program
will look just like Figure 2-12; it just behaves differently.
Figure 2-13: Restrictive focus cycle
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class FocusCycleSample {
public static void main(String args[]) {
JFrame frame = new ExitableJFrame(
"Focus Cycle Sample");
Container contentPane =
frame.getContentPane();
contentPane.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;
// Row One
constraints.gridy=0;
for (int i=0; i<3; i++) {
JButton button = new JButton("" + i);
constraints.gridx=i;
contentPane.add(button, constraints);
}
// Row Two
JPanel panel =
new FocusCycleConstrainedJPanel();
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;
contentPane.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;
contentPane.add(button, constraints);
}
frame.setSize(300, 200);
frame.setVisible(true);
}
}
Customizing Component-Level Focus Management
One other thing you can do at the component level is to subclass a Swing
component and override the public boolean
TIP Because the
The following source demonstrates how you would abort the transferal of the
input focus in the
public void keyPressed(
KeyEvent keyEvent) {
if ((keyEvent.getKeyCode ==
KeyEvent.VK_TAB) || (keyEvent.getKeyChar(
) == '\t'
)) {
if (!(data is valid)) { // Define
validation condition here
keyEvent.consume();
}
}
}
Setting the Next Focusable Component
The JComponent class allows you to programmatically reorder the forward
focus-traversal order by setting the To demonstrate, the following program reverses the functionality of Tab and Shift-Tab. The screen for the program is shown in Figure 2-12, with the 3-by-3 set of buttons.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class NextComponentSample {
public static void main(String args[]) {
JFrame frame = new ExitableJFrame(
"Next Component Sample");
Container contentPane = frame.getContentPane();
contentPane.setLayout(new GridLayout(3,3));
int COUNT = 9;
JButton components[] = new JButton[COUNT];
for (int i=0; i<COUNT; i++) {
JButton button = new JButton("" + (i+1));
components[i] = button;
contentPane.add(button);
}
// Reverse tab order
for (int i=0; i<COUNT; i++) {
System.out.println(components[i].getText(
) + ":" + components[(i+COUNT-1)
% COUNT].getText());
components[i].setNextFocusableComponent(
components[(i+COUNT-1) % COUNT]);
}
frame.setSize(300, 200);
frame.setVisible(true);
}
}
Class FocusManager and DefaultFocusManager
The abstract
public abstract class FocusManager {
// Constructors
public FocusManager();
// Constants
public final static String
FOCUS_MANAGER_CLASS_PROPERTY;
// Class Methods
public static void disableSwingFocusManager(
);
public static FocusManager getCurrentManager(
);
public static boolean isFocusManagerEnabled(
);
public static void setCurrentManager(
FocusManager focusManager);
// Other Methods
public abstract void focusNextComponent(
Component component);
public abstract void focusPreviousComponent(
Component component);
public abstract void processKeyEvent(
Component focusedComponent, KeyEvent keyEvent);
}
NOTE: Although the class definition
may imply that at most one FocusManager is working at a time, in
reality each ThreadGroup manages its own FocusManager.
You can disable the Swing focus manager for an application by calling the
FocusManagerfocusManager=( FocusManager)UIManager.get( FocusManager.FOCUS_MANAGER_CLASS_PROPERTY) NOTE:It is necessary to disable the focus manager when mixing Swing and AWT components within the same window.
The
The default implementation of the FocusManager interface is the
public class DefaultFocusManager
extends FocusManager {
// Constructors
public DefaultFocusManager();
// Other Methods
public boolean compareTabOrder(
Component a, Component b);
public Component getComponentAfter(
Container aContainer, Component component);
public Component getComponentBefore(
Container aContainer, Component component);
public Component getFirstComponent(
Container container);
public Component getLastComponent(
Container container);
public void focusNextComponent(
Component component);
public void focusPreviousComponent(
Component component);
public void processKeyEvent(
Component focusedComponent, KeyEvent keyEvent);
}
TIP If you don't like the default left-to-right, top-to-bottom
tab ordering, simply subclass DefaultFocusManager and override
compareTabOrder().
Writing Your Own Focus Manager
Although the behavior of the
After you define your custom focus manager, you need to install it. Once
installed, it takes over the duties originally performed by the
FocusManager focusManager = new MyFocusManager(); FocusManager.setCurrentManager( focusManager); Multithreaded Swing Event HandlingTo increase their efficiency and decrease the complexity, all Swing components were designed not to be thread-safe. Although this might sound scary, it simply means that all access to displayed, or realized, Swing components needs to be done from a single threadthe event-dispatch thread. If you are unsure that you're in a particular thread, you can ask SwingUtilities with its public static boolean isEventDispatchThread() method.
Whenever you need to get or set a property value of a Swing component, it must
be done from the event-dispatch thread. Again, not to worry; it isn't that
difficult a task. Once you've created a program's screen probably in an init()
method of an applet or a NOTE: The addition and removal of event listeners can be done from any thread. If an event-handling task is going to take too much time, the handler needs to get the appropriate information from the Swing components first, then create another Thread to do the task. If a secondary thread isn't created, the user interface becomes nonresponsive. If a nonevent-handling task needs to access a Swing component, it must create a task to be run on the event-dispatch thread and then process the results when done.
Using SwingUtilities for Thread Safety
The Swing libraries provide two ways of creating tasks to be run on the
event-dispatch thread. If you need to execute a task on the event-dispatch
thread, but you don't need any results and don't care exactly when the task
finishes, you can use the
WARNING The
To demonstrate, let's first look at
Runnable runnable = new Runnable() {
public void run() {
// do work to be done
}
}
SwingUtilities.invokeLater(runnable);
Similarly, the invokeAndWait() method also accepts a Runnable object. However, the line after the call to invokeAndWait() isn't executed until the Runnable object finishes.
try {
Runnable runnable =
new Runnable() {
public void run() {
// do work to be done
// save/return results
}
}
SwingUtilities.invokeAndWait(
runnable);
} catch (
InterruptedException interruptedException) {
// Do something special
} catch (InvocationTargetException
invocationTargetException) {
// Do something special
}
Here are some tips about using the two methods I just described and Swing
component thread safety:
invokeLater() and invokeAndWait() wait until all pending AWT events
finish before they execute.
NOTE: In the Java 2 SDK, AWT's
Class Timer
In addition to the
Creating Timer Objects
Following is the single constructor for creating a Timer that specifies the
millisecond delay time between calls to the public Timer( int delay, ActionListener actionListener); // 1 second interval Timer timer = new Timer(1000, anActionListener); Using Timer ObjectsAfter a Timer object has been created, you need to start() it. Once the Timer is started, the ActionListener will be notified after the given number of milliseconds. If the system is busy, the delay could be longer but it won't be shorter. If there comes a time when you want to stop a Timer, call its stop() method. The Timer also has a restart() method, which calls stop() and start(), restarting the delay period. To demonstrate, the following defines an ActionListener that simply prints a message. You then create Timer to call this listener every half-second. After creating the timer, you need to start it.
import javax.swing.*;
import java.awt.event.*;
public class TimerSample {
public static void main(
String args[]) {
new ExitableJFrame(
).setVisible(true); // To create
//dummy screen
ActionListener actionListener =
new ActionListener() {
public void actionPerformed(
ActionEvent actionEvent) {
System.out.println("Hello World Timer");
}
};
Timer timer = new Timer(
500, actionListener);
timer.start();
}
}
TIP A Timer doesn't start up the AWT event-dispatch thread. If the
event-dispatch thread isn't running, your program might end relatively quickly.
That's why the previous program creates and displays a dummy frame.
Timer PropertiesTable 2-5 lists the five properties of Timer. Four allow you to customize the behavior of the timer, and the fifth (running) tells you if a timer has been started but not stopped. The delay property is the same as the constructor argument. If you change the delay of a running timer, the new delay won't be used until the existing delay runs out.
The initialDelay property allows you to have another startup delay besides the
periodic delay after the first execution. For instance, if you don't want to do
a task for an hour, but then want to do it every 15 minutes thereafter, you need
to change the
The repeats property is true by default, which results in a repeating timer.
When false, the timer notifies action listeners only once. You then need to
The coalesce property allows for a busy system to throw away notifications that haven't happened yet when a new event needs to be fired to the registered ActionListener objects. By default, the coalesce value is true. This means if a timer runs every 500 milliseconds, but its system is bogged down and doesn't respond for a whole 2 seconds, the timer only needs to send one message without all the missing ones, too. If the setting were false, four messages would still need to be sent. In addition to the properties just listed, you can turn on log messages with the following line of source. Log messages are good for actions that lack a visual element, allowing you to see when something happens. Timer.setLogTimers(true);
SummaryIn this chapter, we looked at the many ways of dealing with event handling when using Swing components. Because the components are built on top of AWT components, you can continue to use the delegation-based event mechanism first introduced into the Java class libraries with JDK 1.1. We also explored how the Swing components use the JavaBeans PropertyChangeListener approach for notification of bound property changes. With the description of some enhancements, such as simplified mouse button identification, we learned how the transition to the Swing component set from the AWT component set can be relatively painless.
Besides exploring the similarities between the Swing components and AWT
components, we also looked at several of the new features the Swing library
offers. We explored the Action interface and how it can simplify complex
user-interface development by completely separating the event-handling task from
the visual component. We looked at the technique for registering KeyStroke
objects to components to simplify listening for key events. We explored Swing's
focus management capabilities and how to customize the focus cycle with and
without the In Chapter 3, we'll cover 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 to maintain.
About the AuthorJohn Zukowski, a long-time contributer to the JDC, is a columnist for JavaWorld Magazine, a member of the JavaWorld Senior Advisory Board, and he is the Guide for Java at About.com. He is a faculty member of the Magelang Institute, a leading provider of advanced Java training and also contributors to the JDC. Zukowski has written many other popular titles on Java technology, including Java AWT Reference, Mastering Java 2, and Borland's JBuilder: No Experience Required. Reader FeedbackTell us what you think of this book excerpt.
|