The Life Cycle of a Java Event in JDK 1.1
Last updated 1998 July 17 by Roedy
Green
Introduction
In JDK 1.1, events are handled quite differently from JDK 1.0.2. This essay
will follow an single event from creation to cremation to
give you an idea of how it all fits together. For details, and practical
coding concerns, I suggest you read some other essays on events such as
Richard Baldwin's
instructional essays or Jan
Newmarch's essay. I have written a similar essay
for the older JDK 1.0.2 events.
Overview
Events are:
- A way one object can call a method in another, but with a delay.
Instead of doing the work right away, the work is postponed by putting
into a queue, so that the caller can get on with more important work.
- A flexible way of deciding at run time precisely which objects should
have their methods called when something interesting happens.
- A way for letting the callee decide when it wants to be called rather
than leaving that decision totally up to the caller.
Instead of events percolating up to parents as they did in JDK 1.0.2, any
object or component can register itself as a listener, interested
in hearing about a type of events originating in some other component.
When the event arises, the source component processes the event
by dispatching it to each of the registered listeners. Dispatching
is synchronous, i.e. the listener handler routines are called directly
while the calling dispatcher waits for the handler to complete. According
to the specification, the listeners
may be informed in any order, but the usual implementation is a queue,
with listeners informed in the same order they were added.
If you want to be absolutely sure of a fixed order of
handling listeners, make your own chain and pass the event between them.
Listeners pretty much only hear events they are interested in. They are
not responsible for passing the event on to anyone else.
The new event model gives you three places to hook in your code to process
events.
-
via listeners. This is by far the most common way and the easiest to set
up. The listener can be any object, e.g. the enclosing frame, the component
itself, a non-gui code object, a "mom" object to mother a set of components.
A given listener can service several sources, and a given source can notify
several target listeners. Listener methods have names like actionPerformed,
windowClosed, mouseMoved. Listeners are often implemented with Adapter
classes so you don't have to write dummy methods for events you are not
interested in. Adapter classes have names like: java.awt.event.FocusAdapter,
KeyAdapter, and WindowAdapter. Note, there is no such thing as ActionAdapter
since it has only one method. Often you use inner classes or anonymous
classes to handle fielding events by extending an adapter class. Be very
careful in extending adapter classes to get your method names and signatures
exactly right. Otherwise you won't override the corresponding dummy method,
and your code won't ever be executed, leaving you wondering where all the
events went.
-
via processEvent which receives all raw events for the component. It does
not see events coming in via listeners. If you override it, be sure to
call super.processEvent to ensure all the finer event handlers, like processKeyEvent,
ProcessActionEvent etc. also get called and the listeners alerted. If you
attempt to restrict the events coming to processEvent with enableEvents(mask),
if there are listeners registered for events not in the mask, those event
classes will arrive anyway.
-
via processXXXX(SomeEventClass e), e.g. processActionEvent, processItemEvent.
Every raw event for the component of that class comes through here. It
does not see events coming in via listeners. Be sure to call super.processXXXX
to ensure the listeners are alerted.
A component might quite reasonably register itself as a listener for events
arising in itself or for events arising from the associated native GUI.
There is another deprecated technique to process events arising in itself.
A component could override its processEvent routine, do some action, then
call super.processEvent (which would inform all the listeners), then do
some other action.
The Cast Of Players
Methods for registering a target as a listener of events
arising in some source include: source.addActionListener(target), addAdjustmentListener,
addComponentListener, addContainerListener, addFocusListener, addItemListener,
addKeyListener, addMouseListener, addMouseMotionListener, addPropertyChangeListener,
addTextListener, addVetoableChangeListener, addWindowListener ... Normally
you would invoke these methods but not override them.
The AWT informs the list of registered listeners that an event has occurred
by calling the generic source.processEvent which in turn calls the more
specific event handler: source.processActionEvent, processAdjustmentEvent,
processComponentEvent, processContainerEvent, processItemEvent, processKeyEvent,
processMouseEvent, processMouseMotionEvent, firePropertyChange, processTextEvent,
fireVetoableChange, processWindowEvent ... These routines would in turn
use the AWTEventMulticaster to inform all the listeners and invoke their
actionPerformed, keyPressed, windowOpened etc. methods in turn. These processXXX
routines typically work by calling the relevant dispatching routines on
the listener targets. It is unlikely you will ever deal with these methods
directly.
Your Listener object must implement one or more interfaces such
as: ActionListener, ComponentListener, ContainerListener, FocusListener,
KeyListener, ItemListener, MouseListener, MouseMotionListener, TextListener,
WindowListener ...
The easiest way to implement those interfaces is to extend one of the
related adapter classes. The adapter classes provide dummy methods
to deal with the subcategories of events that are of no interest to you.
ComponentAdapter, ContainerAdapter, FocusAdapter, KeyListener, ItemAdapter,
MouseAdapter, MouseMotionAdapter, TextAdapter, WindowAdapter ...
The event is dispatched to each target listener by invoking its listening
method. These have names like: target.actionPerformed, adjustmentValueChanged,
componentHidden, componentMoved, componentResized, componentShown, componentAdded,
componentRemoved, focusGained, focusLost, itemStateChanged, keyPressed,
keyReleased, keyTyped, mouseClicked, mouseEntered, mouseExited, mousePressed,
mouseReleased, textValueChanged, windowActivated, windowClosed, windowClosing,
windowDeactivated, windowDecionified, WindowIconified, windowOpened ...
You need to write custom versions of these routines.
If there is a listener for an event type registered, then events from
the GUI for that event type will be delivered to that component's processEvent.
The component can control which additional types are delivered to processessEvent
by calling enableEvents with a mask. This way events that no one
is interested in are not even delivered to processEvent. It is unlikely
you will be directly involved with enableEvents.
A key point to understand is that a component has two kinds of acceptor
routines for events.
-
The generic processEvent routine primarily accepts events indirectly coming
from the native GUI. Application programmers do not normally use these
methods.
-
The listener methods, e.g. actionPerformed, accept events from other components.
This is the main tool for application programmers to deal with events.
Component writers can also use the listener interface by registering a
custom component as a listener to events arising in itself.
The Life Cycle Of An Event
Where do Events Come From Daddy?
You synthesize an AWTEvent and post to the SystemEventQueue for later delivery.
-
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(Event e)
In this Alice in Wonderland world, you set the source field of the
event to the component or peer where you want the event delivered.
Later, when the source delivers the event to the various listeners, the
term source for this field will make more sense. A separate thread
will service the queue asynchronously and deliver the event to the target's
ProcessEvent method. When the event percolates to the head of the queue,
the dispatcher will call event.getSource().processEvent(event);
Most events arise in the native GUI. They can enter the Java system
in one of two ways:
-
Notification of interesting happenings in the GUI arrive as a single stream
of messages mentioning native GUI components. This is the traditional native
GUI event loop handled by hidden code, that calls getMessage() in a loop.
The AWT uses a hash table to convert references to native GUI components
into the corresponding Java components and peers, and composes corresponding
Java events and enqueues them in the SystemEventQueue.
-
Java peer components generate events based on information they glean from
the native GUI. These too are enqueued in the SystemEventQueue.
Lightweight components generate events and post them directly to themselves,
without going through the SystemEventQueue.
Where is the Event Loop?
People who cut their teeth in the Windows or Mac native low level GUI API
are used to writing application-specific code to handle dispatching using
an event processing loop. The AWT handles this all automatically and invisibly
in EventDispatchThread.run. It reads the the stream of native
GUI event messages, and translates them, creating Java events, which it
puts into the SystemEventQueue. The thread that processes events, when
it finishing processing one event, goes and picks up another event off
the head of the event queue to dispatch. This is very thread that executes
most of your application code.
The loop that processes events does not even start until your main
program has finished executing!! This event loop code is not visible to
the application programmer.
Creation
Consider what happens behind the scenes when the user clicks an OK button
on the screen. The underlying native GUI notices the mouse click.
It could then work one of two ways:
-
The native GUI indirectly notifies the Java Button peer object which creates
a new event using the ActionEvent constructor.
-
The GUI creates a native format message that a button has been clicked.
Hidden code in the AWT reads this stream of messages and translates this
one into a new event using the ActionEvent: constructor. It has a hashtable
to convert references to native peer components to the corresponding Java
components.
ActionEvent e = new java.awt.event.ActionEvent(myButton,
ActionEvent.ACTION_PERFORMED,
"OK",
0);
ActionEvent(Object source,
int id,
String command,
int modifiers);
-
source
-
A Java component closely associated with this event, namely myButton. At
first this field acts as the destination for the event. Later it informs
listeners where the event came from.
-
id
-
the type of event, in this case ActionEvent.ACTION_PERFORMED.
-
command
-
the command string for this action event. In this case it would be the
string "OK".
-
modifiers
-
the modifiers held down during this click, e.g. CTRL_MASK if the Ctrl key
is held down
Enqueuing
The newly created event is then enqueued on the system event queue using:
EventQueue q = Toolkit.getDefaultTookit().getSystemEventQueue();
Now it can enqueue the event for delivery to the source (myButton) with
the system's postEvent method. We don't use any methods of myButton
just yet.
q.postEvent(e);
The event hibernates in the queue waiting its turn to be dispatched.
postEvent will return immediately. It won't wait until the new event has been
delivered.
Dispatching
There is a single system thread that grabs the next item off the queue
and dispatches it. You can see the code for it in EventDispatchThread.run
When all the processing for that event is complete, it gets the next event.
No other event processing, not even a mouse move, can happen until the
previous event has been processed. I repeat, only one event is processed at at time.
Having said that, this single-thread behaviour is not part of the
specification. There are some JVMs where you have several threads processing
events simultaneously.
It looks at the source field in the event object, and delivers the event
to that source object.
First it checks the eventMask to be sure the myButton component is willing
to accept that flavour of event. The button could conceivably have used
disableEvents to filter out events such as ours. It also checks if there
are listeners for our type of event. If there are, even if our event type
is blocked, our event will still be delivered.
It delivers the event to the button component by calling myButton's
generic processEvent event handler:
myButton.processEvent(e);
or more precisely:
e.getSource().processEvent(e)
This dispatching mechanism is efficient because it does not require any
sort of table lookup or nested switch statements.
Classification
processEvent could in theory do some special processing on the event, but
usually all it does is classify the event and call a more specialised event
handler such as: processActionEvent. (If you override processEvent,
make sure you call super.processEvent to ensure the standard event processing
also occurs.) Had we been processing a mouse event, we would have called
processMouseEvent instead.
Notifying Listeners
A long time ago, before this event was born, parties interested in this
type of event registered their interest my using myButton.addActionListener(myListener).
It is important to remember that addXXXXListener methods are implemented
by the sources, not the listeners. In theory, the addXXXXListener methods
could be invoked by the source or the listener, but usually they are invoked
by a third party, usually an enclosing frame.
Raw events arrive via processEvent. They are classsified and redirected
to more specialised event processors like processActionEvent.
processActionEvent can further classify the incoming events and
can do whatever special processing it wants. Then it notifies any listeners,
one after the other. (If you override processActionEvent, make sure you
call super.processActionEvent to ensure the listeners are notified.)
Each listener effectively gets a fresh cloned copy of the original event.
Unfortunately, this wastes cpu cycles and creates a ton of run time garbage.
The intent is to make events effectively read-only and to render harmless
any malicious component that stored a reference to an event. Once an event
enters Component.processKeyEvent, it isn't replicated anymore so presumably
some of its fields could be modified and used for communication. Ideally
event objects would be read-only and recyclable.
There is no guarantee that the listeners will be notified in any particular
order, however they will be notified one at a time, never in parallel.
To notify each registered Listener, the speaker's processActionEvent
calls each listener's actionPerformed(e) method by chasing the AWTEventMultiCaster
Listener chain.
AWTEventMulticaster contains generic code that most components use for
implementing addXXXListener and the notification of listeners. Component
has various add/remove Listener methods that use AWTEventMulticaster that
all Components inherit.
If we had been processing a mouse motion, we would have called the listener's
mouseMoved method instead. processActionEvent methods are designed to be
called by processEvent; methods like actionPerformed and mouseMoved are
part of Listener interfaces. Most application coding uses only Listeners.
Only if you were inventing a new sort of component would you get involved
with processEvent and processActionEvent.
What Are Listeners?
A listener is any object that
-
implements one of the listener interfaces such as ActionListener. E.g.
an ActionListener must implement an actionPerformed method to be called
when an Action event occurs.
-
Has hooked itself (or been hooked by a third party) onto one or more event
generators with a method such as addActionListener.
A listener might be a component, an ordinary object, a special Mom object
to mother a set of components or an anonymous object created from an anonymous
class to simply provide a hook to some other routine.
A listener might be created by extending one of the adapter shortcut
classes. The adapter classes give you some dummy classes to start with
in defining a listener where you only want to handle a few types of event.
A component may even register itself as a listener for events
originating it itself.. This is the preferred method for application programmers
since there is less possibility of mucking up standard event processing.
A listener can field events from many different sources. Similarly a
source may notify many different listeners. Alternatively you could have
a separate listener object for every source, or anything in between.
What does the Listener do?
What does the listener do when it's actionPerformed (or other mouseMoved
etc...) method is called? It may classify the incoming event based on source,
id or class (via instanceof). Then it would perform some action, do a calculation,
change the look of the screen, delete a file... If it wants, it can use
the consume() method to mark the event consumed to discourage
further processing on it.
It had better be quick! The entire
system is at a standstill, unable to process any more events until your
listener completes its action. If the action is going to take more than
a few milliseconds you should either:
-
Spin off a separate thread to do the work and immediately return from the
event handler. With Swing you must be careful since Swing components are not
thread safe. You must only accesss them with the original thread dispatching thread.
-
Do a little of the work, and enqueue a synthetic event to remind yourself
to do a little more of the work later.
-
Create a second thread to dispatch events, popping them off the SystemEventQueue.
In a similar vein, if it is very important that some action not start until
after the physical paint is finished, the simplest way to arrange that
is to have paint kick it off, perhaps by examining some flag to see if
kickoff is necessary.
How Does consume() Work?
I have not found a precise definition of what is supposed to happen to
a consumed event. By experiment, I discovered it seems to stop further
processing by the base classes of the current component. It does not seem
to stop notification of others listeners. I found for example that in TextField,
consuming a keystroke event would prevent it being displayed. However,
this does not work with the KL group JCTextField or the Swing JTextField.
There you must override processKeyEvent.
After all the listeners have been called, the event is checked to see
if it has been consumed. If so, then it is not passed onto the next stage.
For e.g. KeyEvents for a TextArea, this means that it is not passed back
to the peer, and so does not reach the underlying window/widget, and so
does not appear.
consume() in KeyListeners is effectively broken in Swing 0.7 since it
is ignored. It looks like the event is drawn in JTextArea before
being given to the listeners.
For TextFields, you can change the value of the keystroke in the event
in a KeyListener before passing it on, e.g. convert it to lower or upper
case. However under Motif, converting to lower case is broken, and the
entire technique is broken for Swing. You would have to trap processKeyEvent
instead.
How Do You Compose a Listener?
Java 1.1 added inner classes and anonymous classes to make it easier to
hook up Listener code. Here is code that could appear in the middle
of a frame initialisation method that:
-
defines a new anonymous class that extends theWindowAdapter listener.
-
defines a method in that class to deal with window closing events.
-
allocates an object of that class.
-
hooks that object up as a listener to the current frame component.
-
throws away the reference to the class. (The object won't die since it
is on the list of listeners.)
class MyFrame extends Frame {
// constructor
MyFrame ()
{
setSize(200,300);
this.addWindowListener( new java.awt.event.WindowAdapter()
{ // anonymous WindowAdapter class
public void windowClosing (java.awt.event.WindowEvent w)
{
MyFrame.this.setVisible(false);
MyFrame.this.dispose();
} // end windowClosing
} // end anonymous class
); // end addWindowListener invocation
setVisible(true);
} // end constructor
} // end class MyFrame
Cremation
The various listeners eventually complete their work and return to actionPerformed.
The return stack unwinds: actionPerformed returns. processActionEvent returns.
processEvent returns. We are now back at system the code that dispatches
events from the queue. Our event is forgotten and the next event takes
the stage. With no more references to our old event, garbage collection
soon recycles the RAM the event occupied.
Generalising this Example
Instead of created an ActionEvent with id ActionEvent.ACTION_PERFORMED,
that was dispatched to processEvent, then passed on to processActionEvent,
then passed on to an ActionListener (that registered its interest via addActionListener)
via its actionPerformed method, there are many other possibilities you
can generate from the following table:
Event Class |
id |
Specific
Event
Handler |
Listener
Interface |
Listener
Notification
Method |
Listener
registration |
Adapter shortcut |
ActionEvent |
ACTION_PERFORMED |
processActionEvent |
ActionListener |
actionPerformed |
addActionListener |
AdjustmentEvent |
ADJUSTMENT_VALUE_CHANGED |
processAdjustmentEvent |
AdjustmentListener
ScrollPane |
adjustmentValueChanged |
addAdjustmentListener |
ComponentEvent |
COMPONENT_HIDDEN
COMPONENT_MOVED
COMPONENT_RESIZED
COMPONENT_SHOWN |
processComponentEvent |
ComponentListener |
componentHidden
componentMoved
componentResized
componentShown |
addComponentListener |
ComponentAdapter |
ContainerEvent |
COMPONENT_ADDED
COMPONENT_REMOVED |
processContainerEvent |
ContainerListener |
componentAdded
componentRemoved |
addContainerListener |
ContainerAdapter |
FocusEvent |
FOCUS_GAINED
FOCUS_LOST |
processFocusEvent |
FocusListener |
focusGained
focusLost |
addFocusListener |
FocusAdapter |
KeyEvent |
KEY_PRESSED (e.g.shift, F1)
KEY_RELEASED
KEY_TYPED (eg.a A) |
processKeyEvent |
KeyListener |
keyPressed
keyReleased
keyTyped |
addKeyListener |
KeyAdapter |
ItemEvent |
DESELECTED
ITEM_STATE_CHANGED
SELECTED |
processItemEvent |
ItemListener |
ItemStateChanged |
addItemListener |
MouseEvent |
MOUSE_CLICKED
MOUSE_ENTERED
MOUSE_EXITED
MOUSE_PRESSED
MOUSE_RELEASED |
processMouseEvent |
MouseListener |
mouseClicked
mouseEntered
mouseExited
mousePressed
mouseReleased |
addMouseListener |
MouseAdapter |
MouseEvent |
MOUSE_DRAGGED
MOUSE_MOVED |
processMouseMotionEvent |
MouseMotionListener |
mouseDragged
mouseMoved |
addMouseMotionListener |
MouseMotionAdapter |
PaintEvent |
PAINT
UPDATE |
paint
update |
TextEvent |
TEXT_VALUE_CHANGED |
processTextEvent |
TextListener |
textValueChanged |
addTextListener |
WindowEvent |
WINDOW_ACTIVATED
WINDOW_CLOSED
WINDOW_CLOSING
WINDOW_DEACTIVATED
WINDOW_DEICONIFIED
WINDOW_ICONIFIED
WINDOW_OPENED |
processWindowEvent |
WindowListener |
windowActivated
windowClosed
windowClosing
windowDeactivated
windowDecionified
WindowIconified
windowOpened |
addWindowListener |
WindowAdapter |
For a complete list of the
various listener interfaces, and methods see C:\jdk1.1.5\docs\api\Package-java.awt.event.html.
You want to study the whole java.awt.event package.
Censoring Keystrokes
Let us say you wanted to write your own version of TextField-like component
that disallowed certain keystrokes, or that processed certain keystrokes
specially. You may have tried the easy Listener/consume() method and discovered
it did not work for your base component. Here is bigger hammer to use:
protected void processKeyEvent (KeyEvent e)
{
switch (e.getID())
{
case KeyEvent.KEY_PRESSED:
switch (e.getKeyChar())
{
case KeyEvent.CHAR_UNDEFINED:
nonUnicodeKeyPressed(e);
break;
case KeyEvent.VK_ENTER:
case KeyEvent.VK_TAB:
case KeyEvent.VK_BACK_SPACE:
case KeyEvent.VK_DELETE:
unicodeControlKeyPressed(e);
break
default:
unicodeKeyPressed(e);
break;
} // end inner switch getKeyChar
break;
case KeyEvent.KEY_RELEASED:
case KeyEvent.KEY_TYPED:
default:
break;
} // end outer switch getID()
if (!e.isConsumed()) super.processKeyEvent(e);
} // end processKeyEvent
If you want the underlying base class not to see the event, you consume
it, and that suppresses the call to super.processKeyEvent.
Something to baffle you. Hitting the Enter key under NT generates two keystrokes a
\r and a \n. On Other platforms you get just a \n. Best just to look for VK_ENTER.
Activating a button
Hooking up some code to be executed when a button is hit is very similar
to the earlier WindowClosing example. There is no
such thing as the ActionAdapter class,
so we use the name of the interface instead.
myButton.addActionListener( new java.awt.event.ActionListener()
{ // anonymous class that implements the ActionListener interface
public void actionPerformed (java.awt.event.ActionEvent e)
{
System.out.println("Somebody hit the button.");
} // end actionPerformed
} // end anonymous class
); // end addActionListener invocation
Missing Events
The newsgroups are full of plaintive messages from newbies claiming the
AWT is broken because their Listeners are not receiving any events. Here
are some things to check:
-
When you override a Listener method of an Adatper, did you spell the name
of the Listener method correctly with exactly the right signature? If you
did not, the compiler will give no error message and all the events will
continue to be delivered to the Adapter's dummy Listener method.
-
Are any events arriving? Perhaps the events arriving are slightly
different from those expected. Put in some debug code to dump out whatever
is arriving to any of the Listener methods. e.g.
/**
* debugging tool to track keystrokes
*/
protected void dumpKeyEvent (KeyEvent e)
{
System.out.print(" ID=" + e.getID());
System.out.print(" KeyCode=" + e.getKeyCode());
System.out.print(" KeyChar=\"" + e.getKeyChar() + "\" "
+ (int)e.getKeyChar());
System.out.println();
}
Are you sure your component is supposed to be generating the events you
expect? Often components don't pass on low-level mouseMoved or
mouseClicked events, but instead pass on higher level ItemStateChanged
or actionPerformed events.
Event vs. AWTEvent
In JDK 1.0.2 there was basically only one kind of event, called the
java.awt.Event.
In JDK 1.1 there is a base event called a java.awt.event.AWTEvent.
All the other types of event such as java.awt.event.actionEvent
and java.awt.event.windowEvent are derived from it.
It is a bit confusing since the event package for JDK 1.1 is called
java.awt.event instead of java.awt.AWTEvent as you
might expect.
Changes from JDK 1.0
In addition to the complete revamping of how it works,
some of the details have changed too. The names of the various events no
longer live in the Event class. They live in the various subclasses
of AWTEvent e.g. WindowEvent.WINDOW_CLOSED. Some events
have been renamed. WINDOW_DESTROY is now WINDOW_CLOSED.
You can no longer access the source and id fields of
an event directly. You must call getSource and getID.
handleEvent is now called ProcessEvent.
Summary
The key point to understand is that a component has two kinds of acceptor
routines for events. The generic processEvent routine primarily accepts
events coming indirectly from the native GUI which it then fobs off on
the more specific processXXXXEvent methods. The listener methods, e.g.
actionPerformed, accept events from other components. The listener methods
are primarily for application programmers and those who customise existing
components; the processXXXXEvent methods are primarily for the writers
of radically new components.
Repaint
You may have seen mention of PaintEvent.PAINT and PaintEvent.UPDATE events.
These are used to control how components are repainted. The repaint mechanism
partly uses the SystemEventQueue and partly the queue inside the native
GUI. See Repaint in the Java Glossary for details on how it works.
Credits
This article would not have been possible without the patient assistance
of Jan Newmarch who researched the material. Jan has a a chapter in a SAMS
Book Tricks Of The Java Gurus coming out soon. Richard Baldwin and
Peter Mehlitz helped explain the JDK 1.1 event processing. Steve Odendahl
pointed out the mismatched signature problem in Adapters. Tuyen Tran
pointed out the problems with Swing and threading.