Table 13.1 and Table 13.2 summarize the commonly used Swing
events, which Swing
components fire them, and the methods of the listener interfaces that
receive them. The events and listeners are divided between the
java.awt.event
and
javax.swing.event
packages.
In Swing, a component’s model and view are distinct. Strictly
speaking, components don’t fire events; models do. When you
press a JButton
, for example, it’s actually
the button’s data model that fires an
ActionEvent
, not the button itself. But
JButton
has a convenience method for registering
ActionListener
s; this methods passes its argument
through to register the listener with the button model. In many cases
(as with JButton
s), you don’t have to deal
with the data model separately from the view, so we can speak loosely
of the component itself firing the events.
Table 13-1. Swing Component and Container Events
Event |
Fired by |
Listener Interface |
Handler Method |
---|---|---|---|
|
All components |
|
|
| |||
| |||
| |||
|
All components |
|
|
| |||
|
All components |
|
|
| |||
| |||
|
All components |
|
|
| |||
| |||
| |||
| |||
|
| ||
| |||
|
All containers |
|
|
|
Table 13-2. Component-Specific Swing Events
Event |
Fired by |
Listener Interface |
Handler Method |
---|---|---|---|
|
|
|
|
| |||
| |||
| |||
| |||
| |||
| |||
| |||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| |||
| |||
| |||
| |||
| |||
| |||
|
|
|
|
| |||
| |||
| |||
|
|
|
|
| |||
| |||
|
|
|
|
| |||
|
|
|
|
| |||
| |||
|
|
|
|
| |||
| |||
|
|
|
|
| |||
| |||
|
|
|
|
| |||
| |||
| |||
|
|
|
|
| |||
| |||
| |||
| |||
|
|
|
|
|
|
|
|
| |||
|
|
|
|
| |||
| |||
| |||
|
|
|
|
| |||
|
|
|
|
|
|
|
|
|
| ||
|
| ||
| |||
| |||
| |||
[a] The TableColumnModel class breaks with convention in the names of the methods that add listeners. They are addColumnModelListener( )and removeColumnModelListener( ). |
It’s not usually ideal to have your application components implement a listener interface and receive events directly. Sometimes it’s not even possible. Being an event receiver forces you to modify or subclass your objects to implement the appropriate event listener interfaces and add the code necessary to handle the events. Since we are talking about Swing events here, a more subtle issue is that you are, of necessity, building GUI logic into parts of your application that shouldn’t have to know anything about the GUI. Let’s look at an example.
In Figure 13.4, we have drawn the plans for our
Vegomatic food processor. Here, we have made our
Vegomatic
object implement the
ActionListener
interface, so that it can receive
events directly from the three JButton
components:
Chop
, Puree
, and
Frappe
. The problem is that our
Vegomatic
object now has to know more than how to
mangle food. It also has to be aware that it will be driven by three
controls—specifically, buttons that send action
commands—and be aware of which methods in itself it should
invoke for those commands. Our boxes labeling the GUI and application
code overlap in an unwholesome way. If the marketing people should
later want to add or remove buttons or perhaps just change the names,
we have to be careful. We may have to modify the logic in our
Vegomatic
object. All is not well.
An alternative is to place an adapter class between our event source and receiver. An adapter is a simple object whose sole purpose is to map an incoming event to an outgoing method.
Figure 13.5 shows a better design that uses three adapter classes, one for each button. The implementation of the first adapter might look like:
class VegomaticAdapter1 implements ActionListener { Vegomatic vegomatic; VegomaticAdapter1 ( Vegotmatic vegomatic ) { this.vegomatic = vegomatic; } public void actionPerformed( ActionEvent e ) { vegomatic.chopFood( ); } }
So somewhere in the code where we build our GUI, we could register our listener like this:
Vegomatic theVegomatic = ...; Button chopButton = ...; // make the hookup chopButton.addActionListener( new VegomaticAdapter1(theVegomatic) );
Instead of registering itself (this
) as the
Button’s listener, the adapter registers the
Vegomatic
object
(theVegomatic
). In this way, the adapter acts as
an intermediary, hooking up an event source (the button) with an
event receiver (the virtual chopper).
We have completely separated the messiness of our GUI from the application code. However, we have added three new classes to our application, none of which does very much. Is that good? That depends on your vantage point.
Under different circumstances, our buttons may have been able to
share a common adapter class that was simply instantiated with
different parameters. Various trade-offs can be made between size,
efficiency, and elegance of code. Adapter classes will often be
generated automatically by development tools. The way we have named
our adapter classes VegomaticAdapter1
,
VegomaticAdapter2
, and
VegomaticAdapter3
hints at this. More often, when
hand-coding, you’ll use an inner class. At the other extreme,
we can forsake Java’s strong typing and use the reflection API
to create a completely dynamic hookup between an event source and its listener.
Many listener interfaces contain more than one event-handler method. Unfortunately, this means that to register yourself as interested in any one of those events, you must implement the whole listener interface. And to accomplish this you might find yourself typing in dummy “stubbed-out” methods, simply to complete the interface. There is really nothing wrong with this, but it is a bit tedious. To save you some trouble, AWT and Swing provide some helper classes that implement these dummy methods for you. For each of the most common listener interfaces containing more than one method, there is an adapter class containing the stubbed methods. You can use the adapter class as a base class for your own adapters. So when you need a class to patch together your event source and listener, you can simply subclass the adapter and override only the methods you want.
For example, the
MouseAdapter
class implements the
MouseListener
interface and provides the following
implementation:
public void mouseClicked(MouseEvent e) {}; public void mousePressed(MouseEvent e) {}; public void mouseReleased(MouseEvent e) {}; public void mouseEntered(MouseEvent e) {}; public void mouseExited(MouseEvent e) {};
This isn’t a tremendous time saver; it’s simply a bit of
sugar. The primary advantage comes into play when we use the
MouseAdapter
as the base for our own adapter in an
anonymous inner class. For example, suppose we want to catch a
mousePressed( )
event in some component and blow
up a building. We can use the following to make the hookup:
someComponent.addMouseListener( new MouseAdapter( ) { public void MousePressed(MouseEvent e) { building.blowUp( ); } } );
We’ve taken artistic liberties with the formatting, but it’s very readable. Moreover, we’ve avoided creating stub methods for the four unused event-handler methods. Writing adapters is common enough that it’s nice to avoid typing those extra few lines and perhaps stave off the onset of carpal tunnel syndrome for a few more hours. Remember that any time you use an inner class, the compiler is generating a class for you, so the messiness you’ve saved in your source still exists in the output classes.
Although Java is still a
youngster, it has a bit of a legacy. Versions of Java before 1.1 used
a different style of event delivery. Back in the old days (a couple
of years ago), event types were limited, and an event was delivered
only to the Component
that generated it or one of
its parent containers. The old style component event-handler methods (now
deprecated) returned a boolean
value declaring
whether or not they had handled the event:
boolean handleEvent( Event e ) { ... }
If the method returns false
, the event is
automatically redelivered to the component’s container to give
it a chance. If the container does not handle it, it is passed on to
its parent container, and so on. In this way, events were propagated
up the containment hierarchy until they were either consumed or
passed over to the component peer, just as current
InputEvent
s are ultimately interpreted using the
peer if no registered event listeners have consumed them.
Note that this older style of event processing applies only to AWT components. The newer Swing components handle only events with the new model.
Although this style of event delivery was convenient for some simple
applications, it is not very flexible. Events could be handled only
by components, which meant that you always had to subclass a
Component
or Container
type to
handle events. This was a degenerate use of inheritance (i.e., bad
design) that led to the creation of lots of unnecessary classes.
We could, alternatively, receive the events for many embedded components in a single parent container, but that would often lead to very convoluted logic in the container’s event-handling methods. It is also very costly to run every single AWT event through a gauntlet of (often empty) tests as it traverses its way up the tree of containers. This is why Swing now provides the more dynamic and general event source/listener model that we have described in this chapter. The old-style events and event-handler methods are, however, still with us.
Java is not allowed to simply change and break an established API. Instead, older ways of doing things are simply deprecated in favor of the new ones. This means that code using the old-style event handler methods will still work; you may see old-style code around for a long time. The problem with relying on old-style event delivery, however, is that the old and new ways of doing things cannot be mixed.
By default, Java is obligated to perform the old
behavior—offering events to the component and each of its
parent containers. However, Java turns off the old-style delivery
whenever it thinks that we have elected to use the new style. Java
determines whether a Component
should receive
old-style or new-style events based on whether any event listeners
are registered, or whether new style events have been explicitly
enabled. When an event listener is registered with a
Component
, new-style events are implicitly turned
on (a flag is set). Additionally, a mask is set telling the component the
types of events it should process. The mask allows components to be
more selective about which events they process.
When new-style events are enabled,
all events are first routed to the dispatch-Event( )
method of the Component
class. The
dispatchEvent( )
method examines the
component’s event mask and decides whether the event should be
processed or ignored. Events that have been enabled are sent to the
processEvent( )
method, which looks at the
event’s type and delegates it to a helper processing method
named for its type. The helper processing method finally dispatches
the event to the set of registered listeners for its type.
This process closely parallels the way in which old-style events are
processed and the way in which events are first directed to a single
handleEvent( )
method that dispatches them to more specific handler methods in the
Component
class. The differences are that
new-style events are not delivered unless someone is listening for
them, and the listener registration mechanism means that we
don’t have to subclass the component in order to override its
event-handler methods and insert our own code.
Still, if you are subclassing a Component
or you
really want to process all events in a single method, you should be
aware that it is possible to emulate the old-style event handling and
override your component’s event-processing methods. Call the
Component
’s
enableEvents( )
method with the appropriate mask
value to turn on processing for the given type of event. You can then
override the corresponding method and insert your code. The mask
values, listed in Table 13.3, are found in the
java.awt.AWTEvent
class.
For example:
public void init( ) { ... enableEvent( AWTEvent.KEY_EVENT_MASK ): } public void processKeyEvent(KeyEvent e) { if ( e.getID( ) == KeyEvent.KEY_TYPED ) { // do work } super.processKeyEvent(e); }
If you do this, it is very important that you remember to make a call
to super.
process...Event( )
in
order to allow normal event delegation to continue. Of course, by
emulating old-style event handling, we’re giving up the virtues
of the new style; this code is a lot less flexible than the code we
could write with the new event model. As we’ve seen, the user
interface is hopelessly tangled with the actual work your program
does. A compromise solution would be to have your subclass declare
that it implements the appropriate listener interface and register
itself, as we have done in the simpler
examples in this
book:
class MyApplet implements KeyListener ... public void init( ) { addKeyListener( this ): ... } public void keyTyped(KeyEvent e) { // do work }
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.