Chapter 11

Advanced Event Handling


CONTENTS


This chapter looks at event handling in the AWT. The first section, "Basic Event Handling," is about how events are generated and processed by the AWT and by applications using this toolkit.

When application code is called in response to an event, it may need to get information about the event, such as the coordinates of a mouse click. The information available to an application is discussed in "The Event Class." There are two sets of events that are particularly important for applications-key and mouse events. Further details on these are given in the next two sections.

Regrettably, programs do need debugging. The AWT still has problems, which sometimes makes determining where errors are occurring difficult, so a program is included in this chapter that can be used to show the events that occur in an application and the event information associated with them.

The topic of event generation is revisited in the section "Generating Events." Events are manufactured by the AWT in response to user actions, but applications may also create them and send them to objects.

You have probably already come across a number of problems with the AWT event model. Some of these require fixes by the library authors, and some can be fixed by defining new classes and filling in some gaps in the AWT methods. These techniques are dealt with in "Fixing Broken Event Handling." This section is followed by a longer example incorporating the techniques discussed earlier.

There are deeper problems with AWT events that cannot simply be fixed by a few new classes, requiring instead a replacement of the event model. Two alternative models are examined in the section "Major Surgery to the Event Model."

Basic Event Handling

The AWT toolkit generates events in response to user actions. For example, selecting a button generates an ACTION_EVENT. Applications or applets that use the AWT toolkit have to respond in an event-driven manner to these events; for example, a mechanism must exist for catching these events and processing them.

GUI applications sit in an event loop to catch and dispatch events. The detailed mechanism of this varies between the different GUI toolkits. For example, Windows enables you to catch each event and branch on the event type. Then, application-specific code is invoked. Xlib (the basic library of the X Window System) acts in a similar way, which is a strictly procedural approach.

Motif and other Xt-based toolkits allow application-specific code to be attached to callback functions, which is more object oriented in approach in that it attaches application code to the application objects. It does, however, have flaws. For example, it is hard to separate view from model-one often ends up with application code being mixed up with GUI code.

AWT hides the event processing loop in its internals and posts events to objects. When an event of interest to the AWT toolkit occurs, the toolkit calls postEvent() for the object it occurred in. For subclasses of component, postEvent() calls the handleEvent() method in the object. If handleEvent() returns false, it calls handleEvent() in the parent object, etc., until either the method returns true or the top of the tree is reached.

Note that these events are not Windows events, nor X events, nor Mac events, but AWT events. They get triggered by certain actions occurring in Windows, but not by all actions. For example, when a Motif PushButton is clicked (activated), an AWT event with field id set to ACTION_EVENT is sent to the AWT Button. When the Motif PushButton is merely pressed (armed), however, no AWT event gets generated.

This setup often causes frustation to the programmer familiar with a particular windowing system: At present you just do not have the detailed control over some Java objects that you have over the native objects that they are built on. For example, Java does not presently support control over drag and drop in the full sense of being able to drag information from one application to another-a window can be dragged from, due to native support, but when you try to drop, Java has no way of handling this action. (The MOUSE_DRAG event type is just mouse motion with one of the buttons pressed-it does not register a drop site.)

handleEvent() Method for Component

Most AWT classes inherit the handleEvent() method from component, which does a switch on event id and calls another method:

public boolean handleEvent(Event evt) {
   switch (evt.id) {
      case Event.MOUSE_ENTER:
         return mouseEnter(evt, evt.x, evt.y);
      case Event.MOUSE_EXIT:
         return mouseExit(evt, evt.x, evt.y);
      // other case elements omitted
      default:
         return false;
   }
}

public boolean mouseEnter(Event evt, int x, int y) {
   return false;
}

Note
This group does not include classes derived from MenuComponent. They are dealt with in the section "Fixing Broken Event Handling."

Not all events call methods; some fall through to the default case, which returns false. The methods called for AWT component objects all return false. You can override them in a user-defined subclass.

For a concrete example of this, consider the button. When the button gets clicked, an AWT event the with id set to ACTION_EVENT is generated. handleEvent() calls the method

public boolean action(Event evt, Object what)

The second argument here is the value of the button's label as a String. To examine this value, what needs to be coerced to the String class.

If you use a button and want to attach application code that is executed on a button click, you can do so by overriding action() in a subclass

class MyButton extends Button {
    public boolean action(Event evt, Object what) {
        // handle button ...
        System.out.println("I've been pushed");
        return true;
    }
}

Note
Note that this returns true to prevent the event from being passed to its parent. This return value is intimately tied up with which event model is used. This idea gets discussed further in the following two sections.

To give a realistic example of event handling, consider the problem of password entry. The password should be entered into a single-line TextField. This class actually has a special method to set the echo character to something other than the input character. When the newline character is pressed, the method action() is invoked. You can override this in a subclass of TextField to get useful behavior. The program appears in Listing 11.1.


Listing 11.1. Password.java.
import java.awt.*;

public class Password extends Frame {

    public static void main(String argv[])
        new Password().show();
    }

    Password() {
        add("Center", new PasswordText());
        resize(100, 20);
    }
}

class PasswordText extends TextField {

    PasswordText() {
         setEchoCharacter('*');
    }

    public boolean action(Event evt, Object what) {
        // just print the "secret" password
        System.out.println(getText());
        return true;
    }
}

Event Models

The AWT is built on top of native toolkits. For example, in Windows 95 all Java GUI objects are implemented by Windows objects. The native toolkits have their own event handling models, with their own way of handling user actions. For example, when the user presses the left mouse button, Windows 95 generates a WM_LBUTTONDOWN event, while Motif generates a BUTTON_PRESS event. The details of how these are handled are buried in the native code of each implementation of the AWT.

AWT supplies a layer of event handling code that will be called when an event occurs and is written in C as native code, different for each toolkit. This code is responsible for preparing a dynamic call to the Java interpreter, using the AWT object's peer object. For example, when an AWT button is clicked, a call is made from the native toolkit up to the Java interpreter to execute the method action() of the button's peer. The peer's method prepares an AWT event from the information passed to it and calls postEvent() for the object.

The AWT event is then dealt with by Java code within the AWT toolkit for a while. For objects of the component type, handleEvent() is called on the object, its parent, and its grandparent (in the widget tree) until one of the methods returns true. If none of these return true, then the handleEvent() of the peer objects is called, from outer container to inner. These methods are native and also return a Boolean value.

public boolean postEvent(Event e) {
    ComponentPeer peer = this.peer;

    if (handleEvent(e)) {
        return true;
    }

    if (parent != null) {
        e.translate(x, y);
        if (parent.postEvent(e)) {
            return true;
        }
    }

    if (peer != null) {
        return peer.handleEvent(e);
    }

    return false;
}

For example, suppose a frame contains a panel, and within the panel is a button. If the button is selected, then postEvent() is called successively on the button, the panel, and the frame (assuming that each of these returns false from handleEvent()). Then the event is offered to handleEvent().

Within this structure, the Java AWT 1.0 has two ways of dealing with the native events-the "old" and "new" ways. This has two unfortunate effects: First, the Java code you write as an applications programmer may be different for the two models; second, Sun has promised to migrate events from the old model to the new model over time, which means that the code you write today may not work tomorrow.

Old Event Model for Component

