Chapter 8. Event Handling

Event handling is of fundamental importance to programs with a graphical user interface. To implement user interfaces, you must master the way in which Java handles events. This chapter explains how the Java AWT event model works. You will see how to capture events from the mouse and the keyboard. This chapter also shows you how to use the simplest GUI elements, such as buttons. In particular, this chapter discusses how to work with the basic events generated by these components. The next chapter shows you how to put together the most common of the components that Swing offers, along with a detailed coverage of the events they generate.

Note

Note

Java 1.1 introduced a much improved event model for GUI programming. We do not cover the deprecated 1.0 event model in this chapter.

Basics of Event Handling

Any operating environment that supports GUIs constantly monitors events such as keystrokes or mouse clicks. The operating environment reports these events to the programs that are running. Each program then decides what, if anything, to do in response to these events. In languages like Visual Basic, the correspondence between events and code is obvious. One writes code for each specific event of interest and places the code in what is usually called an event procedure. For example, a Visual Basic button named HelpButton would have a HelpButton_Click event procedure associated with it. The code in this procedure executes whenever that button is clicked. Each Visual Basic GUI component responds to a fixed set of events, and it is impossible to change the events to which a Visual Basic component responds.

On the other hand, if you use a language like raw C to do event-driven programming, you need to write the code that constantly checks the event queue for what the operating environment is reporting. (You usually do this by encasing your code in a giant loop with a massive switch statement!) This technique is obviously rather ugly, and, in any case, it is much more difficult to code. The advantage is that the events you can respond to are not as limited as in languages, like Visual Basic, that go to great lengths to hide the event queue from the programmer.

The Java programming environment takes an approach somewhat between the Visual Basic approach and the raw C approach in terms of power and, therefore, in resulting complexity. Within the limits of the events that the AWT knows about, you completely control how events are transmitted from the event sources (such as buttons or scrollbars) to event listeners. You can designate any object to be an event listener—in practice, you pick an object that can conveniently carry out the desired response to the event. This event delegation model gives you much more flexibility than is possible with Visual Basic, in which the listener is predetermined, but it requires more code and is more difficult to untangle (at least until you get used to it).

Event sources have methods that allow you to register event listeners with them. When an event happens to the source, the source sends a notification of that event to all the listener objects that were registered for that event.

As one would expect in an object-oriented language like Java, the information about the event is encapsulated in an event object. In Java, all event objects ultimately derive from the class java.util.EventObject. Of course, there are subclasses for each event type, such as ActionEvent and WindowEvent.

Different event sources can produce different kinds of events. For example, a button can send ActionEvent objects, whereas a window can send WindowEvent objects.

To sum up, here’s an overview of how event handling in the AWT works.

  • A listener object is an instance of a class that implements a special interface called (naturally enough) a listener interface.

  • An event source is an object that can register listener objects and send them event objects.

  • The event source sends out event objects to all registered listeners when that event occurs.

  • The listener objects will then use the information in the event object to determine their reaction to the event.

You register the listener object with the source object by using lines of code that follow the model

eventSourceObject.addEventListener(eventListenerObject);

Here is an example:

ActionListener listener = . . .;
JButton button = new JButton("Ok");
button.addActionListener(listener);

Now the listener object is notified whenever an “action event” occurs in the button. For buttons, as you might expect, an action event is a button click.

Code like the above requires that the class to which the listener object belongs implements the appropriate interface (which in this case is the ActionListener interface). As with all interfaces in Java, implementing an interface means supplying methods with the right signatures. To implement the ActionListener interface, the listener class must have a method called actionPerformed that receives an ActionEvent object as a parameter.

class MyListener implements ActionListener
{
   . . .
   public void actionPerformed(ActionEvent event)
   {
      // reaction to button click goes here
      . . .
   }
}

Whenever the user clicks the button, the JButton object creates an ActionEvent object and calls listener.actionPerformed(event), passing that event object. It is possible for multiple objects to be added as listeners to an event source such as a button. In that case, the button calls the actionPerformed methods of all listeners whenever the user clicks the button.

Figure 8–1 shows the interaction between the event source, event listener, and event object.

Event notificationeventsevent listener diagramlistenersevent listener diagram

Figure 8–1. Event notification

Note

Note

In this chapter, we put particular emphasis on event handling for user interface events such as button clicks and mouse moves. However, the basic event handling architecture is not limited to user interfaces. As you will see in the next chapter, objects that are not user interface components can also send event notifications to listeners—usually to tell them that a property of the object has changed.

Example: Handling a Button Click

As a way of getting comfortable with the event delegation model, let’s work through all details needed for the simple example of responding to a button click. For this example, we will want

  • A panel populated with three buttons; and

  • Three listener objects that are added as action listeners to the buttons.

With this scenario, each time a user clicks on any of the buttons on the panel, the associated listener object then receives an ActionEvent that indicates a button click. In our sample program, the listener object will then change the background color of the panel.

Before we can show you the program that listens to button clicks, we first need to explain how to create buttons and how to add them to a panel. (For more on GUI elements, see Chapter 9.)

You create a button by specifying a label string, an icon, or both in the button constructor. Here are two examples:

JButton yellowButton = new JButton("Yellow");
JButton blueButton = new JButton(new ImageIcon("blue-ball.gif"));

Adding buttons to a panel occurs through a call to a method named (quite mnemonically) add. The add method takes as a parameter the specific component to be added to the container. For example,

class ButtonPanel extends JPanel
{
   public ButtonPanel()
   {
      JButton yellowButton = new JButton("Yellow");
      JButton blueButton = new JButton("Blue");
      JButton redButton = new JButton("Red");

      add(yellowButton);
      add(blueButton);
      add(redButton);
   }
}

Figure 8–2 shows the result.

A panel filled with buttonspanelsbuttons on panel, examplebuttonson panels, example

Figure 8–2. A panel filled with buttons

Now that you know how to add buttons to a panel, you’ll need to add code that lets the panel listen to these buttons. This requires classes that implement the ActionListener interface, which, as we just mentioned, has one method: actionPerformed, whose signature looks like this:

public void actionPerformed(ActionEvent event)

Note

Note

The ActionListener interface we used in the button example is not restricted to button clicks. It is used in many separate situations:

  • When an item is selected from a list box with a double click;

  • When a menu item is selected;

  • When the ENTER key is clicked in a text field;

  • When a certain amount of time has elapsed for a Timer component.

You will see more details in this chapter and the next.

The way to use the ActionListener interface is the same in all situations: the actionPerformed method (which is the only method in ActionListener) takes an object of type ActionEvent as a parameter. This event object gives you information about the event that happened.

When a button is clicked, then we want to set the background color of the panel to a particular color. We store the desired color in our listener class.

class ColorAction implements ActionListener
{
   public ColorAction(Color c)
   {
      backgroundColor = c;
   }

   public void actionPerformed(ActionEvent event)
   {
      // set panel background color
      . . .
   }

   private Color backgroundColor;
}

We then construct one object for each color and set the objects as the button listeners.

ColorAction yellowAction = new ColorAction(Color.YELLOW);
ColorAction blueAction = new ColorAction(Color.BLUE);
ColorAction redAction = new ColorAction(Color.RED);

yellowButton.addActionListener(yellowAction);
blueButton.addActionListener(blueAction);
redButton.addActionListener(redAction);

For example, if a user clicks on the button marked “Yellow,” then the actionPerformed method of the yellowAction object is called. Its backgroundColor instance field is set to Color.YELLOW, and it can now proceed to set the panel’s background color.

Just one issue remains. The ColorAction object doesn’t have access to the panel variable. You can solve this problem in two ways. You can store the panel in the ColorAction object and set it in the ColorAction constructor. Or, more conveniently, you can make ColorAction into an inner class of the ButtonPanel class. Its methods can then access the outer panel automatically. (For more information on inner classes, see Chapter 6.)

We follow the latter approach. Here is how you place the ColorAction class inside the ButtonPanel class.

class ButtonPanel extends JPanel{    . . .    private class ColorAction implements ActionListener{    . . .    public void actionPerformed(ActionEvent event)    {       setBackground(backgroundColor);       // i.e., outer.setBackground(...)    }    private Color backgroundColor;  }}

Look closely at the actionPerformed method. The ColorAction class doesn’t have a setBackground method. But the outer ButtonPanel class does. The methods are invoked on the ButtonPanel object that constructed the inner class objects. (Note again that outer is not a keyword in the Java programming language. We just use it as an intuitive symbol for the invisible outer class reference in the inner class object.)

This situation is very common. Event listener objects usually need to carry out some action that affects other objects. You can often strategically place the listener class inside the class whose state the listener should modify.

Example 8–1 contains the complete program. Whenever you click one of the buttons, the appropriate action listener changes the background color of the panel.

Example 8–1. ButtonTest.java

 1. import java.awt.*;
 2. import java.awt.event.*;
 3. import javax.swing.*;
 4.
 5. public class ButtonTest
 6. {
 7.    public static void main(String[] args)
 8.    {
 9.       ButtonFrame frame = new ButtonFrame();
10.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
11.       frame.setVisible(true);
12.    }
13. }
14.
15. /**
16.    A frame with a button panel
17. */
18. class ButtonFrame extends JFrame
19. {
20.    public ButtonFrame()
21.    {
22.       setTitle("ButtonTest");
23.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
24.
25.       // add panel to frame
26.
27.       ButtonPanel panel = new ButtonPanel();
28.       add(panel);
29.    }
30.
31.    public static final int DEFAULT_WIDTH = 300;
32.    public static final int DEFAULT_HEIGHT = 200;
33. }
34.
35. /**
36.    A panel with three buttons.
37. */
38. class ButtonPanel extends JPanel
39. {
40.    public ButtonPanel()
41.    {
42.       // create buttons
43.
44.       JButton yellowButton = new JButton("Yellow");
45.       JButton blueButton = new JButton("Blue");
46.       JButton redButton = new JButton("Red");
47.
48.       // add buttons to panel
49.
50.       add(yellowButton);
51.       add(blueButton);
52.       add(redButton);
53.
54.       // create button actions
55.
56.       ColorAction yellowAction = new ColorAction(Color.YELLOW);
57.       ColorAction blueAction = new ColorAction(Color.BLUE);
58.       ColorAction redAction = new ColorAction(Color.RED);
59.
60.       // associate actions with buttons
61.
62.       yellowButton.addActionListener(yellowAction);
63.       blueButton.addActionListener(blueAction);
64.       redButton.addActionListener(redAction);
65.    }
66.
67.    /**
68.       An action listener that sets the panel's background color.
69.    */
70.    private class ColorAction implements ActionListener
71.    {
72.       public ColorAction(Color c)
73.       {
74.          backgroundColor = c;
75.       }
76.
77.       public void actionPerformed(ActionEvent event)
78.       {
79.          setBackground(backgroundColor);
80.       }
81.
82.       private Color backgroundColor;
83.    }
84. }
ButtonTest.java
javax.swing.JButton 1.2
  • JButton(String label)

    constructs a button. The label string can be plain text, or, starting with JDK 1.3, HTML; for example, "<html><b>Ok</b></html>".

    Parameters:

    label

    The text you want on the face of the button

  • JButton(Icon icon)

    constructs a button.

    Parameters:

    icon

    The icon you want on the face of the button

  • JButton(String label, Icon icon)

    constructs a button.

    Parameters:

    label

    The text you want on the face of the button

     

    icon

    The icon you want on the face of the button

ButtonTest.java
java.awt.Container 1.0
  • Component add(Component c)

    adds the component c to this container.

ButtonTest.java
javax.swing.ImageIcon 1.2
  • ImageIcon(String filename)

    constructs an icon whose image is stored in a file. The image is automatically loaded with a media tracker (see Chapter 7).

Becoming Comfortable with Inner Classes

Some people dislike inner classes because they feel that a proliferation of classes and objects makes their programs slower. Let’s have a look at that claim. You don’t need a new class for every user interface component. In our example, all three buttons share the same listener class. Of course, each of them has a separate listener object. But these objects aren’t large. They each contain a color value and a reference to the panel. And the traditional solution, with if . . . else statements, also references the same color objects that the action listeners store, just as local variables and not as instance fields.

We believe the time has come to get used to inner classes. We recommend that you use dedicated inner classes for event handlers rather than turning existing classes into listeners. We think that even anonymous inner classes have their place.

Here is a good example of how anonymous inner classes can actually simplify your code. If you look at the code of Example 8–1, you will note that each button requires the same treatment:

