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 EventObject
s 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.
ActionEvent
s 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.
An event is delivered by passing it
as an argument to the receiving object’s event-handler method.
ActionEvent
s, 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
ActionEvent
s 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 AWTEvent
s 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 MouseEvent
s 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 MouseEvent
s. 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 ofjava.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.
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
ActionEvent
s. 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 ActionEvent
s 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 ActionEvent
s. 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 ItemEvent
s 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.
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).
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.
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.
MouseEvent
s, which
track the state of the mouse, and KeyEvent
s, 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.
InputEvent
s
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.