In the old model, a native event affects the native object immediately. In response to a mouse click, for example, the focus may change to an application and bring it to the top; in response to selecting an item in a list, the highlighted element may change. When the AWT event is created, it belongs to the Java level only. The event that triggered the application code should be treated as a read-only object because changes to it have no effect.

When application code executes for the event it may return true or false from handleEvent(). The common convention has been to return true to stop further event propagation.

There is a lot of code in the public domain that still assumes this event model, and a lot of tutorials and books that tell you that this is the way to do it. Unfortunately, you have to use this old model for some cases even in JDK 1.0.1 because of bugs, some of which are discussed in a later section.

New Event Model for Component

The new model changed the underlying handling of native events so that Java application code can filter events.

In a TextComponent, what gets typed may not be what the application wants to see in the TextComponent. For example, the application may want:

These examples show why an application may want to change the event presented to a different one, to discard the event, or to change it into a sequence of events.

The new model supports this by trapping the native event before it gets to the native GUI object. It uses this to create the AWT object that it sends on its route up through the object and its parents and back down through the peers. Once it arrives back in the peer object, it is converted back into a native event and then is given to the native object. Changes to the AWT event are rejected by changes to the native event, so that an application can filter and modify events.

Caution
The peer objects allow platform-specific code to execute. You should normally stay away from peer objects because they are a "hidden" part of AWT that exists to implement lots of native code methods. The new event model forces you to pay more attention to the peer objects than you should.

Right now, the AWT only actively uses the new event model for key events: A key event is trapped before it gets to the native GUI object, is sent through the application code, and then the (possibly modified) event is fed back into the native GUI object. The new model allows an application to change the input character into a different one: One of the component's handleEvent() methods need only change the key value while returning false. For example, to change every character input into lowercase for a TextArea, the keyUp() method should be overridden to

public boolean keyUp(event evt, int key) {
    evt.key = Character.toLowerCase((char) key);
    return false;
}

Suppressing a character entirely is done by one of the handleEvent() methods returning true. Expanding a single keystroke into a set of keystrokes is a bit messier, but can be done by generating a set of events and passing them to the parent's handleEvent(). An example is given later to do this.

Caution
You should remember the following points:
  • In AWT 1.0.2, filtering by the new event model is only done to key events-but the Sun documents suggest this will be extended to other event types over time.
  • The AWT code to do this is broken in Windows 95 for JDK version 1.0.1 and earlier, so nothing changes if you do change the event's key.
  • The AWT code to catch key events for the X/Motif JDK version 1.0.1 is also broken-you don't even get the events to try to change them.
  • If the bugs get fixed, then events must be passed to the peer object, or they will never get to the native GUI object. Thus, handleEvent() must never return true, only false for key events (unless you want to discard the event).

The net result of this idea is that handleEvent() must return false for those event types which get intercepted before reaching the native GUI object. This happens by default. Even if extensive application code is invoked by receipt of the event, however, it must still return false. If handleEvent() returns true, the event will never get back to the GUI object. This is a distinct change from the old event model.

On the other hand, for those event types which are not intercepted, the old event model applies, and the component that handles the event should return true.

Regrettably, in JDK 1.0 you cannot assume a single approach for all event types. Adopting the old model will result in lost key events, whereas adopting the new model, returning false, leads to problems with other event types (an example appears in the section "Window Events").

This problem could have been solved more cleanly by allowing a string of keys to be stored in an event, using the old event model and then passing the (possibly modified) event into the native object. Unfortunately, this process would have to be done within the peer object, which would be messy for application developers; however, it is fairly easy for those in charge of the AWT.

The Event Class

While processing events, an application may need to make use of information such as the location of the mouse in a button click. Much of this information is contained in the event itself, which is an instance of the Event class. This section discusses this class in more detail.

The Event class is central to the AWT. Events are constructed by peer objects in a supposedly invisible manner. They are then fed into methods such as postEvent() and are used in handleEvent() and convenience methods. A detailed knowledge of the Event class is very necessary for use of the AWT.

Event Fields

A large set of final variables exist that just define constants; these variables are discussed in later sections. Apart from these items, the variables in each Event object are

Object  target;
long    when;
int     id;
int     x;
int     y;
int     key;
int     modifiers;
int     clickCount;
Object  arg;
Event   evt;

The target is the object the event occurred in, for example the button the mouse was pressed in. when is a timestamp for the event. The x and y fields are the coordinates of the event within the target and follow the usual practice of being measured from the top-left of the object. The key and modifiers sometimes convey extra information; they are discussed in more detail later.

When objects share common characteristics, differing only in small ways, you may create separate classes for each, where each class is derived from a common parent. Alternatively, you may use non-OO tricks, distinguishing each item by different values of a field. Which method gets used depends on the designer of the class(es). For the Event type, a large number of different events exist, so having a separate class for each type would lead to a large number of derived classes, which cause confusion. The different variations on event types are instead handled by use of the id field within the single Event class. The values of this field are discussed later.

Events are generated by many different objects-buttons, Lists, TextField, etc. When an Event is prepared, the arg field may be set to any suitable object by the AWT object implementing postEvent(). This field is often set to information that can be obtained from the event and is just for convenience.

Because of Java safety rules, the various fields will always contain sensible values. Whether they actually have useful values depends on the type of the event.

Event Types

The constant values used to distinguish event types appear in Table 11.1. They appear roughly alphabetically but are grouped by function. For example, there are two focus-related events, four key-related events, etc.

Table 11.1. Event types.

ACTION_EVENT
GOT_FOCUS LOST_FOCUS  
KEY_ACTION KEY_ACTION_RELEASE KEY_PRESS
KEY_RELEASE    
LIST_DESELECT LIST_SELECT  
LOAD_FILE SAVE_FILE  
MOUSE_DOWN MOUSE_DRAG MOUSE_ENTER
MOUSE_EXIT MOUSE_MOVE MOUSE_UP
SCROLL_ABSOLUTE SCROLL_LINE_DOWN SCROLL_LINE_UP
SCROLL_PAGE_DOWN SCROLL_PAGE_UP  
WINDOW_DEICONIFY WINDOW_DESTROY WINDOW_EXPOSE
WINDOW_ICONIFY WINDOW_MOVED

Useful Event Fields

The id field is used by the toolkit and also by the Java programmer to distinguish between event types. Just as with native events, different event types have different pieces of useful information. For example, the ACTION_EVENT is generated after a button has been clicked. The toolkit designers have decided that a knowledge of the x, y coordinates of the mouse is not necessary here, but a knowledge of the button's label is.

Because the different event types are all handled within the same class, some fields have useful information, but others do not. This is poor OO practice but is partly excusable. It avoids a large number of subclasses of an abstract event class, and due to the default values for Java data types, the useless fields will never have "dangerous" values in them. Table 11.2 lists the fields of the event class that are valid for the different types of event. The target field and id fields are always valid for each type. Some event types are never generated by the toolkit, so their valid fields are unknown.

Table 11.2. Valid event fields.