  1. Construct the button with a label string.

  2. Add the button to the panel.

  3. Construct an action listener with the appropriate color.

  4. Add that action listener.

Let’s implement a helper method to simplify these tasks:

void makeButton(String name, Color backgroundColor)
{
   JButton button = new JButton(name);
   add(button);
   ColorAction action = new ColorAction(backgroundColor);
   button.addActionListener(action);
}

Then the ButtonPanel constructor simply becomes

public ButtonPanel()
{
   makeButton("yellow", Color.YELLOW);
   makeButton("blue", Color.BLUE);
   makeButton("red", Color.RED);
}

Now you can make a further simplification. Note that the ColorAction class is only needed once: in the makeButton method. Therefore, you can make it into an anonymous class:

void makeButton(String name, final Color backgroundColor)
{
   JButton button = new JButton(name);
   add(button);
   button.addActionListener(new
      ActionListener()
      {
         public void actionPerformed(ActionEvent event)
         {
            setBackground(backgroundColor);
         }
      });
}

The action listener code has become quite a bit simpler. The actionPerformed method simply refers to the parameter variable backgroundColor. (As with all local variables that are accessed in the inner class, the parameter needs to be declared as final.)

No explicit constructor is needed. As you saw in Chapter 6, the inner class mechanism automatically generates a constructor that stores all local final variables that are used in one of the methods of the inner class.

Tip

Tip

Anonymous inner classes can look confusing. But you can get used to deciphering them if you train your eyes to glaze over the routine code, like this:

button.addActionListener(new
   ActionListener()
   {
      public void actionPerformed(ActionEvent event)
      {
         setBackground(backgroundColor);
      }
   });

That is, the button action sets the background color. As long as the event handler consists of just a few statements, we think this can be quite readable, particularly if you don’t worry about the inner class mechanics.

Tip

Tip

JDK 1.4 introduces a mechanism that lets you specify simple event listeners without programming inner classes. For example, suppose you have a button labeled Load whose event handler contains a single method call:

frame.loadData();

Of course, you can use an anonymous inner class:

loadButton.addActionListener(new
   ActionListener()
   {
      public void actionPerformed(ActionEvent event)
      {
         frame.loadData();
      }
   });

But the EventHandler class can create such a listener automatically, with the call

EventHandler.create(ActionListener.class, frame, "loadData")

Of course, you still need to install the handler:

loadButton.addActionListener((ActionListener)
   EventHandler.create(ActionListener.class, frame, "loadData"));

The cast is necessary because the create method returns an Object. Perhaps a future version of the JDK will make use of generic types to make this method even more convenient.

If the event listener calls a method with a single parameter that is derived from the event handler, then you can use another form of the create method. For example, the call

EventHandler.create(ActionListener.class, frame, "loadData", "source.text")

is equivalent to

new ActionListener()
{
   public void actionPerformed(ActionEvent event)
   {
      frame.loadData(((JTextField) event.getSource()).getText());
   }
}

Note that the event handler turns the names of the properties source and text into method calls getSource and getText, using the JavaBeans convention. (For more information on properties and JavaBeans components, please turn to Volume 2.)

However, in practice, this situation is not all that common, and there is no mechanism for supplying parameters that aren’t derived from the event object.

Turning Components into Event Listeners

You are completely free to designate any object of a class that implements the ActionListener interface as a button listener. We prefer to use objects of a new class that was expressly created for carrying out the desired button actions. However, some programmers are not comfortable with inner classes and choose a different strategy. They locate the component that changes as a result of the event, make that component implement the ActionListener interface, and add an actionPerformed method. In our example, you can turn the ButtonPanel into an action listener:

class ButtonPanel extends JPanel implements ActionListener
{
   . . .
   public void actionPerformed(ActionEvent event)
   {
      // set background color
      . . .
   }
}

Then the panel sets itself as the listener to all three buttons:

yellowButton.addActionListener(this);
blueButton.addActionListener(this);
redButton.addActionListener(this);

Note that now the three buttons no longer have individual listeners. They share a single listener object, namely, the button panel. Therefore, the actionPerformed method must figure out which button was clicked.

The getSource method of the EventObject class, the superclass of all other event classes, will tell you the source of every event. The event source is the object that generated the event and notified the listener:

Object source = event.getSource();

The actionPerformed method can then check which of the buttons was the source:

if (source == yellowButton) . . .
else if (source == blueButton) . . .
else if (source == redButton ) . . .

Of course, this approach requires that you keep references to the buttons as instance fields in the surrounding panel.

As you can see, turning the button panel into the action listener isn’t really any simpler than defining an inner class. It also becomes really messy when the panel contains multiple user interface elements.

Caution

Caution

Some programmers use a different way to find out the event source in a listener object that is shared among multiple sources. The ActionEvent class has a getActionCommand method that returns the command string associated with this action. For buttons, it turns out that the command string defaults to being the button label. If you take this approach, an actionPerformed method contains code like this:

String command = event.getActionCommand();
if (command.equals("Yellow")) . . .;
else if (command.equals("Blue")) . . .;
else if (command.equals("Red")) . . .;

We suggest that you do not follow this approach. Relying on the button strings is dangerous. It is an easy mistake to label a button "Gray" and then spell the string slightly differently in the test:

if (command.equals("Grey")) . . .

Button strings give you grief when the time comes to internationalize your application. To make the German version with button labels “Gelb,” “Blau,” and “Rot,” you have to change both the button labels and the strings in the actionPerformed method.

Caution
java.util.EventObject 1.1
  • Object getSource()

    returns a reference to the object where the event occurred.

Caution
java.awt.event.ActionEvent 1.1
  • String getActionCommand()

    returns the command string associated with this action event. If the action event originated from a button, the command string equals the button label, unless it has been changed with the setActionCommand method.

Caution
java.beans.EventHandler 1.4
  • static Object create(Class listenerInterface, Object target, String action)

  • static Object create(Class listenerInterface, Object target, String action, String eventProperty)

  • static Object create(Class listenerInterface, Object target, String action, String eventProperty, String listenerMethod)

    construct an object of a proxy class that implements the given interface. Either the named method or all methods of the interface carry out the given action on the target object.

    The action can be a method name or a property of the target. If it is a property, its setter method is executed. For example, an action "text" is turned into a call of the setText method.

    The event property consists of one or more dot-separated property names. The first property is read from the parameter of the listener method. The second property is read from the resulting object, and so on. The final result becomes the parameter of the action. For example, the property "source.text" is turned into calls to the getSource and getText methods.

Example: Changing the Look and Feel

By default, Swing programs use the Metal look and feel. There are two ways to change to a different look and feel. The first way is to supply a file swing.properties in the jre/lib subdirectory of your Java installation. In that file, set the property swing.defaultlaf to the class name of the look and feel that you want. For example,

swing.defaultlaf=com.sun.java.swing.plaf.motif.MotifLookAndFeel

Note that the Metal look and feel is located in the javax.swing package. The other look-and-feel packages are located in the com.sun.java package and need not be present in every Java implementation. Currently, for copyright reasons, the Windows and Mac look-and-feel packages are only shipped with the Windows and Mac versions of the Java runtime environment.

Tip

Tip

Here is a useful tip for testing. Because lines starting with a # character are ignored in property files, you can supply several look and feel selections in the swing.properties file and move around the # to select one of them:

#swing.defaultlaf=javax.swing.plaf.metal.MetalLookAndFeel
swing.defaultlaf=com.sun.java.swing.plaf.motif.MotifLookAndFeel
#swing.defaultlaf=com.sun.java.swing.plaf.windows.WindowsLookAndFeel

You must restart your program to switch the look and feel in this way. A Swing program reads the swing.properties file only once, at startup.

The second way is to change the look and feel dynamically. Call the static UIManager.setLookAndFeel method and give it the name of the look-and-feel class that you want. Then call the static method SwingUtilities.updateComponentTreeUI to refresh the entire set of components. You need to supply one component to that method; it will find all others. The UIManager.setLookAndFeel method may throw a number of exceptions when it can’t find the look and feel that you request, or when there is an error loading it. As always, we ask you to gloss over the exception handling code and wait until Chapter 11 for a full explanation.

Here is an example showing how you can switch to the Motif look and feel in your program:

String plaf = "com.sun.java.swing.plaf.motif.MotifLookAndFeel";
try
{
   UIManager.setLookAndFeel(plaf);
   SwingUtilities.updateComponentTreeUI(panel);
}
catch(Exception e) { e.printStackTrace(); }

To enumerate all installed look and feel implementations, call

UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels();

Then you can get the name and class name for each look and feel as

String name = infos[i].getName();
String className = infos[i].getClassName();

Example 8–2 is a complete program that demonstrates how to switch the look and feel (see Figure 8–3). The program is similar to Example 8–1. Following the advice of the preceding section, we use a helper method makeButton and an anonymous inner class to specify the button action, namely, to switch the look and feel.

Switching the look and feel

Figure 8–3. Switching the look and feel

There is one fine point to this program. The actionPerformed method of the inner action listener class needs to pass the this reference of the outer PlafPanel class to the updateComponentTreeUI method. Recall from Chapter 6 that the outer object’s this pointer must be prefixed by the outer class name:

SwingUtilities.updateComponentTreeUI(PlafPanel.this);

Example 8–2. PlafTest.java

 1. import java.awt.*;
 2. import java.awt.event.*;
 3. import javax.swing.*;
 4.
 5. public class PlafTest
 6. {
 7.    public static void main(String[] args)
 8.    {
 9.       PlafFrame frame = new PlafFrame();
10.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
11.       frame.setVisible(true);
12.    }
13. }
14.
15. /**
16.    A frame with a button panel for changing look and feel
17. */
18. class PlafFrame extends JFrame
19. {
20.    public PlafFrame()
21.    {
22.       setTitle("PlafTest");
23.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
24.
25.       // add panel to frame
26.
27.       PlafPanel panel = new PlafPanel();
28.       add(panel);
29.    }
30.
31.    public static final int DEFAULT_WIDTH = 300;
32.    public static final int DEFAULT_HEIGHT = 200;
33. }
34.
35. /**
36.    A panel with buttons to change the pluggable look and feel
37. */
38. class PlafPanel extends JPanel
39. {
40.    public PlafPanel()
41.    {
42.       UIManager.LookAndFeelInfo[] infos = UIManager.getInstalledLookAndFeels();
43.       for (UIManager.LookAndFeelInfo info : infos)
44.          makeButton(info.getName(), info.getClassName());
45.    }
46.
47.    /**
48.       Makes a button to change the pluggable look and feel.
49.       @param name the button name
50.       @param plafName the name of the look and feel class
51.     */
52.    void makeButton(String name, final String plafName)
53.    {
54.       // add button to panel
55.
56.       JButton button = new JButton(name);
57.       add(button);
58.
59.       // set button action
60.
61.       button.addActionListener(new
62.          ActionListener()
63.          {
64.             public void actionPerformed(ActionEvent event)
65.             {
66.                // button action: switch to the new look and feel
67.                try
68.                {
69.                   UIManager.setLookAndFeel(plafName);
70.                   SwingUtilities.updateComponentTreeUI(PlafPanel.this);
71.                }
72.                catch(Exception e) { e.printStackTrace(); }
73.             }
74.          });
75.    }
76. }
PlafTest.java
javax.swing.UIManager 1.2
  • static UIManager.LookAndFeelInfo[] getInstalledLookAndFeels()

    gets an array of objects that describe the installed look-and-feel implementations.

  • static setLookAndFeel(String className)

    sets the current look and feel.

    Parameters:

    className

    the name of the look-and-feel implementation class

PlafTest.java
javax.swing.UIManager.LookAndFeelInfo 1.2
  • String getName()

    returns the display name for the look and feel.

  • String getClassName()

