Chapter 8. Event Handling

BASICS OF EVENT HANDLING

ACTIONS

MOUSE EVENTS

THE AWT EVENT HIERARCHY

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 user interface components and input devices. We also show you how to work with actions, a more structured approach for processing action events.

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 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.

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.

Figure 8–1 shows the relationship between the event handling classes and interfaces.

Figure 8–1. Relationship between event sources and listeners

Image

Here is an example for specifying a listener:

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.

To implement the ActionListener interface, the listener class must have a method called actionPerformed that receives an ActionEvent object as a parameter.

Image

Whenever the user clicks the button, the JButton object creates an ActionEvent object and calls listener.actionPerformed(event), passing that event object. An event source such as a button can have multiple listeners. In that case, the button calls the actionPerformed methods of all listeners whenever the user clicks the button.

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

Figure 8–2. Event notification

Image

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 show a panel populated with three buttons. Three listener objects 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.jpg"));

Call the add method to add the buttons to a panel:

Image

Figure 8–3 shows the result.

Figure 8–3. A panel filled with buttons

Image

Next, we need to add code that listens 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)


Image 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, we want the background color of the panel to change to a particular color. We store the desired color in our listener class.

Image

Image

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

Image

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 buttonPanel 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 ButtonFrame 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 ButtonFrame class:

Image

Look closely at the actionPerformed method. The ColorAction class doesn’t have a buttonPanel field. But the outer ButtonFrame class does.

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.

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

Listing 8–1. ButtonTest.java

Image

Image

Image

Image

Image javax.swing.JButton 1.2

JButton(String label)

JButton(Icon icon)

JButton(String label, Icon icon)

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

Image java.awt.Container 1.0

Component add(Component c)

adds the component c to this container.

Image javax.swing.ImageIcon 1.2

ImageIcon(String filename)

constructs an icon whose image is stored in a file.

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.

Here is a good example of how anonymous inner classes can actually simplify your code. If you look at the code of Listing 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:

Image

Then we simply call

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:

Image

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.


Image 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:

Image

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.



Image Note

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 make the container of the event sources implement the ActionListener interface. Then, the container sets itself as the listener, like this:

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

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

Image

As you can see, this gets quite messy, and we do not recommend it.


Image java.util.EventObject 1.1

Object getSource()

returns a reference to the object where the event occurred.

Image 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.

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

constructs 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.

Creating Listeners Containing a Single Method Call

Java SE 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:

Image

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(
   EventHandler.create(ActionListener.class, frame, "loadData"));

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

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

is equivalent to

Image

The property names source and text turn into method calls getSource and getText.

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 Macintosh look-and-feel packages are only shipped with the Windows and Macintosh versions of the Java runtime environment.


Image Tip

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:

Image

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

Listing 8–2 is a complete program that demonstrates how to switch the look and feel (see Figure 8–4). The program is similar to Listing 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.

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

Figure 8–4. Switching the look and feel

Image

Listing 8–2. PlafTest.java

Image

Image

Image

Image 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, using the given class name (such as “javax.swing.plaf.metal.MetalLookAndFeel”).

Image 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.

Adapter Classes

Not all events are as simple to handle as button clicks. In a non-toy program, you will want to monitor when the user tries to close the main frame because you don’t want your users to lose unsaved work. When the user closes the frame, you want to put up a dialog and 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 frame’s 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:

Image


Image Note

To find out whether a window has been maximized, install a WindowStateListener. See the API notes on page 341 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:

Image

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:

Image

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–5). Six of those methods do nothing; the windowClosing method calls System.exit(0), terminating the application.

Figure 8–5. A window listener

Image


Image 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.

Image

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.

Image 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.

Image java.awt.event.WindowStateListener 1.4

void windowStateChanged(WindowEvent event)

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

Image java.awt.event.WindowEvent 1.1

int getNewState() 1.4

int getOldState() 1.4

returns 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

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:

Image

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.jpg"));

Table 8–1 shows all predefined action table names.

Table 8–1. Predefined Action Table Names

Image

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 II.

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.

Image

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

Action blueAction = new ColorAction("Blue", new ImageIcon("blue-ball.jpg"), 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–6.

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

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

Image

Finally, we want to add the action objects to keystrokes so that the actions are carried out when the user types keyboard commands. 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.

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

To understand the next step, you need to know the concept of keyboard focus. A user interface can have many buttons, menus, scrollbars, and other components. When you hit a key, it is sent to the component that has focus. That component is usually (but not always) visually distinguished. For example, in the Java Look and Feel, a button with focus has a thin rectangular border around the button text. You can use the TAB key to move the focus between components. When you press the SPACE key, the button with focus is clicked. Other keys carry out different actions; for example, the arrow keys can move a scrollbar.

However, in our case, we do not want to send the keystroke to the component that has focus. Otherwise, each of the buttons would need to know how to handle 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. 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–2).

Table 8–2. Input Map Conditions

Image

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. Here is an 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");


Image 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.

Listing 8–3 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.

Listing 8–3. ActionTest.java

Image

Image

Image

Image

Image javax.swing.Action 1.2

boolean isEnabled()

void setEnabled(boolean b)

gets or sets the enabled property of this action.

void putValue(String key, Object value)

places a name/value pair inside the action object.

Image

Object getValue(String key)

returns the value of a stored name/value pair.

Image javax.swing.KeyStroke 1.2

static KeyStroke getKeyStroke(String description)

constructs a keystroke from a humanly readable description (a sequence of whitespace-delimited strings). The description starts with zero or more modifiers shift control ctrl meta alt altGraph and ends with either the string typed, followed by a one-character string (for example, "typed a"), or an optional event specifier (pressed—the default—or released), followed by a key code. The key code, when prefixed with VK_, should correspond to a KeyEvent constant; for example, "INSERT" corresponds to KeyEvent.VK_INSERT.

Image javax.swing.JComponent 1.2

ActionMap getActionMap() 1.3

returns the map that associates action map keys (which can be arbitrary objects) with Action objects.

InputMap getInputMap(int flag) 1.3

gets the input map that maps key strokes to action map keys.

Image

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. 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–7).

Figure 8–7. A mouse test program

Image

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.

You use bit masks to test which modifiers have been set. 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 Java SE 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.

Image

As the mouse moves over a window, the window receives a steady stream of mouse movement events. 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.

Our test application traps mouse motion 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–3 lists the constants to use with this method along with what the cursors look like under Windows.

Table 8–3. Sample Cursor Shapes

Image

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

Image


Image 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.jpg");
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, a screen reader program can read the cursor shape description 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.

Image


Image 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. Listing 8–4 is the program listing.

Listing 8–4. MouseTest.java

Image

Image

Image

Image

Image

Image

Image

Image java.awt.event.MouseEvent 1.1

int getX()

int getY()

Point getPoint()

returns 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.

int getClickCount()

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

Image 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.

Image java.awt.Toolkit 1.0

public Cursor createCustomCursor(Image image, Point hotSpot, String name) 1.2

creates a new custom cursor object.

Image

Image java.awt.Component 1.0

public void setCursor(Cursor cursor) 1.1

sets the cursor image to the specified cursor.

The AWT Event Hierarchy

Having given you a taste of how event handling works, we finish this chapter with an overview of the AWT event handling architecture.

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–8 shows the inheritance diagram of the AWT events.

Figure 8–8. Inheritance diagram of AWT event classes

Image

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.

Semantic and Low-Level Events

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)

WindowEvent (the window state changed)

The following interfaces listen to these events:

Image

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:

Image

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

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

Table 8–4. Event Handling Summary

Image

Image

Image

This concludes our discussion of AWT event handling. 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.

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

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