EventValid Fields
ACTION_EVENT arg*
LIST_DESELECT arg
LIST_SELECT arg
GOT_FOCUS none
LOST_FOCUS none
LOAD_FILE never generated
SAVE_FILE never generated
MOUSE_DOWN when, x, y, modifiers, clickCount
MOUSE_DRAG when, x, y, modifiers
MOUSE_ENTER when, x, y
MOUSE_EXIT when, x, y
MOUSE_MOVE when, x, y, modifiers
MOUSE_UP when, x, y, modifiers
SCROLL_ABSOLUTE arg
SCROLL_LINE_DOWN arg
SCROLL_LINE_UP arg
SCROLL_PAGE_DOWN arg
SCROLL_PAGE_UP arg
KEY_ACTION when, x, y, key, modifiers
KEY_ACTION_RELEASE when, x, y, key, modifiers
KEY_PRESS when, x, y, key, modifiers
KEY_RELEASE when, x, y, key, modifiers
WINDOW_DEICONIFY none
WINDOW_DESTROY none
WINDOW_EXPOSE never generated
WINDOW_ICONIFY none
WINDOW_MOVED x, y
* For MenuItem and CheckboxMenuItem the fields when and modifiers are also valid.

This and later tables show some minor inconsistencies. For a GOT_FOCUS or LOST_FOCUS event, no additional fields of the event are set. The gotFocus() and lostFocus() methods, however, have an extra parameter object, which turns out to be just null-a pretty useless parameter, really!

arg value for ACTION_EVENT

The arg value in an event carries additional information supplied by the toolkit about the context in which the event occurred. This value does not contain any new information content because any valid information can either be obtained from other event fields or from the object the event occurred in (available in target). It is, however, convenient to use sometimes.

Table 11.3 lists the value of arg for events of type ACTION_EVENT.

Table 11.3. arg Values for ACTION_EVENT.

ObjectValue Type
ButtongetLabel() String
CheckboxBoolean(getState()) Boolean
CheckboxMenuItemgetLabel() String
ChoicegetSelectedItem() String
ListgetSelectedItem() String
MenuItemgetLabel() String
TextFieldgetText() String

arg Value for SCROLLBAR_... Events

The events generated for the various Scrollbar actions all have a valid arg value of class Integer (note that this class is a wrapper class around the base type int). These values all contain the new slider location value.

arg Value for LIST_... Events

The events generated for LIST_SELECT and LIST_DESELECT have a valid arg value of type Integer, which is the index selected or deselected.

Tracking Mouse Clicks

For a simple illustration using this idea, the following example shows a box within a Canvas. When the user successfully clicks the mouse in the box, a "hit" count is updated, but when the box is missed, a "miss" count is updated instead. After each mouse click, the box gets moved to a new random location. This example uses the method mouseDown() in the Canvas object and uses the x, y values set for this method from the event information. The application, Chase.java, appears in Figure 11.1 with code in Listing 11.2.

Figure 11.1 : Chase application.


Listing 11.2. Chase.java.
import java.util.*;
import java.lang.*;
import java.awt.*;

class Chase extends Frame {
    static public void main(String argv[]) {
        new Chase().show();
    }

    Chase() {
        Report r = new Report();
        add("North", r);
        ChaseArea ca = new ChaseArea(r);
        add("Center", ca);
        resize(300, 300);
    }
}

/** A status bar showing hits and misses counts
 */
class Report extends Panel {
    int HitCount = 0;
    int MissCount = 0;
    Label Hits, Misses;

    Report() {
        setLayout(new GridLayout(1, 2));
        Hits = new Label("Hits: " + HitCount);
        Misses = new Label("Misses: " + MissCount);
        add(Hits);
        add(Misses);
    }

    public void addHit() {
        Hits.setText("Hits: " + ++HitCount);
    }

    public void addMiss() {
        Misses.setText("Misses: " + ++MissCount);
    }
}

/** A Canvas with a box drawn in it that moves
 *  randomly when the mouse is clicked in it
 */
class ChaseArea extends Canvas {
    final int box_size = 8;
    Rectangle box;
    Random rand;
    int box_x = 0, box_y = 0;
    Report report;

    ChaseArea(Report r) {
        report = r;
        rand = new Random();
        box_x = 0;
        box_y = 0;
    }

    // draw a new rectangle
    public void paint(Graphics g) {
        g.drawRect(box_x, box_y, box_size, box_size);
    }

    // move the box to a random location
    public void moveBox() {
        box_x = (int) Math.floor(rand.nextFloat() *
                      (size().width - box_size));
        box_y = (int) Math.floor(rand.nextFloat() *
                      (size().height - box_size));
        repaint();
    }

    // handle mouse down, moving box and updating report line
    public boolean mouseDown(Event evt, int x, int y) {
        if (box_x <= x && x <= box_x + box_size &&
            box_y <= y && y <= box_y + box_size) {
            report.addHit();
        } else {
            report.addMiss();
        }
        moveBox();
        return true;
    }
}

Key Events

Key and mouse events are probably the most important of the events that occur in the AWT. They are a little more complex than other events, and this is explored a little more in these two sections.

There are four key events: KEY_PRESS, KEY_RELEASE, KEY_ACTION, and KEY_ACTION_RELEASE. The first two of these are generated by pressing and releasing of the "ordinary" keys such as alphabetics and punctuation. The second two are generated in response to pressing and releasing "special" keys such as the function keys, the Escape key, etc. The event's id field can be used to distinguish between these types.

Key Values

Java uses the 16-bit Unicode character set to allow for internationalized applications. This is an area where the native toolkit has great influence. Windows NT uses Unicode for all character processing. The Motif toolkit has support for international character sets using "wide" characters and XmStrings. Its implementation of the AWT toolkit does not make any use of this as yet, using only 8-bit characters. Windows 95 still uses the Windows 3.1 windowing functions, which do not understand Unicode at all. Thus, although Java admirably supports Unicode, the ordinary ASCII set is all that can be currently used portably. Isn't this a shame in a graphical environment?

In addition to the "ordinary" characters, the Event class defines a number of constants for certain keys, which appears in Table 11.4.

Table 11.4. Constant key values.

DOWN ENDHOME LEFTPGDN PGUPRIGHT UP f1 ... f12

These can be used in code as

if (evt.key == Event.HOME) ...

modifiers

For some event types-the KEY... and MOUSE... types-the modifiers field is valid. modifiers is a bitmask of values, where zero means no mask. The possible masks are given in Table 11.5.

Table 11.5. modifiers constants.

ALT_MASKCTRL_MASKMETA_MASK SHIFT_MASK

For key events, they have the expected values-for example, the META key on a Sun is the "diamond" key.

Mouse Events

There are six mouse events, MOUSE_DOWN, MOUSE_DRAG, MOUSE_ENTER, MOUSE_EXIT, MOUSE_MOVE, and MOUSE_UP. MOUSE_DOWN and MOUSE_UP occur on button clicks for any other mouse buttons. MOUSE_MOVE and MOUSE_DRAG occur on moving the mouse-in MOUSE_DRAG one of the mouse buttons is held down during movement. MOUSE_ENTER and MOUSE_EXIT occur on moving the mouse pointer in and out of a window.

Modifiers

The following information is not officially documented, and some newsgroup messages suggest that this part of Java may change in the future.

For mouse events, the modifiers play an unusual role in that they distinguish keys using a three-button model. With a modifier value of zero, the button selected is the left button; with a modifier value of ALT_MASK, the button selected is the middle button; with a modifier value of META_MASK, the button selected is the right button.

When working with a physical mouse with fewer buttons than are logically required, one technique is to use modifier keys as described in the preceding text. Another technique is chording, where two buttons are pressed simultaneously. This technique is physically cumbersome. Chording is not supported by AWT: Even though the modifier value is a bit-wise OR of values, the value of zero for the left button means it canot be tested!

