Java Technology Home Page
A-Z Index

Java Developer Connection(SM)
Books

Downloads, APIs, Documentation
Java Developer Connection
Tutorials, Tech Articles, Training
Online Support
Community Discussion
News & Events from Everywhere
Products from Everywhere
How Java Technology is Used Worldwide
Print Button
 
Book Excerpt Index

John Zukowski's Definitive Guide to Swing for Java 2

by John Zukowski

(September 1999)

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.
You can order this book from Amazon.com


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 everything: event handling. When working with the Swing component set, you have the option of continuing to use the delegation-based event-handling mechanism introduced with JDKTM 1.1 and JavaBeansTM. In addition, the Swing component set offers several additional ways to respond to user-initiated actions (as well as to programmatic events). In this chapter, we'll explore all these event-handling response mechanisms.

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 Observer-Observable pair of objects that followed the Observer behavioral design pattern, this wasn't an adequate long-term solution for user-interface programming (The Java 1.0 containment event-handling mechanism was even worse).

The delegation-based event-handling mechanism of JDK 1.1 and higher versions is a specialized form of the Observer design pattern. The Observer pattern is used when an observer wants to know when a watched object's state changes and what that state change is. In the case of the delegation-based event-handling mechanism, instead of the observer's listening for a state change, the observer listens for events to happen.

Figure 2-1 shows the structure of the modified Observer pattern as it relates to the specific classes within the Java libraries for event handling. The generic Subject participant in the pattern manages a list, or lists, of generic Observer objects for each event that the subject can generate. The Observer objects in the list must provide a specific interface through which the Subject participant can notify them. When an event that the Observer objects are interested in happens within the Subject participant, all the registered Observer objects are notified. In the Java world, the specific interface for the Observer objects to implement must extend the java.util.EventListener interface. The specific event the Subject participant must create needs to extend the java.util.EventObject class.

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: addXXXListener() and removeXXXListener(). When an event happens within the subject component, the component notifies all registered listeners of the event. Any observer class interested in such an event needs to register with the component an implementer of the appropriate interface. Then each implementation is notified when the event happens. Figure 2-2 illustrates sequence.


Figure 2-1: The modified Observer pattern

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 Observers

To 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 ActionListener, because the JButton generates ActionEvent objects when selected.


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 JButton has already been created with a reference stored in the variable button, this would merely entail calling the button's addActionListener() method:


 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 (javax.swing.event) adds the MouseInputListener interface, which is a combination of the MouseListener and MouseMotionListener of AWT 1.1. The package also provides a MouseInputAdapterclass with stubbed-out implementations of all the older mouse interface methods.

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 JFrame class. The JFrame class serves as the Swing replacement to the AWT Frame class for a top-level window with appropriate window decorations for the operating system (such as a title bar with Minimize, Maximize, and Close buttons).

Whereas the JFrame class is a subclass of the Frame class, several differences exist between the two objects. The first difference is the default close operation triggered when the Close button on the title bar is selected. By default, the Frame does nothing when this button is selected. Instead, the JFrame class is hidden by default. If you don't like the default behavior, you can change it.

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

The other major difference has to do with how you add components to the frame, as well as how you change its layout manager. With a Frame, you add components directly or modify the layout manager. By comparison, the JFrame relies on a third party for support of capabilities that Frame doesn't support. This third part keeps track of how to layer items such as pop-up menus and tooltip text and adds support for Swing-specific menu bars. Now, with JFrame, instead of adding components directly to the frame object, you can add or modify its content pane, which is called a Container. To get the content pane, just ask for it with public Container getContentPane(). If you don't use the content pane and try to directly add components to the frame or set its layout manager, an Error will be thrown at runtime.

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 JFrame class will be fully discussed in Chapter 8.

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 frame is selected. This class will be used by all the examples in this book. Once this class is created, you won't need to associate a WindowListener to every JFrame you create in order to shut down the runtime environment.

The class being created is called ExitableJFrame. When the frame is closed by a user, by default the frame causes the runtime to exit. It can return a value that's settable if you don't like the default return code of zero. The source code for this class follows:


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 Identification

