HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

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.

The best way to describe this is to hitch a ride on an event and watch how it percolates from birth to death on the event processing assembly line.

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. 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 awt.Button object.
  • AWT's awt.peer.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 most likely will do all of its processing now.

Sometimes the native GUI handles dispatching, finding the corresponding peer object to give the event to. 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 ten public fields. The most important are:

  • Event.id = ACTION_EVENT, type of event
  • event.target = reference to the application programmer's Java-style awt.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 Events Are There?
Events are classified by the event.id field which can have the values shown in Table 1:

Not all these types of event percolate through each component. For most standard components many events never even make it out of the native GUI.

Table 1

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 (in the new model) looks like the code in Listing 1.

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. The 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. Then, handleEvent 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 on 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.

The 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 its 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. handleEvent then calls postEvent of the button's parent -i.e. the panel containing the button-handing it as 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 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 s/he 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 this 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.

Some programmers might prefer to write an overriding handleEvent for the individual button that directly called the relevant application code, to create a classes of smart components that could accept callback delegate objects.

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 had never been 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 for further processing. Now, when it sees the event for the second 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 Component
If you wanted to create your own custom component, e.g. 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. Some may claim 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
In practice, 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 in the background. 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 Thread.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 handling 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 having 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. They don't need to be aware of the clipping. Update then typically erases the region using a fillRect 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. In practice, 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
The typical application program has 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 smart components will make it possible for application programmers to ignore these confusing event critters altogether. Credits: This article would not have been possible without the patient assistance of Jan Newmarch, who researched the material.

About the Author
Roedy Green is a computer consultant and contract programmer. You can reach him at: 604 685-8412.

	

Listing 1: Event Percolation

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);
  }

public boolean handleEvent(Event e) {
   switch (e.id) {
     case Event.MOUSE_ENTER:
       return mouseEnter(e, e.x, e.y);
     case Event.MOUSE_EXIT:
       return mouseExit(e, e.x, e.y);
     case Event.MOUSE_MOVE:
       return mouseMove(e, e.x, e.y);
     case Event.MOUSE_DOWN:
       return mouseDown(e, e.x, e.y);
     case Event.MOUSE_DRAG:
       return mouseDrag(e, e.x, e.y);
     case Event.MOUSE_UP:
       return mouseUp(e, e.x, e.y);
     case Event.KEY_PRESS:
     case Event.KEY_ACTION:
       return keyDown(e, e.key);
     case Event.KEY_RELEASE:
     case Event.KEY_ACTION_RELEASE:
       return keyUp(e, e.key);
     case Event.ACTION_EVENT:
       return action(e, e.arg);
     case Event.GOT_FOCUS:
       return gotFocus(e, e.arg);
     case Event.LOST_FOCUS:
       return lostFocus(e, e.arg);
     }
    return false;
  }


 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.