Many Macintosh users only have a single-button mouse, and many pc mice are still only two-button. Unless absolutely necessary, an application should try not to assume more than one button.

Displaying Events

In debugging an application and in just trying to find out what happens to events, you may want to be able to trace events as the application runs. This process can be done for the majority of events by simply overriding the top-level frame's handleEvent() to print event information and then call the overridden event handler. This step prints out all events, except those which are removed from the event chain by components lower in the window tree.

The following program (Listing 11.3) shows events for a very trivial program of a label and button in a frame.


Listing 11.3. EventTest.java.
import java.awt.*;

public class EventTest extends Frame {

    public static void main(String argv[])
    {
        new EventTest().show();
    }

    EventTest() {
        // add your own windows in here
        add("North", new Label("Hello World"));
        add("South", new Button("Hello too"));
        resize(200, 200);
    }

    public boolean handleEvent(Event evt) {
        System.out.println(evt.toString());
        return super.handleEvent(evt);
    }
}

The program prints constant integer values (such as id) in their literal, rather than symbolic, form. To interpret these values, you may need to have the Event.java source code handy.

Events with Methods

Events are eventually dealt with by a method handleEvent() of some component object. The default method does a switch on event id and often calls another method of component. While the handleEvent() method can be overridden in subclasses, doing so is not an ideal solution, and it is better to override the method called in the switch. For example, override the method action() rather than look for ACTION_EVENT in handleEvent().

Unfortunately, you cannot always use the preferred style of programming because not all event types call their own methods. Table 11.6 lists the methods called (or not called) by handleEvent() of component objects. In particular, note that none of the SCROLL_... nor WINDOW_... events are handled. Special treatment to redeem this deficiency (and a related one with Menus) appears in the section "Fixing Broken Event Handling."

Table 11.6. Event types and associated methods.

Event TypeMethod Called
ACTION_EVENT action(Event evt, Object arg)
LIST_DESELECT no method
LIST_SELECT no method
GOT_FOCUS gotFocus(Event evt, Object arg)
LOST_FOCUS lostFocus(Event evt, Object arg)
LOAD_FILE no method
SAVE_FILE no method
MOUSE_DOWN mouseDown(Event evt, int x, int y)
MOUSE_DRAG mouseDrag(Event evt, int x, int y)
MOUSE_ENTER mouseEnter(Event evt, int x, int y)
MOUSE_EXIT mouseExit(Event evt, int x, int y)
MOUSE_MOVE mouseMove(Event evt, int x, int y)
MOUSE_UP mouseUp(Event evt, int x, int y)
SCROLL_ABSOLUTE no method
SCROLL_LINE_DOWN no method
SCROLL_LINE_UP no method
SCROLL_PAGE_DOWN no method
SCROLL_PAGE_UP no method
KEY_ACTION keyDown(Event evt, int key)
KEY_ACTION_RELEASE keyUp(Event evt, int key)
KEY_PRESS keyDown(Event evt, int key)
KEY_RELEASE keyUp(Event evt, int key)
WINDOW_DEICONIFY no method
WINDOW_DESTROY no method
WINDOW_EXPOSE no method
WINDOW_ICONIFY no method
WINDOW_MOVED no method

Generating Events

Because the AWT responds to events, it is useful to know where the events come. The primary source is from the toolkit itself, which generates events in response to user actions.

Toolkit Generated Events

One complicated area of AWT is knowing what events get generated by what components. For example, an ACTION_EVENT is generated when a button is clicked, but what other events are generated for buttons? While it would be nice to be able to provide a table of what events are generated for what objects, it is not possible-no such table exists. The following list provides some of the particularly worrying aspects of AWT in the JDK version 1.0.1:

These problems could all chalked up to implementation hiccups if an implementation-independent specification of what should happen existed. Unfortunately, no such document seems to exist at the moment.

Application Generated Events

In the native toolkits, you may be able to synthesize events and send them to objects. Can this be done in AWT, and is there any point to it?

It can certainly be done; this process is exactly what happens in the peer objects. Methods such as action() in the button's peer object get invoked by the native toolkit when the button is clicked. The method creates an event and posts it

    public void action() {
        target.postEvent(new Event(target, Event.ACTION_EVENT,
                         ((Button)target).getLabel()));
    }

where the target is the button.

Is it worthwhile for an application to do this? The answer depends, unfortunately, on the type of event and whether it is handled under the old or new event model. Now look at two cases, a button under the old model and a TextField under the new one.

When a button is clicked, the native toolkit catches the event and deals with it. It changes the button's appearance, depressing and raising it. After all this is over, control is passed to AWT to create an ACTION_EVENT and post it to the button. The native event is handled by the old model: You can discard or change the AWT event without any effect on the GUI appearance-all that was already dealt with before the AWT event was created.

You can create an ACTION_EVENT and post it to a button. The application-specific code will still run in response to the event, but it will have no effect on the native implementation of the button. Thus, the button won't depress and raise. But other things won't happen that are also important: Selecting a button in an application that currently does not have the focus will cause it to rise to the top of other applications and set the focus to the button (under many window managers). This cannot be done in Java as yet.

Similar things happen with List. You can create a LIST_SELECT event and send it to the List object. The application will process the new selection, but the native GUI object will still be showing the old selection.

The net result of this for the old model is that the application may get out of synch with its native GUI implementation. This should be avoided. Easier ways probably exist, such as calling select() for the List object, which removes these problems.

So much for the old event model. In the new model, the application acts as a filter on the event, and changes are permissible. From JDK 1.0.2 onwards, what happens with keystrokes in TextComponents is this: The keystroke is captured before it reaches the native object and passed up to AWT. Application code has the ability to modify or discard the event before it finally gets back to the native object via the TextComponent's peer. The (possibly modified) event is then allowed to affect the native object in the normal way.

In the new event model, it does make sense to create and post events from within the application. The created events will not only affect the application but will also make their way into the native GUI object. For example, a single keystroke could be expanded into a sequence of strokes by overriding keyUp() in a subclass of the TextComponent:

public boolean keyUp(Event evt, int key) {
    if (key == 9) {
        // expand tab to 8 spaces
        Event e = new Event(evt.target, evt.when, Event.KEY_RELEASE,
                            evt.x, evt.y, ' ', evt.flags, null));
        if (peer != null)
            for (int n = 0; n < 8; n++) {
                peer.handleEvent(e);
        // lose the tab event
        return true;
    }
    // handle other keys normally
    return super.handleEvent(evt);
}

The preceding code short-circuits what any of its window parents might want to do to the new sequence of events. It also assumes that a subclass of TextArea/TextField is doing this. To relax these restrictions gets messier: You have to avoid potentially recursing around your macro expansion by passing the new events up to the parent's postEvent() and then deal with peer handling if the event comes back.

Fixing Broken Event Handling

In several places in this chapter, we have mentioned problems with the event model. Some of these cannot be fixed by the user of the AWT and will have to wait until the authors of the libraries fix them. On the other hand, many problems can be dealt with. This section looks at some techniques that an application writer can use.

When to Handle Events

If handleEvent() for a component returns false, then the event is passed to handleEvent() for its parent in the window tree. This leads to two common locations for handling events: in the object for which they were generated or in some ancestor object, typically a frame.

If Frame gets used to handle events, then it generally receives events from many descendants. Consequently, it will have to figure out which descendant the event came from before it can call the relevant application code. You see much code like this in a subclass of Frame:

public boolean action(Event evt, Object what) {
    String name = (String) what;

    if (name.equals("File"))
        // handle File button
    else if (name.equals("Quit"))
        // handle Quit button
    // etc
}

This process can lead to very fragile code. Consider an application with a few hundred buttons:

On the other hand, this technique makes it easy to deal with small applications. Because most of the applets and applications seen on the Net so far are fairly small, many examples exist using this method. It doesn't scale, though.

If each object handles its own events, then you have to subclass each object to hold the application code. Each individual action() method will only contain the application code for that object because it won't need the distinction test between objects. The downside is that it leads to a large number of subclasses, each there just to supply application code for that object.

Some intermediate possibilities exist, of course, but these two extremes are the most commonly seen. Of the two, the second seems preferable, where each object handles its own events. It means that you can rename the object, move it around the window hierarchy, and so on without having to worry about losing the association between the object and its application code.

Neither of these extremes satisfy demands for separation of GUI object and application code-these concerns are addressed in the section "Major Surgery to the Event Model."

Missing Methods

For some types of events, handleEvent() calls a convenience method. For example, an event of type KEY_PRESS gets handled by keyDown(). Many events, unfortunately, are not, introducing an inconsistency in handling them.

This section discusses how consistency can be brought in for these other event types. Basically it is quite simple: Define a subclass of the object affected, redefine handleEvent() for this subclass to use convenience methods for these event types, and from then on, use the new class and its new convenience methods rather than the old one.

List Events

List objects generate LIST_SELECT and LIST_DESELECT events. No branch occurs in handleEvent() of component for these event types, so they usually get handled by redefining handleEvent() in application code for each List object needed or deferring the problem to a frame that knows about all of the lists. A much simpler solution is to fix the problem once in a subclass of List and then to use this subclass when needed.

class SelectList extends List {
   public boolean handleEvent(Event evt) {
      List list = (List) evt.target;
      int index = ((Integer) evt.arg).intValue();

      switch (evt.id) {
         case Event.LIST_SELECT:
             return selected(evt, index, list.getItem(index));
         case Event.LIST_DESELECT:
             return deselected(evt, index);
         default:
             return super.handleEvent(evt);
      }
   }

   public boolean selected(Event evt, int index, String item) {
      return false;
   }

   public boolean deselected(Event evt, int index) {
      return false;
   }
}

The SelectList class defines two convenience methods, selected() and deselected(). Each of these methods has as a parameter the index of the item selected/deselected. In the case of selection, it is also worthwhile to include the item selected, for convenience. (The SelectList also requires constructor methods like those of List. They do not appear here.) An example of this is given by the application of Listing 11.4, which displays a list of colors and repaints the label when a color is selected. The application appears in Figure 11.2.

Figure 11.2 : Colors application.


Listing 11.4. Colors.java.
import java.awt.*;

class Colors extends Frame {
    final Color colors[] = {Color.red, Color.blue, Color.green};
    final String colorLabels[] = {"red", "blue", "green"};
    Label label;

    public static void main(String argv[]) {
        new Colors().show();
    }

    Colors() {
        ColorList list = new ColorList();
        for (int n = 0; n< colors.length; n++)
            list.addItem(colorLabels[n]);

        label = new Label("Hello World");

        // set geometry
        add("West", list);
        add("Center", label);
        resize(300, 100);
    }

    public void setColor(int index) {
        label.setForeground(colors[index]);
    }
}

class ColorList extends SelectList {
    public boolean selected(Event evt,  int index, String item) {
        ((Colors) getParent()).setColor(index);
        return true;
    }
}

class SelectList extends List {
    public boolean handleEvent(Event evt) {
        List list = (List) evt.target;
        int index = ((Integer) evt.arg).intValue();

        switch (evt.id) {
             case Event.LIST_SELECT:
                 return selected(evt, index, list.getItem(index));
             case Event.LIST_DESELECT:
                 return deselected(evt, index);
            default:
                 return super.handleEvent(evt);
        }
    }

    public boolean selected(Event evt, int index, String item) {
       return false;
    }

    public boolean deselected(Event evt, int index) {
       return false;
    }
}

Scrollbar Events

Events related to Scrollbar are SCROLL_LINE_UP, SCROLL_LINE_DOWN, SCROLL_PAGE_UP, SCROLL_PAGE_DOWN, and SCROLL_ABSOLUTE. No convenience method gets called by handleEvent()for these event types.

Fix this up in the same way as for the List class by defining a subclass with a set of convenience methods. From then on, use this new class.

class SelectScrollbar extends Scrollbar {
   public boolean handleEvent(Event evt) {
      switch (evt.id) {
          case Event.SCROLL_ABSOLUTE:
              return scrollAbsolute(evt, evt.arg);
          case Event.SCROLL_LINE_DOWN:
              return scrollLineDown(evt, evt.arg);
          case Event.SCROLL_LINE_UP:
              return srcollLineUp(evt, evt.arg);
          case Event.SCROLL_PAGE_DOWN:
              return scrollPageDown(evt, evt.arg);
          case Event.SCROLL_PAGE_UP:
              return scrollPageUp(evt, evt.arg)
          default:
              return super.handleEvent(evt);
      }
   }

   public boolean scrollAbsolute(Event evt, Object what) {
      return false;
   }

   public boolean scrollLineDown(Event evt, Object what) {
      return false;
   }

   public boolean scrollLineUp(Event evt, Object what) {
      return false;
   }

   public boolean scrollPageDown(Event evt, Object what) {
      return false;
   }

   public boolean scrollPageUp(Event evt, Object what) {
      return false;
   }
}

WINDOW Events

WINDOW events are also not dealt with by handleEvent(),which leads to two problems:

These problems should have a default handler that can be overridden by application-specific code. Again, you need new classes with new convenience methods implementing the default behavior.

Window events are generated for Frame, Dialog, and Window. You need a new subclass for each.

class WindowHandlingFrame extends Frame {

    public boolean handleEvent(Event evt) {
        switch (evt.id) {
            case Event.WINDOW_ICONIFY:
                return windowIconified(();
            case Event.WINDOW_DESTROY:
                return windowQuit();
            default:
                return super.handleEvent(evt);
        }

    public boolean windowIconified() {
        return true;
    }

    public boolean windowQuit() {
        System.exit(0);
return true;
    }
}

You should have similar classes for Dialog and Window.

Menu Events

Event handling within menus has been designed to be different than component, which can be a nuisance. When a MenuItem is selected, an event with id set to ACTION_EVENT is generated, and postEvent() is executed for that object. postEvent(), however, does not call handleEvent() like component objects do-no method handleEvent() exists for MenuItems. Instead, it calls the postEvent() method for the parent in the GUI tree. Consequently, it walks up the GUI tree until it gets to the ancestor Frame object and executes handleEvent() for the Frame.

Because no handleEvent() method exists for MenuComponent objects, none of the convenience methods events such as action() or mouseDown() exist to handle menu events.

This approach seems poor because it requires the Frame object to know all sorts of detail information about the structure of its menu, which will make the application difficult to change as it evolves.

You can easily make menus behave in the same way as components, but it means tampering with a method that the designers of the AWT toolkit would probably prefer you not bother: the postEvent method. This is really part of the implementation side that normally should not be overridden. That solution seems the simplest way to get consistency in event handling.

You have no interest in actions for MenuBar and Menu-only in MenuItem. Define a new subclass of MenuItem with two methods postEvent and action, which shortcuts the component mechanism that also uses the handleEvent method.

class MenuButton extends MenuItem {
    public boolean postEvent(Event evt) {
        if (evt.id == Event.ACTION_EVENT)
            if (action(evt, evt.arg))
                return true;
        }
        return super.postEvent(evt);
    }

    public boolean action(Event evt, Object what) {
        return false;
    }
}

This code uses the old event model, which is currently correct for MenuItem. If later versions of AWT change menu handling to the new event model, then you need to modify this code to call peer object handleEvent() on failure.

In the future, subclass all menu buttons from MenuButton, with application logic in action():

class QuitButton extends MenuButton {
   public boolean action(Event evt, Object what) {
      System.out.println("Exiting...");
      System.exit(0);
      return true;
   }
}

For example, consider a program with a label in a frame, where you can change the foreground color of the label by selection from a menu. This program is similar in intent to the program Colors.java of Listing 11.4 but uses a menu to select the color instead of a list. The complete code appears in Listing 11.5.


Listing 11.5. ColorMenu.java.
import java.awt.*;

public class ColorMenu extends Frame {
    private Label label;