The 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 MouseInputListener interface consists of seven methods: mouseClicked (MouseEvent), mouseEntered(MouseEvent), mouseExited(MouseEvent), mousePressed (MouseEvent), mouseReleased(MouseEvent) from MouseListener, and mouseDragged (MouseEvent) and mouseMoved(MouseEvent) from MouseMotionListener.

If you need to determine which buttons on the mouse were selected (or released) when the event happened, check the modifiers property of MouseEvent and compare it to various mask-setting constants of the InputEvent class.

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 mousePressed() method:


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 PropertyChangeListener implementation is a truer representation of the Observer pattern. Each Observer watches for changes to an attribute of the Subject. The Observer is then notified of the new state when changed in the Subject. Figure 2-4 shows the structure of this Observer pattern, as it relates to the specific classes within the JavaBeans libraries for property change handling.

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 PropertyChangeListener, the registered set of listeners is managed within the PropertyChangeSupport class. When the watched property value changes, this support class notifies any registered listeners of the new and old property state values.


Figure 2-4: The property change listener Observer pattern

TIP: Although PropertyChangeListener observers are registered at the class level, not all properties of the class might be bound. A property is bound when a change to the property causes the registered listeners to be notified. In addition, although JavaBeans introduced the concept of property change listeners in JDK 1.1, none of the properties of the AWT components were bound. The Swing components have many of their properties bound. To find out which ones are bound, see the property tables for each Swing component that appear in later chapters of this book.

By registering PropertyChangeListener objects with the various components that support this type of listener, you can reduce the amount of source code you must generate after the initial listening setup. For instance, the background color of a Swing component is bound, meaning someone can register a PropertyChangeListener to a component to be notified when the background setting changes. When the value of the background property for that component changes, anyone listening is notified, allowing an observer to change its background color to the new setting. Therefore, if you want all the components of your program to have the same background color, you can register them all with one component. Then, when that single component changes its background color, all the other components would be notified of the change and would modify their backgrounds to the new setting.

NOTE: Although you can use a PropertyChangeListener to share a common property setting among components, you can also map the property of a subject to a different property of the observer.

To demonstrate a PropertyChangeListener, the following program contains two buttons. When either button is selected, the background of the selected button is changed to some random color. The second button is listening for property changes within the first button. When the background color changes for the first button, the background color of the second button is changed to that new value. The first button isn't listening for property changes for the second button. Therefore, when the second button is selected, changing its background color, this change doesn't propagate back to the first button.


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 ChangeEvent / ChangeListener pair to signify state changes. Although similar to the PropertyChangeEvent / PropertyChangeListener pair, the ChangeEvent doesn't carry with it the new and old data value settings. You can think of it as a lighter-weight version of a property change listener. Nevertheless, the ChangeEvent is useful when more than one property value changes, because ChangeEvent doesn't need to package up the changes.

TIP: The Swing components use the SwingPropertyChangeSupport class, instead of the PropertyChangeSupport class, to manage and notify their PropertyChangeListener list. The Swing version, SwingPropertyChangeSupport, isn't thread-safe, but it is faster and takes up less memory. For more about Swing component thread safety, see the Multithreaded Swing Event Handlingsection later in this chapter.

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 AWTEventMulticaster class for help with list management. Prior to the Swing libraries, if the event wasn't a predefined AWT event type you had to manage this list of listeners yourself. Now, with the help of the EventListenerList class in the javax.swing.event package, you no longer have to manually manage the listener list and worry about thread safety.

Class AWTEventMulticaster

Whether you realize it or not, the AWTEventMulticaster class is used by each and every AWT component to manage event listener lists. The class implements all the AWT 1.1 event listeners (ActionListener, AdjustmentListener, ComponentListener, ContainerListener, FocusListener, ItemListener, KeyListener, MouseListener, MouseMotionListener, TextListener, and WindowListener) and in Java 2 adds support for the new AWT event listener InputMethodListener. Whenever you call an add- or remove-listener method of a component, the AWTEventMulticaster is used for support.

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 AWTEventMulticaster. To demonstrate, we'll create a generic component that generates ActionEvent objects whenever a key is pressed within the component. The component uses the public static String getKeyText(int keyCode) method of KeyEvent to convert the key code to its appropriate text string and passes this string back as the action command for the ActionEvent.

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 AWTEventMulticaster comes in because it will do the adding and removing of listeners from your listener list variable:


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 KeyTextComponent