    returns the name of the implementation class for the look and feel.

Example: Capturing Window Events

Not all events are as simple to handle as button clicks. Here is an example of a more complex case that we already briefly noted in Chapter 7. Before the appearance of the EXIT_ON_CLOSE option in the JDK 1.3, programmers had to manually exit the program when the main frame was closed. In a non-toy program, you will want to do that as well because you want to close the program only after you check that the user won’t lose work. For example, when the user closes the frame, you may want to put up a dialog to warn the user if unsaved work is about to be lost and to exit the program only when the user agrees.

When the program user tries to close a frame window, the JFrame object is the source of a WindowEvent. If you want to catch that event, you must have an appropriate listener object and add it to the list of window listeners.

WindowListener listener = . . .;
frame.addWindowListener(listener);

The window listener must be an object of a class that implements the WindowListener interface. There are actually seven methods in the WindowListener interface. The frame calls them as the responses to seven distinct events that could happen to a window. The names are self-explanatory, except that “iconified” is usually called “minimized” under Windows. Here is the complete WindowListener interface:

public interface WindowListener
{
   void windowOpened(WindowEvent e);
   void windowClosing(WindowEvent e);
   void windowClosed(WindowEvent e);
   void windowIconified(WindowEvent e);
   void windowDeiconified(WindowEvent e);
   void windowActivated(WindowEvent e);
   void windowDeactivated(WindowEvent e);
}

Note

Note

To find out whether a window has been maximized, install a WindowStateListener. See the following API notes on page 302 for details.

As is always the case in Java, any class that implements an interface must implement all its methods; in this case, that means implementing seven methods. Recall that we are only interested in one of these seven methods, namely, the windowClosing method.

Of course, we can define a class that implements the interface, add a call to System.exit(0) in the windowClosing method, and write do-nothing functions for the other six methods:

class Terminator implements WindowListener
{
   public void windowClosing(WindowEvent e)
   {
      System.exit(0);
   }

   public void windowOpened(WindowEvent e) {}
   public void windowClosed(WindowEvent e) {}
   public void windowIconified(WindowEvent e) {}
   public void windowDeiconified(WindowEvent e) {}
   public void windowActivated(WindowEvent e) {}
   public void windowDeactivated(WindowEvent e) {}
}

Adapter Classes

Typing code for six methods that don’t do anything is the kind of tedious busywork that nobody likes. To simplify this task, each of the AWT listener interfaces that has more than one method comes with a companion adapter class that implements all the methods in the interface but does nothing with them. For example, the WindowAdapter class has seven do-nothing methods. This means the adapter class automatically satisfies the technical requirements that Java imposes for implementing the associated listener interface. You can extend the adapter class to specify the desired reactions to some, but not all, of the event types in the interface. (An interface such as ActionListener that has only a single method does not need an adapter class.)

Let us make use of the window adapter. We can extend the WindowAdapter class, inherit six of the do-nothing methods, and override the windowClosing method:

class Terminator extends WindowAdapter
{
   public void windowClosing(WindowEvent e)
   {
      System.exit(0);
   }
}

Now you can register an object of type Terminator as the event listener:

WindowListener listener = new Terminator();
frame.addWindowListener(listener);

Whenever the frame generates a window event, it passes it to the listener object by calling one of its seven methods (see Figure 8–4). Six of those methods do nothing; the windowClosing method calls System.exit(0), terminating the application.

A window listenerwindowswindow listener diagramlistenerswindow listener diagram

Figure 8–4. A window listener

Caution

Caution

If you misspell the name of a method when extending an adapter class, then the compiler won’t catch your error. For example, if you define a method windowIsClosing in a WindowAdapter class, then you get a class with eight methods, and the windowClosing method does nothing.

Creating a listener class that extends the WindowAdapter is an improvement, but we can go even further. There is no need to give a name to the listener object. Simply write

frame.addWindowListener(new Terminator());

But why stop there? We can make the listener class into an anonymous inner class of the frame.

frame.addWindowListener(new
   WindowAdapter()
   {
      public void windowClosing(WindowEvent e)
      {
         System.exit(0);
      }
   });

This code does the following:

  • Defines a class without a name that extends the WindowAdapter class;

  • Adds a windowClosing method to that anonymous class (as before, this method exits the program);

  • Inherits the remaining six do-nothing methods from WindowAdapter;

  • Creates an object of this class; that object does not have a name, either;

  • Passes that object to the addWindowListener method.

We say again that the syntax for using anonymous inner classes takes some getting used to. The payoff is that the resulting code is as short as possible.

Caution
java.awt.event.WindowListener 1.1
  • void windowOpened(WindowEvent e)

    is called after the window has been opened.

  • void windowClosing(WindowEvent e)

    is called when the user has issued a window manager command to close the window. Note that the window will close only if its hide or dispose method is called.

  • void windowClosed(WindowEvent e)

    is called after the window has closed.

  • void windowIconified(WindowEvent e)

    is called after the window has been iconified.

  • void windowDeiconified(WindowEvent e)

    is called after the window has been deiconified.

  • void windowActivated(WindowEvent e)

    is called after the window has become active. Only a frame or dialog can be active. Typically, the window manager decorates the active window, for example, by highlighting the title bar.

  • void windowDeactivated(WindowEvent e)

    is called after the window has become deactivated.

Caution
java.awt.event.WindowStateListener 1.4
  • void windowStateChanged(WindowEvent event)

    is called after the window has been maximized, iconified, or restored to its normal size.

Caution
java.awt.event.WindowEvent 1.1
  • int getNewState() 1.4

  • int getOldState() 1.4

    return the new and old state of a window in a window state change event. The returned integer is one of the following values:

    Frame.NORMAL
    Frame.ICONIFIED
    Frame.MAXIMIZED_HORIZ
    Frame.MAXIMIZED_VERT
    Frame.MAXIMIZED_BOTH
    

The AWT Event Hierarchy

Having given you a taste of how event handling works, we want to turn to a more general discussion of event handling in Java. As we briefly mentioned earlier, event handling in Java is object oriented, with all events descending from the EventObject class in the java.util package. (The common superclass is not called Event because that is the name of the event class in the old event model. Although the old model is now deprecated, its classes are still a part of the Java library.)

The EventObject class has a subclass AWTEvent, which is the parent of all AWT event classes. Figure 8–5 shows the inheritance diagram of the AWT events.

Inheritance diagram of AWT event classesinheritanceAWT event class diagram

Figure 8–5. Inheritance diagram of AWT event classes

Some of the Swing components generate event objects of yet more event types; these directly extend EventObject, not AWTEvent.

The event objects encapsulate information about the event that the event source communicates to its listeners. When necessary, you can then analyze the event objects that were passed to the listener object, as we did in the button example with the getSource and getActionCommand methods.

Some of the AWT event classes are of no practical use for the Java programmer. For example, the AWT inserts PaintEvent objects into the event queue, but these objects are not delivered to listeners. Java programmers don’t listen to paint events; they override the paintComponent method to control repainting. The AWT also generates a number of events that are needed only by system programmers, to provide input systems for ideographic languages, automated testing robots, and so on. We do not discuss these specialized event types. Finally, we omit events that are associated with obsolete AWT components.

Here is a list of the commonly used AWT event types.

ActionEvent                      KeyEvent
AdjustmentEvent                  MouseEvent
FocusEvent                       MouseWheelEvent
ItemEvent                        WindowEvent

You will see examples of these event types in this chapter and the next.

The javax.swing.event package contains additional events that are specific to Swing components. We cover some of them in the next chapter.

The following interfaces listen to these events.

ActionListener                  MouseMotionListener
AdjustmentListener              MouseWheelListener
FocusListener                   WindowListener
ItemListener                    WindowFocusListener
KeyListener                     WindowStateListener
MouseListener

You have already seen the ActionListener and WindowListener interface.

Although the javax.swing.event package contains many more listener interfaces that are specific to Swing user interface components, it still uses the basic AWT listener interfaces extensively for general event processing.

Several of the AWT listener interfaces, namely, those that have more than one method, come with a companion adapter class that implements all the methods in the interface to do nothing. (The other interfaces have only a single method each, so there is no benefit in having adapter classes for these interfaces.) Here are the commonly used adapter classes:

FocusAdapter                    MouseMotionAdapter
KeyAdapter                      WindowAdapter
MouseAdapter

Obviously, there are a lot of classes and interfaces to keep track of—it can all be a bit overwhelming. Fortunately, the principle is simple. A class that is interested in receiving events must implement a listener interface. It registers itself with the event source. It then gets the events that it asked for and processes them through the methods of the listener interface.

C++ Note

C++ Note

People coming from a C/C++ background may be wondering: Why the proliferation of objects, methods, and interfaces needed for event handling? C++ programmers are accustomed to doing GUI programming by writing callbacks with generic pointers or handles. This won’t work in Java. The Java event model is strongly typed: the compiler watches out that events are sent only to objects that are capable of handling them.

Semantic and Low-Level Events in the AWT

The AWT makes a useful distinction between low-level and semantic events. A semantic event is one that expresses what the user is doing, such as “clicking that button”; hence, an ActionEvent is a semantic event. Low-level events are those events that make this possible. In the case of a button click, this is a mouse down, a series of mouse moves, and a mouse up (but only if the mouse up is inside the button area). Or it might be a keystroke, which happens if the user selects the button with the TAB key and then activates it with the space bar. Similarly, adjusting a scrollbar is a semantic event, but dragging the mouse is a low-level event.

Here are the most commonly used semantic event classes in the java.awt.event package:

  • ActionEvent (for a button click, a menu selection, selecting a list item, or ENTER typed in a text field)

  • AdjustmentEvent (the user adjusted a scrollbar)

  • ItemEvent (the user made a selection from a set of checkbox or list items)

Five low-level event classes are commonly used:

  • KeyEvent (a key was pressed or released)

  • MouseEvent (the mouse button was pressed, released, moved, or dragged)

  • MouseWheelEvent (the mouse wheel was rotated)

  • FocusEvent (a component got focus, or lost focus). See page 320 for more information about the focus concept.

  • WindowEvent (the window state changed)

Table 8–1 shows the most important AWT listener interfaces, events, and event sources.

Table 8–1. Event Handling Summary

Interface

Methods

Parameter/Accessors

Events Generated By

ActionListener

actionperformed

ActionEvent

  • getActionCommand

  • getModifiers

AbstractButton
JComboBox
JTextField
Timer

AdjustmentListener

adjustmentvaluechanged

AdjustmentEvent

  • getAdjustable

  • getAdjustmentType

  • getValue

JScrollbar

ItemListener

itemstatechanged

ItemEvent

  • getItem

  • getItemSelectable

  • getStateChange

AbstractButton
JComboBox

FocusListener

focusgained
focuslost

FocusEvent

  • isTemporary

Component

KeyListener

keypressed
keyreleased
keytyped

KeyEvent

  • getKeyChar

  • getKeyCode

  • getKeyModifiersText

  • getKeyText

  • isActionKey

Component

MouseListener

mousepressed
mousereleased
mouseentered
mouseexited
mouseclicked

MouseEvent

  • getClickCount

  • getX

  • getY

  • getPoint

  • translatePoint

Component

MouseMotionListener

mousedragged
mousemoved

MouseEvent

Component

MouseWheelListener

mousewheelmoved

MouseWheelEvent

  • getWheelRotation

  • getScrollAmount

Component

WindowListener

windowclosing
windowopened
windowiconified
windowdeiconified
windowclosed
windowactivated
windowdeactivated

WindowEvent

  • getWindow

Window

WindowFocusListener

windowgainedfocus
windowlostfocus

WindowEvent

  • getOppositeWindow

Window

WindowStateListener

WindowStateChanged

WindowEvent

  • getOldState