    public static void main(String argv[]) {
        ColorMenu cm = new ColorMenu();
        cm.show();
    }

    ColorMenu() {
        label = new Label("Hello");
        add("Center", label);
        CreateMenu();
        resize(100, 100);
    }

    private void CreateMenu()
    {
        MenuBar mb = new MenuBar();
        Menu fileB = new Menu("Color");
        mb.add(fileB);

        ColorMenuButton blueB = new ColorMenuButton("Blue", this);
        ColorMenuButton redB = new ColorMenuButton("Red", this);
        fileB.add(blueB);
        fileB.add(redB);

        setMenuBar(mb);
    }

    public void changeColor(Color col) {
        label.setForeground(col);
    }
}


class MenuButton extends MenuItem {
    MenuButton(String name) {
        super(name);
    }

    public boolean postEvent(Event evt) {
        if (evt.id == Event.ACTION_EVENT) {
            if (action(evt, evt.arg))
                return true;
        }
        return super.postEvent(evt);
    }

    public boolean action(Event evt, Object what) {
        return false;
    }
}

class ColorMenuButton extends MenuButton {
    ColorMenu toplevel;

    ColorMenuButton(String name, ColorMenu top) {
        super(name);
        toplevel = top;
    }

    public boolean action(Event evt, Object what) {
        String name = (String) what;

        if (name.equals("Red"))
            toplevel.changeColor(Color.red);
        else
            toplevel.changeColor(Color.blue);
        return true;
    }
}

Now, look at selected parts of this code. The class ColorMenu is a top-level frame extended to hold a label and a menu. An additional method is used to set the color of the label.

MenuButton derives from MenuItem and adds the new postEvent and action() methods. You also have to add in a constructor method that just calls the super constructor. This step needs to occur because MenuItem has only one constructor, which takes a String parameter, and such constructors cannot be inherited in Java.

The buttons you actually want in the menu are of class ColorMenuButton, which derives from the MenuButton class and overrides the action() method. action() calls the method changeColor() of the top-level ColorMenu, which resets the foreground of its label. The top level is passed through as a parameter in the constructor. An alternative could be to walk up the parent tree when required until the top level is reached.

A Complete Example

The following example enables the selection of an image from a list and shows the image in a clipped area. This setup uses the modified List class of SelectList to handle the selection and the modified Scrollbar class of SelectScrollbar to handle the interaction with the scrollbars. These two classes are omitted from the following listing because they have already been presented.

A screen capture of this applet running from within the AppletViewer appears in Figure 11.3, with the program shown in Listing 11.6. The applet HTML appears in Listing 11.7.

Figure 11.3 : An image viewer applet.


Listing 11.6. ImageViewerApplet.java.
import java.awt.*;
import java.awt.image.*;
import java.applet.Applet;

/**
 * An applet class to view images
 */
public class ImageViewerApplet extends Applet {

    String imageStrings[] = {"udp.gif", "tcp.gif"};
    ImageViewer imageViewer;

    // initialize applet to show first image
    public void init() {
        Image image = getImage(getDocumentBase(), imageStrings[0]);

        imageViewer = new ImageViewer(image);

        ImageList imageList = new ImageList(imageStrings);
        imageList.select(0);

        setLayout(new BorderLayout());
        add("West", imageList);
        add("Center", imageViewer);
    }

    // set a new image in the viewer
    public void setImage(String item) {
        Image image = getImage(getDocumentBase(), item);
        imageViewer.setImage(image);
    }
}

/**
 * a class to display a list of image names and set a new image
 */
class ImageList extends SelectList {

    ImageList(String items[]) {
        for (int n = 0; n  < items.length; n++) {
            addItem(items[n]);
        }
    }

    public boolean selected(Event evt, int index, String item) {
        ((ImageViewerApplet) getParent()).setImage(item);
        return true;
    }
}

/**
 * a composite class to show an image with scrollbars
 */
class ImageViewer extends Panel {

    
    ImageCanvas imageCanvas;
    ImageScrollbar horizontalSB, verticalSB;

    ImageViewer(Image image) {
        imageCanvas = new ImageCanvas(image);

        horizontalSB = new ImageScrollbar(Scrollbar.HORIZONTAL,
                                        0, 200, 0, 200,
                                        imageCanvas);

        verticalSB = new ImageScrollbar(Scrollbar.VERTICAL,
                                        0, 200, 0, 200,
                                        imageCanvas);

        // Set the geometry layout
        GridBagLayout gridBag = new GridBagLayout();

        setLayout(gridBag);

        // first the imageCanvas
        GridBagConstraints c = new GridBagConstraints();
        c.weightx = 1.0;
        c.weighty = 1.0;
        c.fill = GridBagConstraints.BOTH;
        gridBag.setConstraints(imageCanvas, c);
        add(imageCanvas);

        // then the horizontal Scrollbar
        c.weightx = 0.0;
        c.weighty = 0.0;
        c.gridx = 0;
        c.gridy = 1;
        gridBag.setConstraints(horizontalSB, c);
        add(horizontalSB);

        // finally, the vertical Scrollbar
        c.gridx = 1;
        c.gridy = 0;
        gridBag.setConstraints(verticalSB, c);
        add(verticalSB);

    }

    void setImage(Image img) {
        imageCanvas.setImage(img);
    }

    void setHorizontalScrollbar(int value, int visible, int min, int max) {
        horizontalSB.setValues(value, visible, min, max);
    }

    void setVerticalScrollbar(int value, int visible, int min, int max) {
        verticalSB.setValues(value, visible, min, max);
    }

    int getHorizontalValue() {
        return horizontalSB.getValue();
    }

    int getVerticalValue() {
        return verticalSB.getValue();
    }

}

/**
 * a class to display the image
 */
class ImageCanvas extends Canvas {
    Image image;
    int imageWidth, imageHeight;
    int top = 0, left = 0;
    int width = 200, height = 200;

    ImageCanvas(Image img) {
        image = img;
    }