The top portion of the figure is the component, and the bottom is a text field. An ActionListener is registered with the KeyTextComponent that updates the text field in order to display the text string for the key pressed. The source code for the example follows the figure.

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 AWTEventMulticaster class is easy to use, it doesn't work for managing lists of custom event listeners. You can either create a custom extension of the class for each type of event listener list you need to manage (not practical), or you could just store the list in a data structure such as a Vector. Although using a Vector works satisfactorily, with it you'd have to worry about synchronization issues. If you don't program the list management properly, the listener notification may happen with the wrong set of listeners.

To help simplify this situation, the Swing component library includes a special event-listener support class, EventListenerList. One instance of the class can manage all the different types of event listeners for a component. To demonstrate the class usage, I've rewritten the previous example to use EventListenerList, instead of AWTEventMulticaster.

NOTE: In this particular example, using the AWTEventMulticaster class is the simpler solution. However, imagine a similar situation in which the event listener isn't one of the predefined AWT event listeners.

The adding and removing of listeners is similar to the technique you used with the AWTEventMulticaster in the previous section of this chapter. You need to create a variable of the appropriate typethis time EventListenerListas well as define add- and remove-listener methods. One key difference between the two approaches is that the initial EventListenerList is non-null, whereas the other starts off being null. A reference to an empty EventListenerList must be created to start. This removes the need for several checks for a null list variable later. The adding and removing of listeners is also slightly different. Because an EventListenerList can manage a list of listeners of any type, when you add or remove the listener, you must provide the class type for the listener being acted upon.


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 Handling

Besides 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 ActionListener interface with the Action interface to store visual attributes with the event handler. This allows the creation of event handlers independent of visual components. Then, when the Action is later associated with a component, the component automatically gets information (such as a button label) directly from the event handler. This includes notification of updates for the label when the Action is modified. The AbstractAction and TextAction classes are implementations of this concept.

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 TextAction support class will be described in more detail in Chapter 15, along with the remainder of the text event-handling capabilities.

If you found the focus management capabilities of AWT lacking, you'll be happy to learn about Swing's enhancements through the abstract FocusManager class and DefaultFocusManager implementation. But because Swing is built on top of AWT, you'll never be able to completely rid yourself of AWT's focusing capabilities.

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 SwingUtilities class to ensure that this accessing is properly done by creating Runnable objects to be run in the event dispatch thread. In addition, the Timer class allows you to create periodic events that also run within the event dispatch thread.

Figure 2-6 shows a class hierarchy diagram of these classes and interfaces.

Interface Action

The Action interface is an extension to the ActionListener interface that's very flexible for defining shared event handlers independent of the components that act as the triggering agents. As the following interface definition may (or may not) immediately reveal, the interface implements ActionListener and defines a lookup table data structure whose keys act as bound properties. Then, when an Action is associated with a component, these display properties are automatically carried over to it.


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 Action is merely an interface, the Swing libraries offer a class to implement the interface. That class is AbstractAction.

Class AbstractAction

The AbstractAction class provides a default implementation of the Action interface. This is where the bound property behavior is implemented.

Once you define an AbstractAction by subclassing and providing a public void actionPerformed(ActionEvent actionEvent) method, you can then pass it along to some special Swing components. The JMenu, JPopupMenu, and JToolBar directly support the adding of Action objects, whereas the Swing text components have their own built-in support for Action objects through their Keymap. When you add an action with add(Action newAction) to a JMenu or JPopupMenu, a JMenuItem is created and placed within the menu container. For a JToolBar, the add(Action newAction) method creates a JButton component.