  • getNewState

Window

Event Handling Summary

Let’s go over the event delegation mechanism one more time to make sure that you understand the relationship between event classes, listener interfaces, and adapter classes.

Event sources are user interface components, windows, and menus. The operating system notifies an event source about interesting activities, such as mouse moves and keystrokes. The event source describes the nature of the event in an event object. It also keeps a set of listeners—objects that want to be called when the event happens (see Figure 8–6). The event source then calls the appropriate method of the listener interface to deliver information about the event to the various listeners. The source does this by passing the appropriate event object to the method in the listener class. The listener analyzes the event object to find out more about the event. For example, you can use the getSource method to find out the source, or the getX and getY methods of the MouseEvent class to find out the current location of the mouse.

Relationship between event sources and listeners

Figure 8–6. Relationship between event sources and listeners

Note that there are separate MouseListener and MouseMotionListener interfaces. This is done for efficiency—there are a lot of mouse events as the user moves the mouse around, and a listener that just cares about mouse clicks will not be bothered with unwanted mouse moves.

All low-level events inherit from ComponentEvent. This class has a method, called getComponent, which reports the component that originated the event; you can use getComponent instead of getSource. The getComponent method returns the same value as getSource, but already cast as a Component. For example, if a key event was fired because of an input into a text field, then getComponent returns a reference to that text field.

Relationship between event sources and listeners
java.awt.event.ComponentEvent 1.0
  • Component getComponent()

    returns a reference to the component that is the source for the event. This is the same as (Component) getSource().

Low-Level Event Types

In the sections that follow, we discuss in more detail the events that are not linked to specific user interface components, in particular, events related to keystrokes and mouse activity. You can find a detailed discussion of semantic events generated by user interface components in the next chapter.

Keyboard Events

When the user pushes a key, a KeyEvent with ID KEY_PRESSED is generated. When the user releases the key, a KEY_RELEASED KeyEvent is triggered. You trap these events in the keyPressed and keyReleased methods of any class that implements the KeyListener interface. Use these methods to trap raw keystrokes. A third method, keyTyped, combines the two: it reports on the characters that were generated by the user’s keystrokes.

The best way to see what happens is with an example. But before we can do that, we have to add a little more terminology. Java makes a distinction between characters and virtual key codes. Virtual key codes are indicated with a prefix of VK_, such as VK_A or VK_SHIFT. Virtual key codes correspond to keys on the keyboard. For example, VK_A denotes the key marked A. There is no separate lowercase virtual key code—the keyboard does not have separate lowercase keys.

Note

Note

Virtual key codes are related to the “scan codes” that the keyboard sends to the computer whenever a physical key is pressed or released.

So, suppose that the user types an uppercase “A” in the usual way, by pressing the SHIFT key along with the A key. Java reports five events in response to this user action. Here are the actions and the associated events:

  1. Pressed the SHIFT key (keyPressed called for VK_SHIFT)

  2. Pressed the A key (keyPressed called for VK_A)

  3. Typed “A” (keyTyped called for an “A”)

  4. Released the A key (keyReleased called for VK_A)

  5. Released the SHIFT key (keyReleased called for VK_SHIFT)

On the other hand, if the user typed a lowercase “a” by simply pressing the A key, then only three events occur:

  1. Pressed the A key (keyPressed called for VK_A)

  2. Typed “a” (keyTyped called for an “a”)

  3. Released the A key (keyReleased called for VK_A)

Thus, the keyTyped procedure reports the character that was typed (“A” or “a”), whereas the keyPressed and keyReleased methods report on the actual keys that the user pressed.

To work with the keyPressed and keyReleased methods, you should first check the key code.

public void keyPressed(KeyEvent event)
{
   int keyCode = event.getKeyCode();
   . . .
}

The key code will equal one of the following (reasonably mnemonic) constants. They are defined in the KeyEvent class.

VK_A . . . VK_ZVK_0 . . . VK_9VK_COMMA, VK_PERIOD, VK_SLASH, VK_SEMICOLON, VK_EQUALSVK_OPEN_BRACKET, VK_BACK_SLASH, VK_CLOSE_BRACKETVK_BACK_QUOTE, VK_QUOTEVK_GREATER, VK_LESS, VK_UNDERSCORE, VK_MINUSVK_AMPERSAND, VK_ASTERISK, VK_AT, VK_BRACELEFT, VK_BRACERIGHTVK_LEFT_PARENTHESIS, VK_RIGHT_PARENTHESISVK_CIRCUMFLEX, VK_COLON, VK_NUMBER_SIGN, VK_QUOTEDBLVK_EXCLAMATION_MARK, VK_INVERTED_EXCLAMATION_MARKVK_DEAD_ABOVEDOT, VK_DEAD_ABOVERING, VK_DEAD_ACUTEVK_DEAD_BREVEVK_DEAD_CARON, VK_DEAD_CEDILLA, VK_DEAD_CIRCUMFLEXVK_DEAD_DIAERESISVK_DEAD_DOUBLEACUTE, VK_DEAD_GRAVE, VK_DEAD_IOTA, VK_DEAD_MACRONVK_DEAD_OGONEK, VK_DEAD_SEMIVOICED_SOUND, VK_DEAD_TILDE VK_DEAD_VOICED_SOUNDVK_DOLLAR, VK_EURO_SIGNVK_SPACE, VK_ENTER, VK_BACK_SPACE, VK_TAB, VK_ESCAPEVK_SHIFT, VK_CONTROL, VK_ALT, VK_ALT_GRAPH, VK_METAVK_NUM_LOCK, VK_SCROLL_LOCK, VK_CAPS_LOCKVK_PAUSE, VK_PRINTSCREENVK_PAGE_UP, VK_PAGE_DOWN, VK_END, VK_HOME, VK_LEFT, VK_UP, VK_RIGHT, VK_DOWNVK_F1 . . .VK_F24VK_NUMPAD0 . . . VK_NUMPAD9VK_KP_DOWN, VK_KP_LEFT, VK_KP_RIGHT, VK_KP_UPVK_MULTIPLY, VK_ADD, VK_SEPARATER [sic], VK_SUBTRACT, VK_DECIMAL, VK_DIVIDEVK_DELETE, VK_INSERTVK_HELP, VK_CANCEL, VK_CLEAR, VK_FINALVK_CONVERT, VK_NONCONVERT, VK_ACCEPT, VK_MODECHANGEVK_AGAIN, VK_ALPHANUMERIC, VK_CODE_INPUT, VK_COMPOSE, VK_PROPSVK_STOPVK_ALL_CANDIDATES, VK_PREVIOUS_CANDIDATEVK_COPY, VK_CUT, VK_PASTE, VK_UNDOVK_FULL_WIDTH, VK_HALF_WIDTHVK_HIRAGANA, VK_KATAKANA, VK_ROMAN_CHARACTERSVK_KANA, VK_KANJIVK_JAPANESE_HIRAGANA, VK_JAPANESE_KATAKANA, VK_JAPANESE_ROMANVK_WINDOWS, VK_CONTEXT_MENUVK_UNDEFINED

To find the current state of the SHIFT, CONTROL, ALT, and META keys, you can, of course, track the VK_SHIFT, VK_CONTROL, VK_ALT, and VK_META key presses, but that is tedious. Instead, simply use the isShiftDown, isControlDown, isAltDown, and isMetaDown methods. (Sun and Macintosh keyboards have a special META key. On a Sun keyboard, the key is marked a diamond. On a Macintosh, the key is marked with an apple and a cloverleaf.)

For example, the following code tests whether the user presses SHIFT + RIGHT ARROW:

public void keyPressed(KeyEvent event)
{
   int keyCode = event.getKeyCode();
   if (keyCode == KeyEvent.VK_RIGHT && event.isShiftDown())
   {
      . . .
   }
}

In the keyTyped method, you call the getKeyChar method to obtain the actual character that was typed.

Note

Note

Not all keystrokes result in a call to keyTyped. Only those keystrokes that generate a Unicode character can be captured in the keyTyped method. You use the keyPressed method to check for cursor keys and other command keys.

Example 8–3 shows how to handle keystrokes. The program (shown in Figure 8–7) is a simple implementation of the Etch-A-Sketch™ toy.

A sketch program

Figure 8–7. A sketch program

You move a pen up, down, left, and right with the cursor keys. If you hold down the SHIFT key, the pen moves by a larger increment. Or, if you are experienced in using the vi editor, you can bypass the cursor keys and use the lowercase h, j, k, and l keys to move the pen. The uppercase H, J, K, and L move the pen by a larger increment. We trap the cursor keys in the keyPressed method and the characters in the keyTyped method.

There is one technicality: Normally, a panel does not receive any key events. To override this default, we call the setFocusable method. We discuss the concept of keyboard focus later in this chapter.

Example 8–3. Sketch.java

  1. import java.awt.*;
  2. import java.awt.geom.*;
  3. import java.util.*;
  4. import java.awt.event.*;
  5. import javax.swing.*;
  6.
  7. public class Sketch
  8. {
  9.    public static void main(String[] args)
 10.    {
 11.       SketchFrame frame = new SketchFrame();
 12.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 13.       frame.setVisible(true);
 14.    }
 15. }
 16.
 17. /**
 18.    A frame with a panel for sketching a figure
 19. */
 20. class SketchFrame extends JFrame
 21. {
 22.    public SketchFrame()
 23.    {
 24.       setTitle("Sketch");
 25.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
 26.
 27.       // add panel to frame
 28.
 29.       SketchPanel panel = new SketchPanel();
 30.       add(panel);
 31.    }
 32.
 33.    public static final int DEFAULT_WIDTH = 300;
 34.    public static final int DEFAULT_HEIGHT = 200;
 35. }
 36.
 37. /**
 38.    A panel for sketching with the keyboard.
 39. */
 40. class SketchPanel extends JPanel
 41. {
 42.    public SketchPanel()
 43.    {
 44.       last = new Point2D.Double(100, 100);
 45.       lines = new ArrayList<Line2D>();
 46.       KeyHandler listener = new KeyHandler();
 47.       addKeyListener(listener);
 48.       setFocusable(true);
 49.    }
 50.
 51.    /**
 52.       Add a new line segment to the sketch.
 53.       @param dx the movement in x direction
 54.       @param dy the movement in y direction
 55.    */
 56.    public void add(int dx, int dy)
 57.    {
 58.       // compute new end point
 59.       Point2D end = new Point2D.Double(last.getX() + dx, last.getY() + dy);
 60.
 61.       // add line segment
 62.       Line2D line = new Line2D.Double(last, end);
 63.       lines.add(line);
 64.       repaint();
 65.
 66.       // remember new end point
 67.       last = end;
 68.    }
 69.
 70.    public void paintComponent(Graphics g)
 71.    {
 72.       super.paintComponent(g);
 73.       Graphics2D g2 = (Graphics2D) g;
 74.
 75.       // draw all lines
 76.       for (Line2D l : lines)
 77.          g2.draw(l);
 78.    }
 79.
 80.    private Point2D last;
 81.    private ArrayList<Line2D> lines;
 82.
 83.    private static final int SMALL_INCREMENT = 1;
 84.    private static final int LARGE_INCREMENT = 5;
 85.
 86.    private class KeyHandler implements KeyListener
 87.    {
 88.       public void keyPressed(KeyEvent event)
 89.       {
 90.          int keyCode = event.getKeyCode();
 91.
 92.          // set distance
 93.          int d;
 94.          if (event.isShiftDown())
 95.             d = LARGE_INCREMENT;
 96.          else
 97.             d = SMALL_INCREMENT;
 98.
 99.          // add line segment
100.          if (keyCode == KeyEvent.VK_LEFT) add(-d, 0);
101.          else if (keyCode == KeyEvent.VK_RIGHT) add(d, 0);
102.          else if (keyCode == KeyEvent.VK_UP) add(0, -d);
103.          else if (keyCode == KeyEvent.VK_DOWN) add(0, d);
104.       }
105.
106.       public void keyReleased(KeyEvent event) {}
107.
108.       public void keyTyped(KeyEvent event)
109.       {
110.          char keyChar = event.getKeyChar();
111.
112.          // set distance
113.          int d;
114.          if (Character.isUpperCase(keyChar))
115.          {
116.             d = LARGE_INCREMENT;
117.             keyChar = Character.toLowerCase(keyChar);
118.          }
119.          else
120.             d = SMALL_INCREMENT;
121.
122.          // add line segment
123.          if (keyChar == 'h') add(-d, 0);
124.          else if (keyChar == 'l') add(d, 0);
125.          else if (keyChar == 'k') add(0, -d);
126.          else if (keyChar == 'j') add(0, d);
127.       }
128.    }
129. }
Sketch.java
java.awt.event.KeyEvent 1.1
  • char getKeyChar()