    // display/redisplay the image
    public void paint(Graphics g) {
        int x, y;
        ImageViewer parent = (ImageViewer) getParent();

        // find current scrollbar values
        x = parent.getHorizontalValue();
        y = parent.getVerticalValue();

        g.drawImage(image, -x, -y, this);

        // reset scrollbar values in case image changed
        // or resized
        imageHeight = image.getHeight(this);
        imageWidth = image.getWidth(this);
        height = this.size().height;
        width = this.size().width;

        parent.setHorizontalScrollbar(x, width, 0, imageWidth - width);
        parent.setVerticalScrollbar(y, height, 0, imageHeight - height);
    }

    // install a new image and display it
    public void setImage(Image img) {
        image = img;
        repaint();
    }
}

/**
 * a class to provide a scrollbar to manage an image display
 */
class ImageScrollbar extends SelectScrollbar {

    ImageCanvas canvas;

    ImageScrollbar(int o, int v, int vis, int min, int max, ImageCanvas c) {
        super(o, v, vis, min, max);
        canvas = c;
    }

    public boolean scrollLineUp(Event evt, Object what) {
        canvas.repaint();
        return true;
    }

    public boolean scrollLineDown(Event evt, Object what) {
        canvas.repaint();
        return true;
    }

    public boolean scrollPageUp(Event evt, Object what) {
        canvas.repaint();
        return true;
    }

    public boolean scrollPageDown(Event evt, Object what) {
        canvas.repaint();
        return true;
    }

    public boolean scrollAbsolute(Event evt, Object what) {
        canvas.repaint();
        return true;
    }
}


Listing 11.7. HTML to show Image Viewer.
<html>
<head>
<title> An Image Viewer </title>
</head>

<body>

<h1 align="center">An Image Viewer</h1>

<applet code="ImageViewerApplet.class" width=400 height=300>
Since your browser cannot run applets, this is what the application
looks like:
<br>
<img src="ImageViewer.gif" align="center">
</applet>

</body>

Major Surgery to the Event Model

The AWT event model has generated a lot of complaints from programmers. Mostly, these issues revolve around confusion about what is actually going on, with two event models, many bugs, and no clear statement about what events can be generated for each object class. These concerns are serious and can have a practical everyday effect; this chapter has attempted to resolve some of these issues.

If you step back a little from these immediate concerns, the AWT event model still shows fundamental problems. Consider the two major ones:

Just as the AWT event model is built above other event models, it is possible to build new event models above AWT. The following sections discuss two other models that have become publically available. The first is the awtExt package of Sal Cataudella. The second is the awtCommand class of Jan Newmarch, the author of this chapter.

The awtExt Package

The Xt and Motif toolkits for the X Window System use an event handling mechanism called callback functions. When a GUI object is created, so-called callback functions can be added to the object that gets executed when events occur in the object. For example, the Motif Pushbutton can have callback functions added that will be called on pressing the button down or on releasing it. Application code gets placed in these callback functions.

Basically, all that an Xt/Motif application has to do to add application code is to have the event handling mechanism execute these callback functions without requiring the application to do any event dispatching.

The awtExt package transports this idea into the Java realm. Each AWT GUI object gets subclassed by one which knows about callback methods. A callback method is an arbitrary method of an arbitrary class that can be attached to one of these new objects.

Because the awtExt package handles and dispatches events itself, you have no need for any overriding of handleEvent() or its convenience methods. Because each GUI object of the awtExt class can have callbacks attached, you don't need to create subclasses of the GUI objects just to hold application code, which solves the first set of problems in the AWT model.

Selecting and Showing Colors

Look at how the Colors.java program is done using this event handling package. The revised program, ExtColors.java, appears in Listing 11.8.


Listing 11.8. Colors using awtExt.
import java.awt.Frame;
import java.awt.Label;
import java.awt.Color;

import sysExt.*;
import awtExt.*;

public class ExtColors extends Frame {
    final Color colors[] = {Color.red, Color.blue, Color.green};
    final String colorLabels[] = {"red", "blue", "green"};
    Label label;

    public static void main(String argv[]) {
new ExtColors().show();
    }

    public ExtColors() {
List list = new List();
for (int n = 0; n  < colors.length; n++)
            list.addItem(colorLabels[n]);

        // add the callback method
        try {
            list.eventDispatch.LIST_SELECT =
                Callback.newRef(this, "listSelect");
        } catch(Exception e) {e.printStackTrace();}

        label = new Label("Hello World");

        // set geometry
        add("West", list);
        add("Center", label);
        resize(300, 100);    }

    public void setColor(int index) {
        label.setForeground(colors[index]);
    }

