Events

We’ve spent a lot of time discussing the different kinds of objects in Swing—components, containers, and special containers like frames and windows. Now it’s time to discuss interobject communication in detail.

Swing objects communicate by sending events. The way we talk about “firing” events and “handling” them makes it sound as if they are part of some special Java language feature. But they aren’t. An event is simply an ordinary Java object that is delivered to its receiver by invoking an ordinary Java method. Everything else, however interesting, is purely convention. The entire Java event mechanism is really just a set of conventions for the kinds of descriptive objects that should be delivered; these conventions prescribe when, how, and to whom events should be delivered.

Events are sent from a single source object to one or more listeners (or receivers). A listener implements prescribed event-handling methods that enable it to receive a type of event. It then registers itself with a source of that kind of event. Sometimes an adapter object may be interposed between the event source and the listener, but in any case, registration of a listener is always established before any events are delivered.

An event object is an instance of a subclass of java.util.EventObject; it holds information about something that’s happened to its source. The EventObject class itself serves mainly to identify event objects; the only information it contains is a reference to the event source (the object that sent the event). Components do not normally send or receive EventObjects as such; they work with subclasses that provide more specific information. AWTEvent is a subclass of EventObject that is used within AWT; further subclasses of AWTEvent provide information about specific event types. Swing has events of its own that descend directly from EventObject. For the most part, you’ll just be working with specific event subclasses, which are used in the same ways in AWT and Swing.

ActionEvents correspond to a decisive “action” that a user has taken with the component—like pressing a button or pressing Enter. An ActionEvent thus carries the name of an action to be performed (the action command) by the program. MouseEvent s are generated when a user operates the mouse within a component’s area. They describe the state of the mouse, and therefore carry information like the x and y coordinates and the state of your mouse buttons at the time the MouseEvent was created.

ActionEvent operates at a higher semantic level than MouseEvent: an ActionEvent lets us know that a component has performed its job, while a MouseEvent simply confers a lot of information about the mouse at a given time. You could figure out that somebody clicked on a JButton by examining mouse events, but it is simpler to work with action events. The precise meaning of an event, however, can depend on the context in which it is received.

Event Receivers and Listener Interfaces

An event is delivered by passing it as an argument to the receiving object’s event-handler method. ActionEvents, for example, are always delivered to a method called actionPerformed( ) in the receiver:

public void actionPerformed( ActionEvent e ) { 
    ... 
}

For each type of event, there is a corresponding listener interface that prescribes the method(s) it must provide to receive those events. In this case, any object that receives ActionEvents must implement the ActionListener interface:

public interface ActionListener extends java.util.EventListener { 
    public void actionPerformed( ActionEvent e ); 
}

All listener interfaces are subinterfaces of java.util.EventListener , which is an empty interface. It exists only to help the compiler identify listener interfaces.

Listener interfaces are required for a number of reasons. First, they help to identify objects that are capable of receiving a given type of event. This way we can give the event-handler methods friendly, descriptive names and still make it easy for documentation, tools, and humans to recognize them in a class. Next, listener interfaces are useful because several methods can be specified for an event receiver. For example, the FocusListener interface contains two methods:

abstract void focusGained( FocusEvent e ); 
abstract void focusLost( FocusEvent e );

Athough these methods both take a FocusEvent as an argument, they correspond to different reasons for firing the event; in this case, whether the FocusEvent means that focus was received or lost. You could figure out what happened by inspecting the event; all AWTEvents contain a constant specifying the event’s subtype. By requiring two methods, the FocusListener interface saves you the effort: if focusGained( ) is called, you know the event type was FOCUS_GAINED.

Similarly, the MouseListener interface defines five methods for receiving mouse events (and MouseMotionListener defines two more), each of which gives you some additional information about why the event occurred. In general, the listener interfaces group sets of related event-handler methods; the method called in any given situation provides a context for the information in the event object.

There can be more than one listener interface for dealing with a particular kind of event. For example, the MouseListener interface describes methods for receiving MouseEvents when the mouse enters or exits an area or a mouse button is pressed or released. MouseMotionListener is an entirely separate interface that describes methods to get mouse events when the mouse is moved (no buttons pressed) or dragged (buttons pressed). By separating mouse events into these two categories, Java lets you be a little more selective about the circumstances under which you want to receive MouseEvents. You can register as a listener for mouse events without receiving mouse motion events; since mouse motion events are extremely common, you don’t want to handle them if you don’t need to.

Two simple patterns govern the naming of Swing event listener interfaces and handler methods:

  • Event-handler methods are public methods that return type void and take a single event object (a subclass of java.util.EventObject) as an argument.[42]

  • Listener interfaces are subclasses of java.util.EventListener that are named with the suffix “Listener”—for example, MouseListener and ActionListener.

These may seem pretty obvious, but they are important because they are our first hint of a design pattern governing how to build components that work with events.

Event Sources

The previous section described the machinery that an event receiver uses to listen for events. In this section, we’ll describe how a receiver tells an event source to send it events, as they occur.

To receive events, an eligible listener must register itself with an event source. It does this by calling an “add listener” method in the event source and passing a reference to itself. (Thus, this scheme implements a callback facility.) For example, the Swing JButton class is a source of ActionEvents. Here’s how a TheReceiver object might register to receive these events:

// source of ActionEvents 
JButton theButton = new JButton("Belly"); 
   
// receiver of ActionEvents 
class TheReceiver implements ActionListener { 

   setupReceiver( ) { 
      ... 
      theButton.addActionListener( this ); 
   } 