NOTE: The add(Action newAction) methods of JMenu, JPopupMenu, and JToolBar all return the object created: a JMenuItem, JMenuItem, or JButton, respectively.

When the Action is added to the respective Swing container, selection of the component created by the container triggers the calling of the method of the Action, actionPerformed(ActionEvent actionEvent). The display of the component is defined by the property elements added to the internal data structure. I'll demonstrate by presenting an Action with a Print label and an image icon. When this is activated, a Hello Worldmessage is printed.


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 = new 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 shows what the PrintHelloAction might look like on a JToolBar and a JMenu. Selectable buttons are provided to enable or disable the Action as well as change its name.

Figure 2-7: The PrintHelloAction in use

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 = 
  new 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 AbstractAction class has only a single bound property available from setter/getter methods.

PROPERTY NAME DATA TYPE ACCESS
enabled boolean read-write bound
Table 2-1: AbstractAction properties

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.
Constant Description
NAME Action name, used as button label.
SMALL_ICON Icon for action, used as button label.
SHORT_DESCRIPTION Short description of action; could be used as tooltip text, but not by default
LONG_DESCRIPTION Long description of action; could be used by accessibility
DEFAULT Unused constant that could be used for your own property

Table 2-2: AbstractAction Lookup Property Keys

Once a property has been placed in the lookup table, you can get it with public Object getValue(String key). It works similarly to the java.util.Hashtable class, with one distinction: If you try to put a key-value pair into the table with a null value, the table removes the key, if it's present.

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 Action JButton

To let you see what happens behind the scenes when using an Action with a JMenuBar or a JToolBar, we'll create a JButton that accepts an Action for configuration. Surprisingly, this isn't available by default. To make this example even more helpful, we'll provide built-in support for tooltip textsomething that's not provided with the built-in Action support for JMenu, JPopupMenu, or JToolBar.

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 JButton, the Action.NAME and Action.SMALL_ICON are included for the label, and they get mapped to the text and icon properties. The initial state of the JButton must also match the state of the Action, so you need to ask for the enabled property value of the Action. The tooltip text will be provided by the Action.SHORT_DESCRIPTION. Lastly, you'll need to associate the Action as the ActionListener for the JButton.