    public void listSelect(CallbackInfo cbi) {
        List list = (List) cbi.thisAwtObj;
        setColor(list.getSelectedIndex());
    }
}

The awtExt package defines a subclass of each of the standard AWT GUI objects, awtExt.Button, awtExt.List, etc. This reuse of names can be a little bit of a nuisance. Instead of importing all of java.awt and all of awtExt, you have to be more selective about which classes get imported, or use package-qualified names such as java.awt.Button. Anyway, this point is minor.

Ordinary AWT objects and awtExt objects may get mixed in the same program. The example uses objects from the java.awt package, such as java.awt.Frame, and objects from the awtExt package, such as awtExt.List.

The interesting part comes from the fact that you do not need to subclass List just to hold application code. Instead, a callback method is added to the awtExt list for this, which is done in the lines

list.eventDispatch.LIST_SELECT =
        Callback.newRef(this, "listSelect");

Consequently, the method this.listSelect() will be executed automatically when the List object receives a LIST_SELECT event. The newRef() method exploits a generally unknown part of Java, the capability to generate a method call from the name of the method. By passing in the String "listSelect", the sysExt package included as part of awtExt can later execute a call to the listSelect() method. The code has an exception handler around it because newRef() can throw an InvalidMethodRefException exception.

When the method listSelect() executes, it does so with a parameter of class CallbackInfo. CallbackInfo has public fields of the event and also of the awtExt object in which the event occurred. The awtExt object is used in the method to find the current index selected.

Basically, Frame doesn't need to override handleEvent(), and you haven't had to subclass List. Although this example seems fairly trivial, the technique scales well; even if you had thousands of objects, you would not have to override handleEvent() or subclass the GUI objects. Many very large Xt/Motif programs have been written using this type of event model.

A number of methods exist to install and manipulate callback methods. Many of them are convenience ones for cases where only one callback should occur (button with an ACTION_EVENT callback is one case). These areas can make the code look simpler.

Availability

The awtExt package is available from the Web page http://www.panix.com/~rangerx/packages.html.

The awtCommand Package

The other major issue in the AWT model is separation of GUI code from application code. This concept becomes important because an object whose purpose is to read in the contents of a file should not need to know anything about the FileDialog object that allowed the filename to be selected; a request to show a piece of information to the user should not be dealt with directly in terms of Dialog objects, but should be given to an object that can deal with such objects.

The support given by AWT for the separation between GUI code and general application code is fairly low. The majority of applets and applications available today have application code scattered throughout GUI code. Whenever a change is made to any part of the GUI interface, rewrites of the application code are often required. These changes are often minor: changing a GUI object's name or its path in the window tree. This setup also tends to promote the nearest equivalent to global variables: Every GUI object is known by a unique name to the top-level frame! For example, the following type of code is common:

public boolean action(Event evt, Object what) {
    String name = (String) what;

    if (name.equals("File"))
        if (fileDialog == null)
            fileDialog = new FileDialog(...);
        fileDialog.show();
        // etc
}

This process requires FileDialog to be an object known to this object. The code becomes fragile with respect to name clashes and other more subtle considerations-how would you translate this application to a French version?

Of course, you cannot completely separate the two: The application code does need to communicate with the GUI side after all! The GUI code, however, should not need to know the detailed internals of the application objects, and vice versa. Ideally, you should be able to swap a command-line interface for a Windows interface without changing any of the application objects.

AWT does not give direct support for separation. How about the awtExt package described in the preceding section? The example given was poor in one respect: It cast the application code into the Frame, often considered a bad thing; however, the example was small enough to get away with it. The awtExt package, in fact, gives some support for separation because any method of any object could have been used as the callback. For example, the callback could have been set as

list.eventDispatch.LIST_SELECT =
         Callback.newRef(new ApplicationObject(), "colorSelected");

to place the processing within the colorSelected() method of an ApplicationObject. This object may have a very limited knowledge of the user interface.

The Command Class Model

The awtExt package allows separation but does not enforce it. A more disciplined approach is to use an event model that enforces separation by default, which the Command class of the awtCommand package supplies.

The Command class model separates GUI objects from application behavior by placing application behavior in subclasses of Command objects. For example, when the application wants a file to be saved, it should call on the FileSaveCommand object to perform this action, instead of making GUI objects, such as a frame or a MenuItem, perform this task.

The book Design Patterns by Gamma, Helm, Johnson and Vlissides provides an excellent look above the language level to identify "good" patterns of usage and design of object classes. For event handling, it identifies this Command class as appropriate for this. Each GUI object has associated Command objects to handle the application code.

Each Command object has a method execute() that is invoked to perform this application-specific code. This object uses as little information as possible about its GUI environment to perform its tasks.

A GUI object from the awtCommand package does not perform application-specific code itself. It "installs" a Command object. When an event of interest to the GUI object occurs, the object invokes the execute() method on its Command object.

This capability allows Command objects to be written more or less independently of GUI objects. The implementation of both the GUI code and the application code can then be varied independently, as long as they use the same Command objects.

The Command Class

The Command class defines one abstract method execute(). This could be implemented either as an abstract class or as an interface. An application will be expected to have a fairly complex class structure of its own. An interface allows the Java "multiple inheritance" model to work well here, so Command is defined as an interface.

Each object has a set of events that it handles. For example, a List object will generate LIST_SELECT, LIST_DESELECT, and ACTION_EVENT events. There will be a (possibly) different Command object used to handle each of these. The LIST_SELECT event will be handled by a selectCommand object, the EVENT_ACTION event will be handled by an actionCommand object, etc.

The awtCommand package subclasses all of the relevant AWT classes. Each class is prefixed with "C" (really, the prefix should be "Command," but that is too verbose). So CList is a subclass of List, CFrame is a subclass of Frame, etc. Each of these classes has additional methods over the parent class to allow a Command object to be attached. These methods have names based on the event types that they handle.

In order to associate Command objects with awtCommand objects, a method sets the Command object for each event type. For example, CList has additional methods:

setSelectCommand(Command c)
setDeselectCommand(Command c)
setActionCommand(Command c)

When an event occurs for which a Command object has been registered, the awtCommand package invokes the following method of the Command object:

execute(Object target, Event evt, Object what)

The actual Command object is an instance of a subclass, which contains the application code in the execute method. The targetparameter is the object the event occurred in, and the what parameter is similar to the what parameter of component methods such as action().

If no Command object is registered for a particular type of event, then the original event processing is done. (For example, for component objects, the method handleEvent will pass the event to its parent in the GUI tree. For MenuComponent objects, the method postEvent will pass the event to its parent.) This setup allows the event-handling techniques of the AWT tookit to be still used if needed. For example, an AWT application will continue to work if all AWT objects are changed to awtCommand objects without other changes.

This allows several ways of writing applications using Command objects:

Selecting and Showing Colors

The following application is another variation of the program, which shows a list of colors next to a label. When one of the colors is selected, the label's foreground changes to that color. This is the program of Listing 11.4 adapted to use the Command class and is given as Listing 11.9.

A Command object is used to process the LIST_SELECT events. It is created and installed by

ColorCommand command = new ColorCommand(this, colors);
list.setSelectCommand(command);

Two parameters are passed through in the constructor: the list of colors and the top-level frame. The list of colors is passed so that the execute()method can later determine which color is selected. The frame is passed through in an attempt to minimize the amount of knowledge the Command object needs to have about the GUI side.

The "application code" here is fairly trivial-it just has to figure out what color was selected and then call back into the GUI code to set the color. Sufficient information is passed into the Command object's constructor and in the parameters to execute() to do all of this. The Command object knows very little about the structure of the GUI side, just calling on a method of the top-level frame to set the color.

To see the separation of application code from GUI code even in this simple example, consider the changes that would need to be made if the label was changed into a button. For the Command object, no changes would be needed at all. For the Frame object, the occurrences of the label would be changed into a button. More substantial changes, such as changing the color of a tree of windows, not just a single one, would also only need changes on the frame side.

On the other hand, changing from a List selection to a Menu selection would involve changes to the Command object because the execute() method can only examine the String name of the selected menu item. The changes are still relatively minor, involving adding String handling.


Listing 11.9. CommandColors.java.
import java.awt.*;
import java.awtCommand.*;

class CommandColors extends CFrame {
    final Color colors[] = {Color.red, Color.blue, Color.green};
    final String colorLabels[] = {"red", "blue", "green"};
    Label label;

    public static void main(String argv[]) {
        new CommandColors().show();
    }

    CommandColors() {
    // a CList showing the color choices
    CList list = new CList();
    for (int n = 0; n < colors.length; n++)
            list.addItem(colorLabels[n]);

        // set a Command invoked on button select
        ColorCommand command = new ColorCommand(this, colors);
        list.setSelectCommand(command);

        label = new Label("Hello World");

        // set geometry
        add("West", list);
        add("Center", label);
        resize(300, 100);
    }

    public void setColor(Color color) {
        label.setForeground(color);
    }
}

class ColorCommand implements Command {
    CommandColors app;
    Color colors[];

    // Constructor stores local info
    ColorCommand(CommandColors app, Color colors[]) {
        this.app = app;
        this.colors = colors;
    }

    public void execute(Object target, Event evt, Object what) {
        int index = ((Integer) what).intValue();

        app.setColor(colors[index]);
    }
}

Availability

The awtCommand package is available from http://pandonia.canberra.edu.au/java/ or by anonymous ftp from ftp://ftp.canberra.edu.au/pub/motif/command/.

Summary

AWT event handling is fraught with problems. Obvious bugs exist, and no clear specifications to resolve these issues have been created. Two event models exist with a vague promise that events will move from the old to the new model. Inconsistencies occur in event handling with missing methods, inappropriate defaults, and different handlers between component and MenuComponent objects.

The release of JDK 1.0 has stated that the libraries have basically been frozen-which is clearly a mistake as far as event handling is concerned. One can only hope that this part of JDK 1.0 will be allowed to evolve rapidly.

Apart from the bugs, most of these problems can be worked around. This chapter has shown how new subclasses can be defined that resolve many of these issues. It has also supplied much information that is needed by the AWT programmer to use the toolkit effectively.

The AWT event models do not directly support separation of view from the model. Two alternative event models have been built above AWT which try to fix this. They are both available in source code form, free to use in your own projects. They are written in Java so that they may be used in both applets and applications. Each model represent ways of handling events in large scale Java systems where the simple techniques used in current small systems will break down.