    returns the character that the user typed.

  • int getKeyCode()

    returns the virtual key code of this key event.

  • boolean isActionKey()

    returns true if the key in this event is an “action” key. The following keys are action keys: HOME, END, PAGE UP, PAGE DOWN, UP, DOWN, LEFT, RIGHT, F1 ... F24, PRINT SCREEN, SCROLL LOCK, CAPS LOCK, NUM LOCK, PAUSE, INSERT, DELETE, ENTER, BACKSPACE, DELETE, and TAB.

  • static String getKeyText(int keyCode)

    returns a string describing the key code. For example, getKeyText(KeyEvent.VK_END) is the string "End".

  • static String getKeyModifiersText(int modifiers)

    returns a string describing the modifier keys, such as SHIFT or CTRL + SHIFT.

    Parameters:

    modifiers

    The modifier state, as reported by getModifiers

Sketch.java
java.awt.event.InputEvent 1.1
  • int getModifiers()

    returns an integer whose bits describe the state of the modifiers SHIFT, CONTROL, ALT, and META. This method applies to both keyboard and mouse events. To see if a bit is set, test the return value against one of the bit masks SHIFT_MASK, CTRL_MASK, ALT_MASK, ALT_GRAPH_MASK, META_MASK, or use one of the following methods.

  • boolean isShiftDown()

  • boolean isControlDown()

  • boolean isAltDown()

  • boolean isAltGraphDown() 1.2

  • boolean isMetaDown()