To ensure that the button property values change when the Action changes, we need to add our own PropertyChangeListener to the Action. In addition, if you associate a new Action with the button, you need to remember to remove the old one. The source for the constructor and setAction(Action) method follow.


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 = newValue;
 if (action == 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 ActionTester example shown in the section Class AbstractAction, the three components in the example share the same Action. Figure 2-8 shows the updated display with the new component. contentPane.add(new ActionButton(printAction), BorderLayout.CENTER); The complete source for the ActionButton follows.


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 = newValue;


 if (action == 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 KeyStroke class and the public void registerKeyboardAction(ActionListener actionListener, KeyStroke keyStroke, int condition) method of JComponent provide a simple replacement for registering KeyListener objects to components and watching for specific keys to be pressed. The KeyStroke class allows you to define a single combination of key strokes, such as Shift-Ctrl-P or f4. You can then activate the keystroke by registering it with a component and telling the keystroke what to do when the component recognizes it, causing the ActionListener to be notified.

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 JComponent to help. The fourth is for an undefined state. If you want the keystroke to activate when the actual component has the input focus, you would use the JComponent.WHEN_FOCUSED constant. If you want the keystroke to activate when the window that the component is in has the input focus, you would use JComponent.WHEN_IN_FOCUSED_WINDOW. The remaining constant, JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT, is used when the keystroke is pressed in the component or a container of the component. The four available constants are listed in Table 2-3.

Constants WHEN_FOCUSED WHEN_IN_FOCUSED_WINDOW WHEN_ANCESTOR_OF_FOCUSED_COMPONENT

Table 2-3: Keystroke registration conditions

Creating a KeyStroke

The KeyStroke class has no public constructor. You create a keystroke by using one of the static getKeyStroke() methods plus the one getKeyStrokeForEvent() method, which follow:


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 boolean onKeyRelease argument. This, too, should be avoided.

The public static KeyStroke getKeyStroke(String representation) version is the most interesting of the lot. It allows you to specify a keystroke as a text string, such as control F4. The set of modifiers to the string are shift | control | meta | alt | button1 | button2 | button3, and multiple modifiers can be specified. The remainder of the string comes from one of the many VK_* constants of the KeyEvent class.

KeyStrokecontrolAlt7=
  KeyStroke.getKeyStroke("controlalt7");
NOTE:The method I just listed didn't work prior to Swing 1.1.1.

The public static KeyStroke getKeyStroke(int keyCode, int modifiers) and public static KeyStroke getKeyStroke(int keyCode, int modifiers, boolean onKeyRelease) methods are the most clear-cut. They allow you to directly specify the VK_* key constant and the InputEvent masks for the modifiers (or zero for no modifiers). When not specified, onKeyRelease is false.


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 KeyStroke objects yourself, the Swing libraries use KeyStroke objects for several internal functions. Two such instances of this are component mnemonics and accelerators. In a component mnemonic, one character in a label appears underlined. When that character is pressed using a platform-specific hot-key combination, the component is activated. For instance, pressing Alt-A in Figure 2-9 would select the About button on a Windows 98 platform.

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 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-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 FocusListener, just as you'd do with all AWT 1.1 components. Although this doesn't change when you move from the AWT component set to the Swing component set, what does differ is the set of methods used programatically to move the focus around.

With AWT components, you can explicitly set the focus to a component with the public void requestFocus() method. In addition, you can transfer focus to the next component with the public void transferFocus() method. In the AWT world, the next component is defined by the order in which components are added to a container, as shown in Figure 2-11. By default, this focus traversal starts at the top left of the figure and goes across each row and down to the bottom right. When all the components are in the same container, this traversal order is called a focus cycle and can be limited to remain within the container.

Figure 2-11: Default focus ordering

NOTE: Besides your programmatically using transferFocus() to move the input focus, a user can press Tab or Shift-Tab to move forward or backward around the components in a container.

To demonstrate the use of the two methods I mentioned earlier in this section, we'll create 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 requestFocus() 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.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 public boolean isFocusCycleRoot() method to return true, thus restricting the focus traversal from going beyond an inner container. Then, when a 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-13 illustrates how it would look if you placed the middle three buttons from Figure 2-11 within a container restricted in this way. Notice that there is no way of getting to the first component on the third row by just pressing the Tab key to move forward.

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 isManagingFocus() method to return true. This allows you to manage focus traversal yourself by listening for Tab key presses in a KeyListener. Then when the Tab key is pressed, you can consume() the KeyEvent if you don't want the input focus to move on, for example, because of an invalid data state. When consumed, the FocusManager would check the consumption status of the KeyEvent and not transfer the input focus if already consumed. The FocusManager class is described later in this chapter.

TIP Because the managingFocus property of JComponent is read-only, you must subclass a Swing component to internally manage focus.

The following source demonstrates how you would abort the transferal of the input focus in the keyPressed() method of a KeyEvent.


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 nextFocusableComponent property. Any AWT component and not just Swing components can be the next focusable component. When this property is set for a component, it defines where the input focus moves when the Tab key is pressed. No way exists to explicitly set the response for Shift-Tab. The response is determined by the system's finding the first component that has the current component as its next component.

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 FocusManager class in the Swing library serves as the control mechanism framework for input focus behavior of Swing components. The Swing FocusManager is the basic control mechanism, but because Swing components live within the AWT world, the AWT focus manager has overall control. The abstract class defines a series of static methods and three abstract methods for the concrete focus manager to implement.


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 public static void disableSwingFocusManager() method of FocusManager. This gives the AWT focus manager complete control. To enable it again, call the public static void setCurrentManager (FocusManager newValue) method with a new manager as the argument. To get the default manager for the look and feel, ask the UIManager the following:

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 FocusManager doesn't remember what has the input focus when it's asked to find the next component to get the input focus. When asked to adjust the input focus, all three of the abstract methods take the last component, with the input focus as an argument.

The default implementation of the FocusManager interface is the DefaultFocusManager class, which provides the predefined behavior for the five properties listed back in Table 2-4.


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 DefaultFocusManager might suffice for most needs, it won't for all. There might come a time when you need to customize the focus behavior. Before wandering down this path, be sure the behavior you desire can't first be accomplished by using the properties listed in Table 2-4.

After you define your custom focus manager, you need to install it. Once installed, it takes over the duties originally performed by the DefaultFocusManager.

FocusManager focusManager = 
 new MyFocusManager();
FocusManager.setCurrentManager(
 focusManager);

Multithreaded Swing Event Handling

To 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 main() method of an applicationyou show it. The components on the screen are then responsive to user input. When a component is activated, the component might look at another component for its state and pass the state data along to something else. All this happens in the event-dispatch thread by default while the event handler for the activated component executes in the event-dispatch thread. Even the paint() method executes in the event-dispatch thread in response to paint events (java.awt.PaintEvent).

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 public static void invokeLater(Runnable runnable) method of SwingUtilities. If, on the other hand, you can't continue with what you're doing until the task completes and returns you a value, you can use the public static void invokeAndWait(Runnable runnable) method of SwingUtilities. The code to get the value is left up to you and not the return value to the invokeAndWait() method.

WARNING The invokeAndWait(Runnable) method can throw an InterrutpedException or an InvocationTargetException.

To demonstrate, let's first look at invokeLater(). The method requires a Runnable object as its argument, so you only need to create a Runnable object and pass it along to the invokeLater() method. Some time after the current event dispatching is done, this runnable object will execute.


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:
    Both invokeLater() and invokeAndWait() wait until all pending AWT events finish before they execute.
  • Don't call invokeAndWait() from the event-dispatch thread within an AWT event handler, because the event processing will never finish in order for invokeAndWait() to execute.
  • When using invokeAndWait(), be sure the object being blocked doesn't retain any system locks, otherwise the method may never return because of a deadlock situation.
  • Calls to repaint(), revalidate(), and invalidate() don't have to be done from the event-dispatch thread.

NOTE: In the Java 2 SDK, AWT's EventQueue class has its own pair of invokeAndWait() and invokeLater() methods. They function in the same way as the SwingUtilities methods; however, their usage should be limited to interactions with AWT components.

Class Timer

In addition to the invokeAndWait() and invokeLater() methods of SwingUtilities, you can use the Timer class to create actions to be executed on the event- dispatch thread. A Timer provides a way of notifying an ActionListener after a predefined number of milliseconds. The timer can repeatedly notify the listeners, or just call them once.

Creating Timer Objects

Following is the single constructor for creating a Timer that specifies the millisecond delay time between calls to the ActionListener:


public Timer(
 int delay, ActionListener actionListener);


// 1 second interval


Timer timer = new Timer(1000, anActionListener);


Using Timer Objects

After 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 Properties

Table 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 initialDelay setting before you start the timer. By default, the initialDelay and delay properties are set to the same setting in the constructor.

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 restart() the timer to trigger the listener again. Nonrepeating timers are good for one-time notifications that need to happen after a triggering event.

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);

Summary

In 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 FocusManager. Lastly, we looked at the multithreading limitations of the Swing components and how to get around them with the invokeAndWait() and invokeLater() methods of SwingUtilities.

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 Author

John 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 Feedback

Tell us what you think of this book excerpt.

 Very worth reading  Worth reading  Not worth reading

If you have other comments or ideas for future technical content, please type them here:


Print Button
[ This page was updated: 27-Sep-2000 ]
Products & APIs | Developer Connection | Docs & Training | Online Support
Community Discussion | Industry News | Solutions Marketplace | Case Studies
Glossary | Feedback | A-Z Index
For more information on Java technology
and other software from Sun Microsystems, call:
(800) 786-7638
Outside the U.S. and Canada, dial your country's AT&T Direct Access Number first.
Sun Microsystems, Inc.
Copyright © 1995-2000 Sun Microsystems, Inc.
All Rights Reserved. Terms of Use. Privacy Policy.