Event Handling in Java

Any operating environment that supports GUIs constantly monitors events such as keystrokes or mouse clicks. These events are then reported to the programs that are running. Each program then decides what, if anything, to do in response to these events.

1. Basic Event Handling Concepts

In the Java AWT, event sources (such as buttons or scrollbars) have methods that allow you to register event listeners—objects that carry out the desired response to the event.

When an event listener is notified about an event, information about the event is encapsulated in an event object. In Java, all event objects ultimately derive from the class java.utit.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:

  • An event listener is an instance of a class that implements 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 then uses the information in the event object to determine their reaction to the event.

Figure 10.12 shows the relationship between the event handling classes and interfaces.

Here is an example for specifying a listener:

ActionListener listener = …;

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

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. 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 10.13 shows the interaction between the event source, event listener, and event object.

2. Example: Handling a Button Click

As a way of getting comfortable with the event delegation model, let’s work through all the 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 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.

To create a button, specify a label string, an icon, or both in the button constructor. Here are two examples:

var yettowButton = new JButton(“Yeltow”);

var blueButton = new JButton(new ImageIcon(“blue-baU.gif”));

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

var yellowButton = new JButton(“YeUow”);

var blueButton = new JButton(“Blue”);

var redButton = new JButton(“Red”);

 

buttonPanel.add(yeUowButton);

buttonPanel.add(blueButton);

buttonPanel.add(redButton);

Figure 10.14 shows the result.

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:

pubtic void actionPerformed(ActionEvent event)

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.

class CotorAction implements ActionListener

{

private Color backgroundColor;

public ColorAction(Color c)

{

backgroundColor = c;

}

public void actionPerformed(ActionEvent event)

{

// set panel background color

}

}

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

var yellowAction = new ColorAction(Color.YELLOW);

var blueAction = new ColorAction(Color.BLUE);

var 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”, 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.

Listing 10.5 contains the complete frame class. Whenever you click one of the buttons, the appropriate action listener changes the background color of the panel.

3. Specifying Listeners Concisely

In the preceding section, we defined a class for the event listener and con­structed three objects of that class. It is not all that common to have multiple instances of a listener class. Most commonly, each listener carries out a sep­arate action. In that case, there is no need to make a separate class. Simply use a lambda expression:

exitButton.addActionListener(event -> System.exit(O));

Now consider the case in which we have multiple related actions, such as the color buttons of the preceding section. In such a case, implement a helper method:

public void makeButton(String name, Color backgroundCotor)

{

var button = new JButton(name);

buttonPanel.add(button);

button.addActionListener(event ->

buttonPanel.setBackground(backgroundColor));

}

Note that the lambda expression refers to the parameter variable backgroundColor.

Then we simply call

makeButton(“yellow”, Color.YELLOW);

makeButton(“blue”, Color.BLUE);

makeButton(“red”, Color.RED);

Here, we construct three listener objects, one for each color, without explic­itly defining a class. Each time the helper method is called, it makes an in­stance of a class that implements the ActionListener interface. Its actionPerformed action references the backGroundColor value that is, in fact, stored with the listener object. However, all this happens without you having to explicitly define listener classes, instance variables, or constructors that set them.

4. Adapter Classes

Not all events are as simple to handle as button clicks. Suppose you want to monitor when the user tries to close the main frame in order to put up a dialog and exit the program only when the user agrees.

When the user tries to close a 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(tistener);

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

}

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. However, typing code for six methods that don’t do any­thing is the kind of tedious busywork that nobody likes. To simplify this task, each of the AWT listener interfaces that have more than one method comes with a companion adapter class that implements all the methods in the inter­face but does nothing with them. For example, the WindowAdapter class has seven do-nothing methods. You extend the adapter class to specify the desired re­actions 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.)

Here is how we can define a window listener that overrides the windowClosing method:

class Terminator extends WindowAdapter

{

public void windowClosing(WindowEvent e)

{

if (user agrees)

System.exit(0);

}

}

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

var listener = new Terminator!);

frame.addWindowListener(tistener);

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

The color change command will now be 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 com­mands 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 setEnabted(bootean b)

boolean isEnabted()

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, 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 10.1 shows all predefined action table names.

If the action object is added to a menu or toolbar, 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 dis­abled, the menu is called and can gray out the action name.

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 manag­ing 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 CotorAction class. The constructor sets the name/value pairs, and the actionPerformed method carries out the color change action.

public class CotorAction extends AbstractAction

{

public ColorAction(String name, Icon icon, Color c)

{

putValue(Action.NAME, name);

putVatue(Action.SMALL_ICON, icon);

putValue(“color”, c);

putValue(Action.SHORT_DESCRIPTION, “Set panel color to ” + name.toLowerCase());

}

pubtic void actionPerformed(ActionEvent event)

{

Color c = (Color) getValue(“color”);

buttonPanel.setBackground(c);

}

}

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

var blueAction = new ColorAction(“Blue”, new ImageIcon(“blue-baU.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.

var blueButton = new JButton(blueAction);

That constructor reads the name and icon from the action, sets the short de­scription as the tooltip, and sets the action as the listener. You can see the icons and a tooltip in Figure 10.15.

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 an action is carried out when the user types a keyboard command. To associate actions with keystrokes, you first need to generate objects of the Keystroke class. This convenience class encapsulates the description of a key. To generate a Keystroke object, don’t call a constructor but instead use the static getKeyStroke method of the Keystroke class.

Keystroke ctrtBKey = KeyStroke.getKeyStroke(“ctrt B”);

To understand the next step, you need to understand 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 bar, 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 conve­nient solution. Every JComponent has three input maps, each mapping Keystroke objects to associated actions. The three input maps correspond to three different conditions (see Table 10.2).

Keystroke processing checks these maps in the following order:

  1. Check the WHEN_FOCUSED map of the component with input focus. If the key­stroke exists and its corresponding action is enabled, execute the action and 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 and a corresponding enabled action is found, execute the action and 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.

To obtain an input map from the component, use the getInputMap method. Here is an example:

InputMap imap = panet.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(“ctrt Y”), “panet.yettow”);

ActionMap amap = panet.getActionMap();

amap.put(“panet.yettow”, yettowAction);

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(“ctrt C”), “none”);

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.

6. 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 the mouse move, click, and drag events.

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

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.

In our sample program, we supply both a mousePressed and a mouseClicked methods. When you click on 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 mouseCticked method because we need the click count.

public void mousePressed(MouseEvent event)

{

current = find(event.getPoint());

if (current == nutt) // not inside a square

add(event.getPoint());

}

pubtic void mouseCticked(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. 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 10.3 lists the constants to use with this method along with what the cursors look like under Windows.

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

}

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

}

}

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 mouseCticked method, which is part of the MouseListener interface. Many applications are only interested in mouse clicks and not in mouse moves; with the mouse move events occurring 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 MouseMotionHandter implements the MouseMotionListener and defines both methods of that interface. Listing 10.6 is the program listing.

7. The AWT Event Hierarchy

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

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 program­mer. 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; instead, they override the paintComponent method to control re­painting. The AWT also generates a number of events that are needed only by systems programmers, to provide input systems for ideographic languages, automated testing robots, and so on.

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”; 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)
  • MouseWheetEvent (the mouse wheel was rotated)
  • FocusEvent (a component got focus or lost focus)
  • WindowEvent (the window state changed)

Table 10.4 shows the most important AWT listener interfaces, events, and event sources.

Source: Horstmann Cay S. (2019), Core Java. Volume I – Fundamentals, Pearson; 11th edition.

Leave a Reply

Your email address will not be published. Required fields are marked *