    return true if the modifier key was held down when this event was generated.

Mouse Events

You do not need to handle mouse events explicitly if you just want the user to be able to click on a button or menu. These mouse operations are handled internally by the various components in the user interface and then translated into the appropriate semantic event. However, if you want to enable the user to draw with the mouse, you will need to trap mouse move, click, and drag events.

In this section, we show you a simple graphics editor application that allows the user to place, move, and erase squares on a canvas (see Figure 8–8).

A mouse test program

Figure 8–8. A mouse test program

When the user clicks a mouse button, three listener methods are called: mousePressed when the mouse is first pressed, mouseReleased when the mouse is released, and, finally, mouseClicked. If you are only interested in complete clicks, you can ignore the first two methods. By using the getX and getY methods on the MouseEvent argument, you can obtain the x- and y-coordinates of the mouse pointer when the mouse was clicked. To distinguish between single, double, and triple (!) clicks, use the getClickCount method.

Some user interface designers inflict mouse click and keyboard modifier combinations, such as CONTROL + SHIFT + CLICK, on their users. We find this practice reprehensible, but if you disagree, you will find that checking for mouse buttons and keyboard modifiers is a mess. In the original API, two of the button masks equal two keyboard modifier masks, namely:

BUTTON2_MASK == ALT_MASK
BUTTON3_MASK == META_MASK

This was done so that users with a one-button mouse could simulate the other mouse buttons by holding down modifier keys instead. However, as of JDK 1.4, a different approach is recommended. There are now masks

BUTTON1_DOWN_MASK
BUTTON2_DOWN_MASK
BUTTON3_DOWN_MASK
SHIFT_DOWN_MASK
CTRL_DOWN_MASK
ALT_DOWN_MASK
ALT_GRAPH_DOWN_MASK
META_DOWN_MASK

The getModifiersEx method accurately reports the mouse buttons and keyboard modifiers of a mouse event.

Note that BUTTON3_DOWN_MASK tests for the right (nonprimary) mouse button under Windows. For example, you can use code like this to detect whether the right mouse button is down:

if ((event.getModifiersEx() & InputEvent.BUTTON3_DOWN_MASK) != 0)
   . . . // code for right click

In our sample program, we supply both a mousePressed and a mouseClicked method. When you click onto a pixel that is not inside any of the squares that have been drawn, a new square is added. We implemented this in the mousePressed method so that the user receives immediate feedback and does not have to wait until the mouse button is released. When a user double-clicks inside an existing square, it is erased. We implemented this in the mouseClicked method because we need the click count.

public void mousePressed(MouseEvent event)
{
   current = find(event.getPoint());
   if (current == null) // not inside a square
      add(event.getPoint());
}

public void mouseClicked(MouseEvent event)
{
   current = find(event.getPoint());
   if (current != null && event.getClickCount() >= 2)
      remove(current);
}

As the mouse moves over a window, the window receives a steady stream of mouse movement events. These are ignored by most applications. However, our test application traps the events to change the cursor to a different shape (a cross hair) when it is over a square. This is done with the getPredefinedCursor method of the Cursor class. Table 8–2 lists the constants to use with this method along with what the cursors look like under Windows. (Note that several of these cursors look the same, but you should check how they look on your platform.)

Table 8–2. Sample Cursor Shapes

Icon

Constant

Sample Cursor Shapes

DEFAULT_CURSOR

Sample Cursor Shapes

CROSSHAIR_CURSOR

Sample Cursor Shapes

HAND_CURSOR

Sample Cursor Shapes

MOVE_CURSOR

Sample Cursor Shapes

TEXT_CURSOR

Sample Cursor Shapes

WAIT_CURSOR

Sample Cursor Shapes

N_RESIZE_CURSOR

Sample Cursor Shapes

NE_RESIZE_CURSOR

Sample Cursor Shapes

E_RESIZE_CURSOR

Sample Cursor Shapes

SE_RESIZE_CURSOR

Sample Cursor Shapes

S_RESIZE_CURSOR

Sample Cursor Shapes

SW_RESIZE_CURSOR

Sample Cursor Shapes

W_RESIZE_CURSOR

Sample Cursor Shapes

NW_RESIZE_CURSOR

Tip

Tip

You can find cursor images in the jre/lib/images/cursors directory. The file cursors.properties defines the cursor “hot spots.” This is the point that the user associates with the pointing action of the cursor. For example, if the cursor has the shape of a magnifying glass, the hot spot would be the center of the lens.

Here is the mouseMoved method of the MouseMotionListener in our example program:

public void mouseMoved(MouseEvent event)
{
   if (find(event.getPoint()) == null)
      setCursor(Cursor.getDefaultCursor());
   else
      setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
}

Note

Note

You can also define your own cursor types through the use of the createCustomCursor method in the Toolkit class:

Toolkit tk = Toolkit.getDefaultToolkit();
Image img = tk.getImage("dynamite.gif");
Cursor dynamiteCursor = tk.createCustomCursor(img, new Point(10, 10), "dynamite stick");

The first parameter of the createCustomCursor points to the cursor image. The second parameter gives the offset of the “hot spot” of the cursor. The third parameter is a string that describes the cursor. This string can be used for accessibility support, for example, to read the cursor shape to a user who is visually impaired or who simply is not facing the screen.

If the user presses a mouse button while the mouse is in motion, mouseDragged calls are generated instead of mouseMoved calls. Our test application lets a user drag the square under the cursor. We simply update the currently dragged rectangle to be centered under the mouse position. Then, we repaint the canvas to show the new mouse position.

public void mouseDragged(MouseEvent event)
{
   if (current != null)
   {
      int x = event.getX();
      int y = event.getY();

      current.setFrame(
         x - SIDELENGTH / 2,
         y - SIDELENGTH / 2,
         SIDELENGTH,
         SIDELENGTH);
      repaint();

}

Note

Note

The mouseMoved method is only called as long as the mouse stays inside the component. However, the mouseDragged method keeps getting called even when the mouse is being dragged outside the component.

There are two other mouse event methods: mouseEntered and mouseExited. These methods are called when the mouse enters or exits a component.

Finally, we explain how to listen to mouse events. Mouse clicks are reported through the mouseClicked procedure, which is part of the MouseListener interface. Because many applications are interested only in mouse clicks and not in mouse moves and because mouse move events occur so frequently, the mouse move and drag events are defined in a separate interface called MouseMotionListener.

In our program we are interested in both types of mouse events. We define two inner classes: MouseHandler and MouseMotionHandler. The MouseHandler class extends the MouseAdapter class because it defines only two of the five MouseListener methods. The MouseMotionHandler implements the MouseMotionListener and defines both methods of that interface.

Example 8–4 is the program listing.

Example 8–4. MouseTest.java

  1. import java.awt.*;
  2. import java.awt.event.*;
  3. import java.util.*;
  4. import java.awt.geom.*;
  5. import javax.swing.*;
  6.
  7. public class MouseTest
  8. {
  9.    public static void main(String[] args)
 10.    {
 11.       MouseFrame frame = new MouseFrame();
 12.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 13.       frame.setVisible(true);
 14.    }
 15. }
 16.
 17. /**
 18.    A frame containing a panel for testing mouse operations
 19. */
 20. class MouseFrame extends JFrame
 21. {
 22.    public MouseFrame()
 23.    {
 24.       setTitle("MouseTest");
 25.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
 26.
 27.       // add panel to frame
 28.
 29.       MousePanel panel = new MousePanel();
 30.       add(panel);
 31.    }
 32.
 33.    public static final int DEFAULT_WIDTH = 300;
 34.    public static final int DEFAULT_HEIGHT = 200;
 35. }
 36.
 37. /**
 38.    A panel with mouse operations for adding and removing squares.
 39. */
 40. class MousePanel extends JPanel
 41. {
 42.    public MousePanel()
 43.    {
 44.       squares = new ArrayList<Rectangle2D>();
 45.       current = null;
 46.
 47.       addMouseListener(new MouseHandler());
 48.       addMouseMotionListener(new MouseMotionHandler());
 49.    }
 50.
 51.    public void paintComponent(Graphics g)
 52.    {
 53.       super.paintComponent(g);
 54.       Graphics2D g2 = (Graphics2D) g;
 55.
 56.       // draw all squares
 57.       for (Rectangle2D r : squares)
 58.          g2.draw(r);
 59.    }
 60.
 61.    /**
 62.       Finds the first square containing a point.
 63.       @param p a point
 64.       @return the first square that contains p
 65.    */
 66.    public Rectangle2D find(Point2D p)
 67.    {
 68.       for (Rectangle2D r : squares)
 69.       {
 70.          if (r.contains(p)) return r;
 71.       }
 72.       return null;
 73.    }
 74.
 75.    /**
 76.       Adds a square to the collection.
 77.       @param p the center of the square
 78.    */
 79.    public void add(Point2D p)
 80.    {
 81.       double x = p.getX();
 82.       double y = p.getY();
 83.
 84.       current = new Rectangle2D.Double(
 85.          x - SIDELENGTH / 2,
 86.          y - SIDELENGTH / 2,
 87.          SIDELENGTH,
 88.          SIDELENGTH);
 89.       squares.add(current);
 90.       repaint();
 91.    }
 92.
 93.    /**
 94.       Removes a square from the collection.
 95.       @param s the square to remove
 96.    */
 97.    public void remove(Rectangle2D s)
 98.    {
 99.       if (s == null) return;
100.       if (s == current) current = null;
101.       squares.remove(s);
102.       repaint();
103.    }
104.
105.
106.    private static final int SIDELENGTH = 10;
107.    private ArrayList<Rectangle2D> squares;
108.    private Rectangle2D current;
109.    // the square containing the mouse cursor
110.
111.    private class MouseHandler extends MouseAdapter
112.    {
113.       public void mousePressed(MouseEvent event)
114.       {
115.          // add a new square if the cursor isn't inside a square
116.          current = find(event.getPoint());
117.          if (current == null)
118.             add(event.getPoint());
119.       }
120.
121.       public void mouseClicked(MouseEvent event)
122.       {
123.          // remove the current square if double clicked
124.          current = find(event.getPoint());
125.          if (current != null && event.getClickCount() >= 2)
126.             remove(current);
127.       }
128.    }
129.
130.    private class MouseMotionHandler
131.       implements MouseMotionListener
132.    {
133.       public void mouseMoved(MouseEvent event)
134.       {
135.          // set the mouse cursor to cross hairs if it is inside
136.          // a rectangle
137.
138.          if (find(event.getPoint()) == null)
139.             setCursor(Cursor.getDefaultCursor());
140.          else
141.             setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
142.       }
143.
144.       public void mouseDragged(MouseEvent event)
145.       {
146.          if (current != null)
147.          {
148.             int x = event.getX();
149.             int y = event.getY();
150.
151.             // drag the current rectangle to center it at (x, y)
152.             current.setFrame(
153.                x - SIDELENGTH / 2,
154.                y - SIDELENGTH / 2,
155.                SIDELENGTH,
156.                SIDELENGTH);
157.             repaint();
158.          }
159.       }
160.    }
161. }
MouseTest.java
java.awt.event.MouseEvent 1.1
  • int getX()

  • int getY()

  • Point getPoint()

    return the x- (horizontal) and y- (vertical) coordinate, or point where the event happened, measured from the top-left corner of the component that is the event source.

  • void translatePoint(int x, int y)

    translates the coordinates of the event by moving x units horizontally and y units vertically.

  • int getClickCount()

    returns the number of consecutive mouse clicks associated with this event. (The time interval for what constitutes “consecutive” is system dependent.)

MouseTest.java
java.awt.event.InputEvent 1.1
  • int getModifiersEx() 1.4

    returns the extended or “down” modifiers for this event. Use the following mask values to test the returned value:

    BUTTON1_DOWN_MASK
    BUTTON2_DOWN_MASK
    BUTTON3_DOWN_MASK
    SHIFT_DOWN_MASK
    CTRL_DOWN_MASK
    ALT_DOWN_MASK
    ALT_GRAPH_DOWN_MASK
    META_DOWN_MASK
    
  • static String getModifiersExText(int modifiers) 1.4

    returns a string such as “Shift+Button1” describing the extended or “down” modifiers in the given flag set.

MouseTest.java
java.awt.Toolkit 1.0
  • public Cursor createCustomCursor(Image image, Point hotSpot, String name) 1.2

    creates a new custom cursor object.

    Parameters:

    image

    The image to display when the cursor is active

     

    hotSpot

    The cursor’s hot spot (such as the tip of an arrow or the center of cross hairs)

     

    name

    A description of the cursor, to support special accessibility environments

MouseTest.java
java.awt.Component 1.0
  • public void setCursor(Cursor cursor) 1.1

    sets the cursor image to the specified cursor.

Focus Events

When you use a mouse, you can point to any object on the screen. But when you type, your keystrokes must go to a specific screen object. The window manager (such as Windows or X Windows) directs all keystrokes to the active window. Often, the active window is distinguished by a highlighted title bar. Only one window can be active at any one time.

Now suppose the active window is controlled by a Java program. The Java window receives the keystrokes, and it in turn directs them toward a particular component. That component is said to have focus. Just like the active window is usually distinguished in some way, most Swing components give a visual cue if they currently have focus. A text field has a blinking caret, a button has a rectangle around the label, and so on. When a text field has focus, you can enter text into the text field. When a button has focus, you can “click” it by pressing the space bar.

Only one component in a window can have focus. A component loses focus if the user clicks on another component, which then gains focus. The user can also use the TAB key to give focus to each component in turn. This traverses all components that are able to receive input focus. By default, Swing components are traversed from left to right, then top to bottom, as they are laid out in the container. You can change the focus traversal order; see the next chapter for more on this subject.

Note

Note

Unfortunately, focus handling in older versions of the JDK had quite a few problems, with over 100 separate bugs reported. The reasons for focus flakiness were twofold. Component focus interacts with window focus, which is the responsibility of the windowing system. Therefore, some focus behavior showed platform-dependent variations. Moreover, the implementation code had apparently mushroomed out of control, particularly with the addition of an unsatisfactory focus traversal architecture in JDK 1.3.

Winston Churchill once said “The Americans will always do the right thing... after they’ve exhausted all the alternatives.” Apparently, the same is true for the Java team, and they decided to do the right thing in JDK 1.4. They completely reimplemented the focus handling code, and they provided a complete description of the expected behavior, including an explanation of unavoidable platform dependencies.

You can find the focus specification at http://java.sun.com/j2se/1.4/docs/api/java/awt/doc-files/FocusSpec.html.

Fortunately, most application programmers don’t need to worry too much about focus handling. Before JDK 1.4, a common use for trapping component focus events was error checking or data validation. Suppose you have a text field that contains a credit card number. When the user is done editing the field and moves to another field, you trap the lost focus event. If the credit card format was not formatted properly, you can display an error message and give the focus back to the credit card field. However, JDK 1.4 has robust validation mechanisms that are easier to program. We discuss validation in Chapter 9.

Some components, such as labels or panels, do not get focus by default because it is assumed that they are just used for decoration or grouping. You need to override this default if you implement a drawing program with panels that paint something in reaction to keystrokes. As of JDK 1.4, you can simply call

panel.setFocusable(true);

Note

Note

In older versions of the JDK, you had to override the isFocusTraversable method of the component to achieve the same effect. However, the old focus implementation had separate concepts for the ability to gain focus and participation in focus traversal. That distinction led to confusing behavior and has now been removed. The isFocusTraversable method has been deprecated.

In the remainder of this section, we discuss focus event details that you can safely skip until you have a special need that requires fine-grained control of focus handling.

In JDK 1.4, you can easily find out

  • The focus owner, that is, the component that has focus;

  • The focused window, that is, the window that contains the focus owner;

  • The active window, that is, the frame or dialog that contains the focus owner.

The focused window is usually the same as the active window. You get a different result only when the focus owner is contained in a top-level window with no frame decorations, such as a pop-up menu.

To obtain that information, first get the keyboard focus manager:

KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager();

Then call

Component owner = manager.getFocusOwner();
Window focused = manager.getFocusedWindow();
Window active = manager.getActiveWindow(); // a frame or dialog

For notification of focus changes, you need to install focus listeners into components or windows. A component focus listener must implement the FocusListener interface with two methods, focusGained and focusLost. These methods are triggered when the component gains or loses the focus. Each of these methods has a FocusEvent parameter. There are several useful methods for this class. The getComponent method reports the component that gained or lost the focus, and the isTemporary method returns true if the focus change was temporary. A temporary focus change occurs when a component loses control temporarily but will automatically get it back. This happens, for example, when the user selects a different active window. As soon as the user selects the current window again, the same component regains focus.

JDK 1.4 introduces the delivery of window focus events. You add a WindowFocusListener to a window and implement the windowGainedFocus and windowLostFocus methods.

As of JDK 1.4, you can find out the “opposite” component or window when focus is transferred. When a component or window has lost focus, the opposite is the component or window that gains focus. Conversely, when a component or window gains focus, then its opposite is the one that lost focus. The getOppositeComponent method of the FocusEvent class reports the opposite component, and the getOppositeWindow of the WindowEvent class reports the opposite window.

You can programmatically move the focus to another component by calling the requestFocus method of the Component class. However, the behavior is intrinsically platform dependent if the component is not contained in the currently focused window. To enable programmers to develop platform-independent code, JDK 1.4 adds a method requestFocusInWindow to the Component class. That method succeeds only if the component is contained in the focused window.

Note

Note

You should not assume that your component has focus if requestFocus or requestFocusInWindow returns true. Wait for the FOCUS_GAINED event to be delivered to be sure.

Note

Note

Some programmers are confused about the FOCUS_LOST event and try to stop another component from gaining focus by requesting the focus in the focusLost handler. However, at that time, the focus is already lost. If you must trap the focus in a particular component, install a “vetoable change listener” in the KeyboardFocusManager and veto the focusOwner property. See Chapter 8 of Volume 2 for details on property vetoes.

Note
java.awt.Component 1.0
  • void requestFocus()

    requests that this component gets the focus.

  • boolean requestFocusInWindow() 1.4

    requests that this component gets the focus. Returns false if this component is not contained in the focused window or if the request cannot be fulfilled for another reason. Returns true if it is likely that the request will be fulfilled.

  • void setFocusable(boolean b) 1.4

  • boolean isFocusable() 1.4

    set or get the “focusable” state of this component. If b is true, then this component can gain focus.

  • boolean isFocusOwner() 1.4

    returns true if this component currently owns the focus.

Note
java.awt.KeyboardFocusManager 1.4
  • static KeyboardFocusManager getCurrentKeyboardFocusManager()

    gets the current focus manager.

  • Component getFocusOwner()

    gets the component that owns the focus, or null if this focus manager does not manage the component that has focus.

  • Window getFocusedWindow()

    gets the window that contains the component that owns the focus, or null if this focus manager does not manage the component that has focus.

  • Window getActiveWindow()

    gets the dialog or frame that contains the focused window, or null if this focus manager does not manage the focused window.

Note
java.awt.Window() 1.0
  • boolean isFocused() 1.4

    returns true if this window is currently the focused window.

  • boolean isActive() 1.4

    returns true if this frame or dialog is currently the active window. The title bars of active frames and dialogs are usually marked by the window manager.

Note
java.awt.event.FocusEvent 1.1
  • Component getOppositeComponent() 1.4

    returns the component that lost focus in the focusGained handler, or the component that gained focus in the focusLost handler.

Note
java.awt.event.WindowEvent 1.4
  • Window getOppositeWindow() 1.4

    returns the window that lost focus in the windowGainedFocus handler, the window that gained focus in the windowLostFocus handler, the window that was deactivated in the windowActivated handler, or the window that was activated in the windowDeactivated handler.

Note
java.awt.event.WindowFocusListener 1.4
  • void windowGainedFocus(WindowEvent event)

    is called when the event source window gained focus.

  • void windowLostFocus(WindowEvent event)

    is called when the event source window lost focus.

Actions

It is common to have multiple ways to activate the same command. The user can choose a certain function through a menu, a keystroke, or a button on a toolbar. This is easy to achieve in the AWT event model: link all events to the same listener. For example, suppose blueAction is an action listener whose actionPerformed method changes the background color to blue. You can attach the same object as a listener to several event sources:

  • A toolbar button labeled “Blue”

  • A menu item labeled “Blue”

  • A keystroke CTRL+B

Then the color change command is handled in a uniform way, no matter whether it was caused by a button click, a menu selection, or a key press.

The Swing package provides a very useful mechanism to encapsulate commands and to attach them to multiple event sources: the Action interface. An action is an object that encapsulates

  • A description of the command (as a text string and an optional icon); and

  • Parameters that are necessary to carry out the command (such as the requested color in our example).

The Action interface has the following methods:

void actionPerformed(ActionEvent event)
void setEnabled(boolean b)
boolean isEnabled()
void putValue(String key, Object value)
Object getValue(String key)
void addPropertyChangeListener(PropertyChangeListener listener)
void removePropertyChangeListener(PropertyChangeListener listener)

The first method is the familiar method in the ActionListener interface: in fact, the Action interface extends the ActionListener interface. Therefore, you can use an Action object whenever an ActionListener object is expected.

The next two methods let you enable or disable the action and check whether the action is currently enabled. When an action is attached to a menu or toolbar and the action is disabled, then the option is grayed out.

The putValue and getValue methods let you store and retrieve arbitrary name/value pairs in the action object. A couple of important predefined strings, namely, Action.NAME and Action.SMALL_ICON, store action names and icons into an action object:

action.putValue(Action.NAME, "Blue");
action.putValue(Action.SMALL_ICON, new ImageIcon("blue-ball.gif"));

Table 8–3 shows all predefined action table names.

Table 8–3. Predefined Action Table Names

Name

Value

NAME

The name of the action; displayed on buttons and menu items.

SMALL_ICON

A place to store a small icon; for display in a button, menu item, or toolbar.

SHORT_DESCRIPTION

A short description of the icon; for display in a tooltip.

LONG_DESCRIPTION

A long description of the icon; for potential use in online help. No Swing component uses this value.

MNEMONIC_KEY

A mnemonic abbreviation; for display in menu items (see Chapter 9)

ACCELERATOR_KEY

A place to store an accelerator keystroke. No Swing component uses this value.

ACTION_COMMAND_KEY

Historically, used in the now obsolete registerKeyboardAction method.

DEFAULT

Potentially useful catch-all property. No Swing component uses this value.

If the action object is added to a menu or toolbar, then the name and icon are automatically retrieved and displayed in the menu item or toolbar button. The SHORT_DESCRIPTION value turns into a tooltip.

The final two methods of the Action interface allow other objects, in particular menus or toolbars that trigger the action, to be notified when the properties of the action object change. For example, if a menu is added as a property change listener of an action object and the action object is subsequently disabled, then the menu is called and can gray out the action name. Property change listeners are a general construct that is a part of the “JavaBeans” component model. You can find out more about beans and their properties in Volume 2.

Note that Action is an interface, not a class. Any class implementing this interface must implement the seven methods we just discussed. Fortunately, a friendly soul has provided a class AbstractAction that implements all methods except for actionPerformed. That class takes care of storing all name/value pairs and managing the property change listeners. You simply extend AbstractAction and supply an actionPerformed method.

Let’s build an action object that can execute color change commands. We store the name of the command, an icon, and the desired color. We store the color in the table of name/value pairs that the AbstractAction class provides. Here is the code for the ColorAction class. The constructor sets the name/value pairs, and the actionPerformed method carries out the color change action.

public class ColorAction extends AbstractAction
{
   public ColorAction(String name, Icon icon, Color c)
   {
      putValue(Action.NAME, name);
      putValue(Action.SMALL_ICON, icon);
      putValue("color", c);
      putValue(Action.SHORT_DESCRIPTION, "Set panel color to " + name.toLowerCase());
   }

   public void actionPerformed(ActionEvent event)
   {
      Color c = (Color) getValue("color");
      setBackground(c);
   }
}

Our test program creates three objects of this class, such as

Action blueAction = new ColorAction("Blue", new ImageIcon("blue-ball.gif"), Color.BLUE);

Next, let’s associate this action with a button. That is easy because we can use a JButton constructor that takes an Action object.

JButton blueButton = new JButton(blueAction);

That constructor reads the name and icon from the action, sets the short description as the tooltip, and sets the action as the listener. You can see the icons and a tooltip in Figure 8–9.

Buttons display the icons from the action objectsmenusadding action toactionsmenu, adding to

Figure 8–9. Buttons display the icons from the action objects

As we demonstrate in the next chapter, it is just as easy to add the same action to a menu.

Finally, we want to add the action objects to keystrokes so that the actions are carried out when the user types keyboard commands. Now we run into a technical complexity. Keystrokes are delivered to the component that has focus. Our sample application is made up of several components, namely, three buttons inside a panel. Therefore, at any time, any one of the three buttons may have focus. Each of the buttons would need to handle key events and listen to the CTRL+Y, CTRL+B, and CTRL+R keys.

This is a common problem, and the Swing designers came up with a convenient solution for solving it.

Note

Note

In fact, in JDK version 1.2, there were two different solutions for binding keys to actions: the registerKeyboardAction method of the JComponent class and the KeyMap concept for JTextComponent commands. As of JDK version 1.3, these two mechanisms are unified. This section describes the unified approach.

To associate actions with keystrokes, you first need to generate objects of the KeyStroke class. This is a convenience class that encapsulates the description of a key. To generate a KeyStroke object, you don’t call a constructor but instead use the static getKeyStroke method of the KeyStroke class. You specify the virtual key code and the flags (such as SHIFT and CONTROL key combinations):

KeyStroke ctrlBKey = KeyStroke.getKeyStroke(KeyEvent.VK_B, InputEvent.CTRL_MASK);

Alternatively, you can describe the keystroke as a string:

KeyStroke ctrlBKey = KeyStroke.getKeyStroke("ctrl B");

Every JComponent has three input maps, each mapping KeyStroke objects to associated actions. The three input maps correspond to three different conditions (see Table 8–4).

Table 8–4. Input Map Conditions

Flag

Invoke Action

WHEN_FOCUSED

When this component has keyboard focus

WHEN_ANCESTOR_OF_FOCUSED_COMPONENT

When this component contains the component that has keyboard focus

WHEN_IN_FOCUSED_WINDOW

When this component is contained in the same window as the component that has keyboard focus

Keystroke processing checks these maps in the following order:

  1. Check the WHEN_FOCUSED map of the component with input focus. If the keystroke exists, execute the corresponding action. If the action is enabled, stop processing.

  2. Starting from the component with input focus, check the WHEN_ANCESTOR_OF_FOCUSED_COMPONENT maps of its parent components. As soon as a map with the keystroke is found, execute the corresponding action. If the action is enabled, stop processing.

  3. Look at all visible and enabled components in the window with input focus that have this keystroke registered in a WHEN_IN_FOCUSED_WINDOW map. Give these components (in the order of their keystroke registration) a chance to execute the corresponding action. As soon as the first enabled action is executed, stop processing. This part of the process is somewhat fragile if a keystroke appears in more than one WHEN_IN_FOCUSED_WINDOW map.

You obtain an input map from the component with the getInputMap method, for example:

InputMap imap = panel.getInputMap(JComponent.WHEN_FOCUSED);

The WHEN_FOCUSED condition means that this map is consulted when the current component has the keyboard focus. In our situation, that isn’t the map we want. One of the buttons, not the panel, has the input focus. Either of the other two map choices works fine for inserting the color change keystrokes. We use WHEN_ANCESTOR_OF_FOCUSED_COMPONENT in our example program.

The InputMap doesn’t directly map KeyStroke objects to Action objects. Instead, it maps to arbitrary objects, and a second map, implemented by the ActionMap class, maps objects to actions. That makes it easier to share the same actions among keystrokes that come from different input maps.

Thus, each component has three input maps and one action map. To tie them together, you need to come up with names for the actions. Here is how you can tie a key to an action:

imap.put(KeyStroke.getKeyStroke("ctrl Y"), "panel.yellow");
ActionMap amap = panel.getActionMap();
amap.put("panel.yellow", yellowAction);

It is customary to use the string "none" for a do-nothing action. That makes it easy to deactivate a key:

imap.put(KeyStroke.getKeyStroke("ctrl C"), "none");

Caution

Caution

The JDK documentation suggests using the action name as the action’s key. We don’t think that is a good idea. The action name is displayed on buttons and menu items; thus, it can change at the whim of the UI designer and it may be translated into multiple languages. Such unstable strings are poor choices for lookup keys. Instead, we recommend that you come up with action names that are independent of the displayed names.

To summarize, here is what you do to carry out the same action in response to a button, a menu item, or a keystroke:

  1. Implement a class that extends the AbstractAction class. You may be able to use the same class for multiple related actions.

  2. Construct an object of the action class.

  3. Construct a button or menu item from the action object. The constructor will read the label text and icon from the action object.

  4. For actions that can be triggered by keystrokes, you have to carry out additional steps. First locate the top-level component of the window, such as a panel that contains all other components.

  5. Then get the WHEN_ANCESTOR_OF_FOCUSED_COMPONENT input map of the top-level component. Make a KeyStroke object for the desired keystroke. Make an action key object, such as a string that describes your action. Add the pair (keystroke, action key) into the input map.

  6. Finally, get the action map of the top-level component. Add the pair (action key, action object) into the map.

Example 8–5 shows the complete code of the program that maps both buttons and keystrokes to action objects. Try it out—clicking either the buttons or pressing CTRL+Y, CTRL+B, or CTRL+R changes the panel color.

Example 8–5. ActionTest.java

 1. import java.awt.*;
 2. import java.awt.event.*;
 3. import javax.swing.*;
 4.
 5. public class ActionTest
 6. {
 7.    public static void main(String[] args)
 8.    {
 9.       ActionFrame frame = new ActionFrame();
10.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
11.       frame.setVisible(true);
12.    }
13. }
14.
15. /**
16.    A frame with a panel that demonstrates color change actions.
17. */
18. class ActionFrame extends JFrame
19. {
20.    public ActionFrame()
21.    {
22.       setTitle("ActionTest");
23.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
24.
25.       // add panel to frame
26.
27.       ActionPanel panel = new ActionPanel();
28.       add(panel);
29.    }
30.
31.    public static final int DEFAULT_WIDTH = 300;
32.    public static final int DEFAULT_HEIGHT = 200;
33. }
34.
35. /**
36.    A panel with buttons and keyboard shortcuts to change
37.    the background color.
38. */
39. class ActionPanel extends JPanel
40. {
41.    public ActionPanel()
42.    {
43.       // define actions
44.
45.       Action yellowAction = new ColorAction("Yellow",
46.          new ImageIcon("yellow-ball.gif"),
47.          Color.YELLOW);
48.       Action blueAction = new ColorAction("Blue",
49.          new ImageIcon("blue-ball.gif"),
50.          Color.BLUE);
51.       Action redAction = new ColorAction("Red",
52.          new ImageIcon("red-ball.gif"),
53.          Color.RED);
54.
55.       // add buttons for these actions
56.
57.       add(new JButton(yellowAction));
58.       add(new JButton(blueAction));
59.       add(new JButton(redAction));
60.
61.       // associate the Y, B, and R keys with names
62.
63.       InputMap imap = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
64.
65.       imap.put(KeyStroke.getKeyStroke("ctrl Y"), "panel.yellow");
66.       imap.put(KeyStroke.getKeyStroke("ctrl B"), "panel.blue");
67.       imap.put(KeyStroke.getKeyStroke("ctrl R"), "panel.red");
68.
69.       // associate the names with actions
70.
71.       ActionMap amap = getActionMap();
72.       amap.put("panel.yellow", yellowAction);
73.       amap.put("panel.blue", blueAction);
74.       amap.put("panel.red", redAction);
75.    }
76.
77.    public class ColorAction extends AbstractAction
78.    {
79.       /**
80.          Constructs a color action.
81.          @param name the name to show on the button
82.          @param icon the icon to display on the button
83.          @param c the background color
84.       */
85.       public ColorAction(String name, Icon icon, Color c)
86.       {
87.          putValue(Action.NAME, name);
88.          putValue(Action.SMALL_ICON, icon);
89.          putValue(Action.SHORT_DESCRIPTION, "Set panel color to " + name.toLowerCase());
90.          putValue("color", c);
91.       }
92.
93.       public void actionPerformed(ActionEvent event)
94.       {
95.          Color c = (Color) getValue("color");
96.          setBackground(c);
97.       }
98.    }
99. }
ActionTest.java
javax.swing.Action 1.2
  • void setEnabled(boolean b)

    enables or disables this action.

  • boolean isEnabled()

    returns true if this action is enabled.

  • void putValue(String key, Object value)

    places a name/value pair inside the action object.

    Parameters:

    key

    The name of the feature to store with the action object. This can be any string, but several names have predefined meanings—see Table 8–3 on page 324.

     

    value

    The object associated with the name.

  • Object getValue(String key)

    returns the value of a stored name/value pair.

ActionTest.java
javax.swing.JMenu 1.2
  • JMenuItem add(Action a)

    adds a menu item to the menu that invokes the action a when selected; returns the added menu item.

ActionTest.java
javax.swing.KeyStroke 1.2
  • static KeyStroke getKeyStroke(char keyChar)

    creates a KeyStroke object that encapsulates a keystroke corresponding to a KEY_TYPED event.

  • static KeyStroke getKeyStroke(int keyCode, int modifiers)

  • static KeyStroke getKeyStroke(int keyCode, int modifiers, boolean onRelease)

    create a KeyStroke object that encapsulates a keystroke corresponding to a KEY_PRESSED or KEY_RELEASED event.

    Parameters:

    keyCode

    The virtual key code

     

    modifiers

    Any combination of InputEvent.SHIFT_MASK, InputEvent.CTRL_MASK, InputEvent.ALT_MASK, InputEvent.META_MASK

     

    onRelease

    true if the keystroke is to be recognized when the key is released

  • static KeyStroke getKeyStroke(String description)

    constructs a keystroke from a humanly readable description. The description is a sequence of whitespace-delimited strings in the following format:

    1. The strings shift control ctrl meta alt button1 button2 button3 are translated to the appropriate mask bits.

    2. The string typed must be followed by a one-character string, for example, "typed a".

    3. The string pressed or released indicates a key press or release. (Key press is the default.)

    4. Otherwise, the string, when prefixed with VK_, should correspond to a KeyEvent constant, for example, "INSERT" corresponds to KeyEvent.VK_INSERT.

    For example, "released ctrl Y" corresponds to: getKeyStroke(KeyEvent.VK_Y, Event.CTRL_MASK, true)

ActionTest.java
javax.swing.JComponent 1.2
  • ActionMap getActionMap() 1.3

    returns the action map that maps keystrokes to action keys.

  • InputMap getInputMap(int flag) 1.3

    gets the input map that maps action keys to action objects.

    Parameters:

    flag

    A condition on the keyboard focus to trigger the action, one of the values in Table 8–4 on page 326

Multicasting

In the preceding section, we had several event sources report to the same event listener. In this section, we do the opposite. All AWT event sources support a multicast model for listeners. This means that the same event can be sent to more than one listener object. Multicasting is useful if an event is potentially of interest to many parties. Simply add multiple listeners to an event source to give all registered listeners a chance to react to the events.

Caution

Caution

According to the JDK documentation, “The API makes no guarantees about the order in which the events are delivered to a set of registered listeners for a given event on a given source.” Therefore, don’t implement program logic that depends on the delivery order.

Here we show a simple application of multicasting. We will have a frame that can spawn multiple windows with the New button, and it can close all windows with the Close all button—see Figure 8–10.

All frames listen to the Close all commandframesClose All commandClose All command

Figure 8–10. All frames listen to the Close all command

The listener to the New button of the MulticastPanel is the newListener object constructed in the MulticastPanel constructor—it makes a new frame whenever the button is clicked.

But the Close all button of the MulticastPanel has multiple listeners. Each time the BlankFrame constructor executes, it adds another action listener to the Close all button. Each of those listeners is responsible for closing a single frame in its actionPerformed method. When the user clicks the Close all button, each of the listeners is activated and each of them closes its frame.

Furthermore, the actionPerformed method removes the listener from the Close all button because it is no longer needed once the frame is closed.

Example 8–6 shows the source code.

Example 8–6. MulticastTest.java

 1. import java.awt.*;
 2. import java.awt.event.*;
 3. import javax.swing.*;
 4.
 5. public class MulticastTest
 6. {
 7.    public static void main(String[] args)
 8.    {
 9.       MulticastFrame frame = new MulticastFrame();
10.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
11.       frame.setVisible(true);
12.    }
13. }
14.
15. /**
16.    A frame with buttons to make and close secondary frames
17. */
18. class MulticastFrame extends JFrame
19. {
20.    public MulticastFrame()
21.    {
22.       setTitle("MulticastTest");
23.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
24.
25.       // add panel to frame
26.
27.       MulticastPanel panel = new MulticastPanel();
28.       add(panel);
29.    }
30.
31.    public static final int DEFAULT_WIDTH = 300;
32.    public static final int DEFAULT_HEIGHT = 200;
33. }
34.
35. /**
36.    A panel with buttons to create and close sample frames.
37. */
38. class MulticastPanel extends JPanel
39. {
40.    public MulticastPanel()
41.    {
42.       // add "New" button
43.
44.       JButton newButton = new JButton("New");
45.       add(newButton);
46.       final JButton closeAllButton = new JButton("Close all");
47.       add(closeAllButton);
48.
49.       ActionListener newListener = new
50.          ActionListener()
51.          {
52.             public void actionPerformed(ActionEvent event)
53.             {
54.                BlankFrame frame = new BlankFrame(closeAllButton);
55.                frame.setVisible(true);
56.             }
57.          };
58.
59.       newButton.addActionListener(newListener);
60.    }
61. }
62.
63. /**
64.    A blank frame that can be closed by clicking a button.
65. */
66. class BlankFrame extends JFrame
67. {
68.    /**
69.       Constructs a blank frame
70.       @param closeButton the button to close this frame
71.    */
72.    public BlankFrame(final JButton closeButton)
73.    {
74.       counter++;
75.       setTitle("Frame " + counter);
76.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
77.       setLocation(SPACING * counter, SPACING * counter);
78.
79.       closeListener = new
80.          ActionListener()
81.          {
82.             public void actionPerformed(ActionEvent event)
83.             {
84.                closeButton.removeActionListener(closeListener);
85.                dispose();
86.             }
87.          };
88.       closeButton.addActionListener(closeListener);
89.    }
90.
91.    private ActionListener closeListener;
92.    public static final int DEFAULT_WIDTH = 200;
93.    public static final int DEFAULT_HEIGHT = 150;
94.    public static final int SPACING = 40;
95.    private static int counter = 0;
96. }

Implementing Event Sources

In the last section of this chapter, we show you how to implement a class that generates its own events and notifies interested listeners. This is occasionally necessary when you use advanced Swing components. It is also interesting to see what goes on behind the scenes when you add a listener to a component.

Our event source will be a PaintCountPanel that counts how often the paintComponent method was called. Every time the count is incremented, the PaintCountPanel notifies all listeners. In our sample program, we will attach just one listener that updates the frame title—see Figure 8–11.

Counting how often the panel is paintedpaintingcounting times panel is paintedpanelscounting times painted

Figure 8–11. Counting how often the panel is painted

Whenever you define an event source, you need three ingredients:

  • an event type. We could define our own event class, but we will simply use the existing PropertyChangeEvent class.

  • an event listener interface. Again, we could define our own interface, but we will use the existing PropertyChangeListener interface. That interface has a single method.

    public void propertyChange(PropertyChangeEvent event)
    
  • methods for adding and removing listeners. We will supply two methods in the PaintCountPanel class:

    public void addPropertyChangeListener(PropertyChangeListener listener)
    public void removePropertyChangeListener(PropertyChangeListener listener)
    

How do we make sure that events are sent to interested parties? This is the responsibility of the event source. It must construct an event object and pass it to the registered listeners whenever an event occurs.

Event management is a common task, and the Swing designers provide a convenience class, EventListenerList, to make it easy to implement the methods for adding and removing listeners and for firing events. The class takes care of the tricky details that can arise when multiple threads attempt to add, remove, or dispatch events at the same time.

Because some event sources accept listeners of multiple types, each listener in the event listener list is associated with a particular class. The add and remove methods are intended for the implementation of addXxxListener methods. For example,

public void addPropertyChangeListener(PropertyChangeListener listener)
{
   listenerList.add(PropertyChangeListener.class, listener);
}

public void removePropertyChangeListener(PropertyChangeListener listener)

{
   listenerList.remove(PropertyChangeListener.class, listener);
}

Note

Note

You may wonder why the EventListenerList doesn’t simply check which interface the listener object implements. But an object can implement multiple interfaces. For example, it is possible that listener happens to implement both the PropertyChangeListener and the ActionListener interface, but a programmer may choose only to add it as a PropertyChangeListener by calling the addPropertyChangeListener. The EventListenerList must respect that choice.

Whenever the paintComponent method is called, the PaintCountPanel class constructs a PropertyChangeEvent object, specifying the event source, the property name, and the old and new property values. It then calls the firePropertyChangeEvent helper method.

public void paintComponent(Graphics g)
{
   int oldPaintCount = paintCount;
   paintCount++;
   firePropertyChangeEvent(new PropertyChangeEvent(this,
      "paintCount", oldPaintCount, paintCount));
   super.paintComponent(g);
}

The firePropertyChangeEvent method locates all registered listeners and calls their propertyChange methods.

public void firePropertyChangeEvent(PropertyChangeEvent event)
{
   EventListener[] listeners = listenerList.getListeners(PropertyChangeListener.class);
   for (EventListener l : listeners)
      ((PropertyChangeListener) l).propertyChange(event);
}

Example 8–7 shows the source code of a sample program that listens to a PaintCountPanel. The frame constructor adds a property change listener to the panel that updates the frame title:

panel.addPropertyChangeListener(new
   PropertyChangeListener()
   {
      public void propertyChange(PropertyChangeEvent event)
      {
         setTitle("EventSourceTest - " + event.getNewValue());
      }
   });

This ends our discussion of event handling. In the next chapter, you will learn more about user interface components. Of course, to program user interfaces, you will put your knowledge of event handling to work by capturing the events that the user interface components generate.

Example 8–7. EventSourceTest.java

 1. import java.awt.*;
 2. import java.awt.event.*;
 3. import java.util.*;
 4. import javax.swing.*;
 5. import java.beans.*;
 6.
 7. public class EventSourceTest
 8. {
 9.    public static void main(String[] args)
10.    {
11.       EventSourceFrame frame = new EventSourceFrame();
12.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
13.       frame.setVisible(true);
14.    }
15. }
16.
17. /**
18.    A frame that contains a panel with drawings
19. */
20. class EventSourceFrame extends JFrame
21. {
22.    public EventSourceFrame()
23.    {
24.       setTitle("EventSourceTest");
25.       setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
26.
27.       // add panel to frame
28.
29.       final PaintCountPanel panel = new PaintCountPanel();
30.       add(panel);
31.
32.       panel.addPropertyChangeListener(new
33.          PropertyChangeListener()
34.          {
35.             public void propertyChange(PropertyChangeEvent event)
36.             {
37.                setTitle("EventSourceTest - " + event.getNewValue());
38.             }
39.          });
40.    }
41.
42.    public static final int DEFAULT_WIDTH = 400;
43.    public static final int DEFAULT_HEIGHT = 200;
44. }
45.
46. /**
47.    A panel that counts how often it is painted.
48. */
49. class PaintCountPanel extends JPanel
50. {
51.    public void paintComponent(Graphics g)
52.    {
53.       int oldPaintCount = paintCount;
54.       paintCount++;
55.       firePropertyChangeEvent(new PropertyChangeEvent(this,
56.          "paintCount", oldPaintCount, paintCount));
57.       super.paintComponent(g);
58.    }
59.
60.    /**
61.       Adds a change listener
62.       @param listener the listener to add
63.    */
64.    public void addPropertyChangeListener(PropertyChangeListener listener)
65.    {
66.       listenerList.add(PropertyChangeListener.class, listener);
67.    }
68.
69.    /**
70.       Removes a change listener
71.       @param listener the listener to remove
72.    */
73.    public void removePropertyChangeListener(PropertyChangeListener listener)
74.    {
75.       listenerList.remove(PropertyChangeListener.class, listener);
76.    }
77.
78.    public void firePropertyChangeEvent(PropertyChangeEvent event)
79.    {
80.       EventListener[] listeners = listenerList.getListeners(PropertyChangeListener.class);
81.       for (EventListener l : listeners)
82.          ((PropertyChangeListener) l).propertyChange(event);
83.    }
84.
85.    public int getPaintCount()
86.    {
87.       return paintCount;
88.    }
89.
90.    private int paintCount;
91. }
EventSourceTest.java
javax.swing.event.EventListenerList 1.2
  • void add(Class t, EventListener l)

    adds an event listener and its class to the list. The class is stored so that event firing methods can selectively call events. Typical usage is in an addXxxListener method:

    public void addXxxListener(XxxListener l){   listenerList.add(XxxListener.class, l);}

    Parameters:

    t

    The listener type

     

    l

    The listener

  • void remove(Class t, EventListener l)

    removes an event listener and its class from the list. Typical usage is in a removeXxxListener method:

    public void removeXxxListener(XxxListener l){   listenerList.remove(XxxListener.class, l);}

    Parameters:

    t

    The listener type

     

    l

    The listener

  • EventListener[] getListeners(Class t) 1.3

    returns an array of all the listeners of the given type. The array is guaranteed to be non-null.

  • Object[] getListenerList()

    returns an array whose elements with an even-numbered index are listener classes and whose elements with an odd-numbered index are listener objects. The array is guaranteed to be non-null.

EventSourceTest.java
java.beans.PropertyChangeEvent 1.1
  • PropertyChangeEvent(Object source, String name, Object oldValue, Object newValue)

    constructs a property change event.

    Parameters:

    source

    The event source, that is, the object that reports a property change

     

    name

    The name of the property

     

    oldValue

    The value of the property before the change

     

    newValue

    The value of the property after the change

EventSourceTest.java
java.beans.PropertyChangeListener 1.1
  • void propertyChange(PropertyChangeEvent event)

    called when a property value has changed.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset