An AWT event has a life cycle something like the Pacific
salmon. The salmon is born in a stream, migrates to the
sea, then, if it isn't killed in the ocean, returns to the
stream where it was born and dies.
The AWT event starts in the native GUI, percolates up
through the peer, button, panel, window in the Java
application, and returns to the peer and back to the
native GUI.
Largely with the help of Jan Newmarch, it is coming ever
clearer how events percolate through the AWT.
Here is my fourth crack at describing how it all works. I
figured the best way to describe this is to hitch a ride
on an event and watch how it percolates from birth to
death on an assembly line.
Please correct any errors. Gradually we are getting
there.
WHERE IS THE EVENT LOOP?
************************
People who cut their teeth in the Windows or Mac native
low level API are used to writing application-specific
code to handle dispatching and event loops. The AWT
handles this all automatically and invisibly.
The loop that processes events does not even start until
your main program has returned!! This code is not visible
to the application programmer.
HOW A BUTTON PRESS WORKS
************************
For each button on the screen there are three objects:
- the application program's Button object.
- AWT's ButtonPeer object for interfacing to the native GUI.
- possibly the Native GUI's totally mysterious internal
button object.
To kick this all off, the user clicks the mouse on a
button.
The native GUI looks in the peers and/or its private
tables to narrow down which app, and within that which
panel, and within that which button was pressed and
generates a native GUI event which is handed over to the
corresponding ButtonPeer object in the AWT.
The narrowing logic all happens totally behind the AWT's
back. It is the native GUI's problem to figure out which
component in a container was clicked by analysing x-y
co-ordinates and comparing them with bounding rectangles.
The native GUI may change the look of the button at this
point, e.g. to make it look pressed. The peer object and
native GUI will get a second crack at processing the event
later, but it will likely do all of its processing now.
Sometimes the native GUI handles dispatching -- finding
the corresponding peer object. Sometimes the AWT peer
logic has to do it. Application programmers need not
concern themselves with either narrowing or dispatching.
None of the accessible AWT routines have those
responsibilities.
From the Java programmer's point of view an event
magically appears at the bottom of the hierarchy directly
at the corresponding ButtonPeer object.
The AWT then constructs a Java-style corresponding event
object to announce that a button has been clicked.
WHAT DOES AN EVENT LOOK LIKE?
*****************************
An event is just an object with instance variables.
The event object has 10 public fields. The most important
are:
Event.id = ACTION_EVENT, type of event
event.target = reference to the application programmer's
Java-style button object.
event.arg = string label on the button
event.x = x of mouse
in a component-relative co-ordinate system
i.e. Precisely where inside the button did he
click
event.y = y of mouse
WHAT KINDS OF EVENT ARE THERE?
******************************
Events are classified by the event.id field which can have
the following values:
ACTION_EVENT - user clicked a button
KEY_PRESS - user pressed a key.
KEY_RELEASE - user let go of a key.
KEY_ACTION - user pressed a function key or
control key such as Home or PgUp.
KEY_ACTION_RELEASE - use let go of a function key.
GOT_FOCUS - keystrokes will now be directed here
LOST_FOCUS - keystrokes will be directed elsewhere
MOUSE_ENTER - mouse entered the component
MOUSE_EXIT - mouse left the component
MOUSE_DOWN - user pressed the mouse button
MOUSE_UP - user let go of the mouse button
MOUSE_MOVE - mouse moved
MOUSE_DRAG - user dragged the mouse,
(move with button down)
LIST_SELECT - user clicked on one of the items
in a list
LIST_DESELECT - user clicked off one of the items
in a list
SCROLL_LINE_UP - user requested a scroll up
SCROLL_LINE_DOWN - user requested a scroll down
SCROLL_PAGE_UP - user requested a scroll up
SCROLL_PAGE_DOWN - user requested a scroll down
SCROLL_ABSOLUTE - user requested scroll to a particular point.
WINDOW_DESTROY - window is being destroyed
WINDOW_ICONIFY - window is being shrunk down to an icon
WINDOW_DEICONIFY - window is being blown back up from an icon
WINDOW_MOVED - window moved
Not all these types of event percolate through each
component. Some never even make it out of the native GUI
for standard components.
HOW EVENTS PERCOLATE
********************
The AWT then calls the postEvent method for the awt button
object, passing the event as a parameter. The default
code for event percolation (new model) looks like this:
public void postEvent(Event e) {
if (handleEvent(e))
return;
Component parent = this.parent;
if (parent != null) {
e.translate(x, y);
if (parent.postEvent(e)) {
return true;
}
}
if (peer != null) {
return peer.handleEvent(e);
}
As you can see, postEvent IMMEDIATELY calls handleEvent
for the button, and waits for the event to percolate all
the way up through the event handlers of the parents of
this component. postEvent does NOT put the event in a
queue for later handling. All the processing for an event
happens in one sweep, before any other event is processed.
handleEvent then classifies the event and hands it off to
more specific event handlers like mouseDown, and action.
If the button's handleEvent returns "true" it means
kill the event. Returning true means the event is totally
and completely handled and nothing further should be done
with it anywhere. If the event handler returns "false" it
means there is still more to be done with this event. The
event handler may or may not have modified the event
object or done some work behalf of the button-pressing
quite independently of whether it returns true or false.
Normally it will return false. If it returns true, the event
will never return to the native GUI, which may result in its
effect being totally suppressed.
postEvent then looks at the return code from the button's
handleEvent. If it is true, it just returns, effectively
killing the event. If the button's handleEvent returns
false, postEvent finds the button's parent (not
superclass!) i.e. its containing panel. It transforms the
x,y in the event object into the co-ordinate system that
the parent panel uses -- i.e. where in the entire panel
the mouse was clicked. It then calls postEvent of the
button's parent -- i.e. the panel containing the button --
handing it the same event object.
The event percolates up the tree, until either somebody
handles it and kills it or it bubbles off the top and is
handled back to the ButtonPeer object i.e. back to the
native GUI.
In code modeled on the Java In A Nutshell old model style
of event handlers, typically the default handleEvent for
the generic button would do nothing, just return false.
The button's PostEvent would then pass the event onto the
enclosing panel. The panel's handleEvent would classify
the event, and pass it on to the panel's action. action
would typically be an overridden application method that
figures out which of the panel's buttons had been pressed
by examining the Event.Target, and Event.arg fields
(happily examining x,y is usually NOT necessary). action
would then finally do something useful -- like display a
graph -- i.e. whatever the user wanted to happen when she
clicked the button.
The panel's HandleEvent then would return true to indicate
everything that needs to happen as a result of the button
being pressed is now complete. This kills the event and
stops further percolation.
In the new model, all works the same, except that every
event handler returns false so that the event continues to
percolate all the way back to the native GUI again.
Programmers with a Delphi background might prefer to write
an overriding handleEvent for the individual button that
directly called the relevant application code.
HOW A KEYPRESS WORKS
********************
Keypress logic is currently buggy in some implementations,
notably Windows 95. However, here is how it is SUPPOSED
to work in the new model. For a keypress to have any
effect it MUST make it all the way back to the native GUI.
Application code may modify or suppress the keystroke as
it percolates. The intent is to allow filtering,
censoring, blocking or transformation of the keystroke,
e.g. converting it into lower case. On the same
percolating pass, application code must also process the
keystroke if the native GUI component can't do it all on
its own.
A parent cannot censor what keystrokes its children see.
Event percolation works the other way around. The kids
alone decide what they want their parents to know.
Let us say that the user hit the Z key while the focus was
on a listbox (awt.List) component. Her intent is to
select Zebra from a list of animals presented.
The KEY_PRESS event starts at the bottom at the peer
listbox component's postEvent. The peer hands the event
off to the AWT listbox component by calling its postEvent.
The event would normally percolate all the way to the top
without modification or any other action. The handleEvent
for each level would just return false. Event handlers
may optionally modify the event.key or the event.modifiers
on the way through. If any event handler returns true,
that stops dead any further processing of the keystroke.
It will be as if the "Z" key were never pressed.
If the KEY_PRESS event successfully percolates all the way
to the top with no event handler killing it, it eventually
reaches the list box peer component again. The peer then
hands the (possibly-modified) keystroke back to the native
GUI again for further processing. This time the native
GUI does the bulk of the keystroke processing work. This
contrasts with button processing where the bulk of the
native GUI work happens before the event percolates.
WHAT SORTS OF COMPONENTS SEE WHAT SORTS OF EVENTS?
**************************************************
You might wonder if the awt peer or awt list box component
sees ACTION events or more primitive MOUSE_DOWN/MOUSE_UP
events or both. Surprisingly this is not documented. Exactly
what happens depends rather too much on what the native GUI
is willing to provide.
How the work is split between the peer and the GUI and the
awt component is still in flux.
WRITING YOUR OWN CUSTOM CONTROLS
********************************
If you wanted to create your own custom control, say a dot
that changed colour each time it was pressed, cycling
through red, yellow, green, what duties would your code
have to perform? What kinds of event would you receive?
What sorts of event would you be expected to generate?
Unfortunately the answers to these questions are not yet
documented. Your best bet is to just see what sorts of
events arrive and code to that, then test your code on
other platforms and adjust until it finally works
everywhere. Ouch!
The lack of documentation on event processing may be
deliberate. I think the idea is your program should not
depend on how the AWT works inside. This has deliberately
been left undefined. Your code should fly even if there
is only a single thread processing all paint requests and
events, It should also fly even if there are 10,000
threads attempting to process every event fully
simultaneously even for the same component. If you need
serialisation, you should be using synchronised methods.
REPERCUSSIONS
*************
An event must be completely handled before another can be
processed. Don't do anything time consuming to process an
event or you will stall the processing of all events.
Instead, spin off time-consuming work to be done on a new
thread, or enqueue it to be done later by a fixed set of
worker threads that process work request packets. Be
wary of thread fission.
In some systems, your repaint logic can't even start until
the current event has been completely handled. Doing a
sleep just stalls event processing, making matters even
worse. Don't go to sleep in the middle of processing an
event!
You might think the best way to a handle a time-consuming
event hand task is to do some of the work then post an
event to yourself to tell you where to carry on later, as
you might under Windows 3.1. This won't work. postEvent
does not enqueue events. It does not return until the
posted event has been totally handled. You could of course
invent your own queuing mechanism.
PAINTING
********
Painting is handled by a totally separate mechanism
nothing to do with events. When you invoke repaint, it
sends a message to the native GUI suggesting that it would
be a good idea if sometime in the distant future when it
is convenient and things are slack and when the GUI feels
in the mood that the painting work should be done.
The native GUI enqueues this request. As windows occlude
each other and reveal each other, the native GUI itself
decides that certain components, or parts of components
also need to be repainted. The native GUI merges all these
requests and removes the duplicates. It may reorder them
so that background panels are repainted before the
overlaying components.
Eventually the GUI will call the update method for a
component to be painted, passing it a graphics object
describing the region on the screen to be updated complete
with clipping region, since quite likely only part of the
component needs to be repainted.
The update and paint routines just paint the entire
component. The clipping happens automatically.
update then typically erases the region and calls paint.
A side effect of this painting will be that some of the
subcomponents will be overlaid and will need to be
repainted. It is not the concern of update nor paint to
arrange for this. The native GUI handles that logic all
by itself. No application code need concern itself with
the problem.
Don't dawdle in a paint routine. No other painting can be
done until your routine completes. In theory painting is
handled by a separate thread from the event handling and
can run in parallel, but it seems in practice any stalling
in either paint or event processing sometimes holds
EVERYTHING up.
PHILOSOPHY
**********
Application programs have no business dealing with
individual events. Only the custom controls should see
them. This "plumbing" should be hidden behind the walls.
Techniques of hanging callback code off buttons will make
it possible for application programmers to ignore these
confusing event critters altogether.
-30-
Ro...@bix.com <Roedy Green of Canadian Mind Products> contract programming
-30-