   public void actionPerformed( ActionEvent e ) { 
      // Belly Button pushed... 
   }

The receiver makes a call to addActionListener( ) to become eligible to receive ActionEvents from the button when they occur. It passes the reference this to register itself as an ActionListener.

To manage its listeners, an ActionEvent source (like the JButton) always implements two methods:

// ActionEvent source 
public void addActionListener(ActionListener listener) { 
    ... 
} 
public void removeActionListener(ActionListener listener) { 
    ... 
}

The removeActionListener( ) method removes the listener from the list so that it will not receive future events of that kind. Swing components supply implementations of both methods; normally, you won’t need to implement them yourself.

Now, you may be expecting an EventSource interface listing these two methods, but there isn’t one. There are no event source interfaces in the current conventions. If you are analyzing a class and trying to determine what events it generates, you have to look for the add and remove methods. For example, the presence of the addActionListener( ) and removeActionListener( ) methods define the object as a source of ActionEvents. If you happen to be a human being, you can simply look at the documentation; but if the documentation isn’t available, or if you’re writing a program that needs to analyze a class (a process called “reflection”), you can look for this design pattern :

A source of FooEvent events for the FooListener interface must implement a pair of add/remove methods:

addFooListener (FooListener listener )
removeFooListener (FooListener listener )

If an event source can support only one event listener (unicast delivery), the add listener method can throw the checked exception java.util.TooManyListenersException .

So what do all the naming patterns up to this point accomplish? Well, for one thing, they make it possible for automated tools and integrated development environments to divine what are sources of particular events. Tools that work with Java Beans will use the Java reflection and introspection APIs to search for these kinds of design patterns and identify the events that can be fired by a component.

It also means that event hookups are strongly typed, just like the rest of Java. So it’s not easy to accidentally hook up the wrong kind of components; for example, you can’t register to receive ItemEvents from a JButton, because a button doesn’t have an addItemListener( ) method. Java knows at compile time what types of events can be delivered to whom.

Event Delivery

Swing and AWT events are multicast; every event is associated with a single source but can be delivered to any number of receivers. When an event is fired, it is delivered individually to each listener on the list (Figure 13.3).

Event delivery

Figure 13-3. Event delivery

There are no guarantees about the order in which events will be delivered. Neither are there any guarantees about what happens if you register yourself more than once with an event source; you may or may not get the event more than once. Similarly, you should assume that every listener receives the same event data. Events are immutable; they can’t be changed by their listeners.

To be complete, we could say that event delivery is synchronous with respect to the event source, but that follows from the fact that the event delivery is really just the invocation of a normal Java method. The source of the event calls the handler method of each listener. However, listeners shouldn’t assume that all of the events will be sent in the same thread. An event source could decide to send out events to all of the listeners in parallel, each in its own thread.

Event Types

All of the events used by Swing GUI components are subclasses of java.util.EventObject. You can use or subclass any of the EventObject types for use in your own components. We’ll describe the important event types here.

The events and listeners that are used by Swing fall into two packages: java.awt.event and javax.swing.event. As we’ve discussed, the structure of components has changed significantly between AWT and Swing. The event mechanism, however, is fundamentally the same, so the events and listeners in java.awt.event are still used by the new Swing components. In addition, Swing has added its own event types and listeners in the javax.swing.event package.

java.awt.event.ComponentEvent is the base class for events that can be fired by any component. This includes events that provide notification when a component changes its dimensions or visibility, as well as the other event types for mouse operation and key presses. ContainerEvent s are fired by containers when components are added or removed.

The java.awt.event.InputEvent Class

MouseEvents, which track the state of the mouse, and KeyEvents, which are fired when the user uses the keyboard, are kinds of java.awt.event.InputEvent. When the user presses a key or moves the mouse within a component’s area, the events are generated with that component as the source.

Input events and some other GUI events are placed on a special event queue that is managed by Swing. This gives Swing control over how the events are delivered. First, under some circumstances, a sequence of the same type of event may be compressed into a single event. This is done to make some event types more efficient—in particular, mouse events and some special internal events used to control repainting. Perhaps more important to us, input events are delivered with extra information that lets listeners decide if the component itself should act on the event.

Mouse and Key Modifiers on InputEvents

InputEvents come with a set of flags for special modifiers. These let you detect whether the Shift or Alt key was held down during a mouse button or key press, and detect which mouse button was pressed. The following are the flag values contained in java.awt.event.InputEvent:

SHIFT_MASK
CTRL_MASK
META_MASK
ALT_MASK
BUTTON1_MASK
BUTTON2_MASK
BUTTON3_MASK

To check for one or more flags, evaluate the boolean AND of the complete set of modifiers and the flag or flags you’re interested in. The complete set of modifiers involved in the event is returned by the InputEvent’s getModifiers( ) method:

public void mousePressed (MouseEvent e) { 
    int mods = e.getModifiers( ); 
    if ((mods & InputEvent.SHIFT_MASK) != 0) {
        // shifted Mouse Button press
    }
}

The three BUTTON flags can be used to detect which mouse button was pressed on a two- or three-button mouse. If you use these, you run the risk that your program won’t work on platforms without multibutton mice. Currently, BUTTON2_MASK is equivalent to ALT_MASK, and BUTTON3_MASK is equivalent to META_MASK. This means that pushing the second mouse button is equivalent to pressing the first (or only) button with the Alt key depressed, and the third button is equivalent to the first with the Meta key depressed. However, if you really want to guarantee portability, you should limit yourself to a single button, possibly in combination with keyboard modifiers, rather than relying on the button masks.



[42] This rule is not complete. JavaBeans allows event-handler methods to take additional arguments when absolutely necessary and also to throw checked exceptions.

Get Learning Java now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.