X Programmers will already be familiar with at least two different styles of event processing: the Xlib style has a single event loop which switches on event type and contains application code within each branch. On the other hand the Xt approach hides the raw event model under several layers and ends up associating user events to widget actions, and these actions invoke callback functions that contain the application code.
The AWT toolkit uses its own set of events, and generates them in response
to certain user actions by the method postEvent()
. The toolkit
supports both the Xlib and Xt methods of dealing with these events: they
can be left to a single event loop, or in most cases be dealt with
directly by the object receiving the event.
As discussed in the previous article, I prefer the object approach
but it is very common to see the single event loop approach.
Unfortunately the toolkit does not completely support the object approach.
This article continues the exploration of the AWT toolkit by examing the AWT event class in detail. Many of the details are only accessible by an examination of the source code, and I have used the beta-one source code release for this. The beta-two version has been released but this does not contain any of the Motif implementation files.
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. These follow the usual
practice of measuring from the top-left of the object.
The key
and modifiers
sometimes convey extra
information and are discussed in more detail later.
When objects share common characteristics, differing only in small ways,
one may create separate classes for each where each class is derived from
a common parent. Alternatively, one may use non-OO tricks, distinguishing
each by different values of a field. Which method is used depends on
the designer of the class(es).
For the Event type there are a large number of different events,
so if there was a separate class for each type it would lead
to a large number of derived classes which might be quite confusing. 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. In the X toolkits there is often a structure of widget-specific
information passed back to the callback function as the
call_data
parameter.
This type of capability
is present in the AWT Events through the arg
field.
When an Event is prepared the arg
field may be set to any
suitable object by the AWT object implementing postEvent()
.
It is not as rich in information as in Motif, but this doesn't really
matter - the entire object is accessible through the target
field anyway in addition to the rest of the Event information.
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.
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 |
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
it usually is not an ideal solution, and it is better to override the
method called after the switch. For example, override the method
action()
rather than look for ACTION_EVENT
in handleEvent
.
Unfortunately the preferred style of programming cannot always be used because
not all event types call their own methods. The following table lists
the methods called (or not called) by handleEvent
of
Component objects.
In particular, it can be observed that SCROLL_... events are not handled
at all, so it may be advisable to define a subclass of
Scrollbar
which handles these event types in its
handleEvent
, just like we had to do with Menu events
in the last article. The same applies to the various WINDOW... events.
Alternatively, one could lobby for methods to be added to the Component class...
Event Type | Method 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 |
id
field is used by the toolkit and also by the Java
programmer to distinguish between event types. Just as with X events,
different event types have different pieces of useful information.
For example, the ACTION_EVENT is generated after a Button has been selected.
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
this means that some fields have useful information but others do not.
Really, this is poor OO practice but is partly excusable: it avoids a
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.
The following table 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.
Event | Valid 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, modifiers |
MOUSE_EXIT | when, x, y, modifiers |
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 |
MenuItem
and CheckboxMenuItem
the fields
when
and modifiers
are also valid.)
There are some minor inconsistencies in these tables. For a GOT_FOCUS
or LOST_FOCUS event no additional fields of the event are set.
However, the methods gotFocus
and lostFocus
methods
have an extra parameter Object what
which turns out
to be just null
.
For a simple example using this, 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 is 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.
import java.util.*; import java.lang.*; import java.awt.*; class Chase extends Frame { static public void main(String argv[]) { new Chase(); } Chase() { Report r = new Report(); add("North", r); ChaseArea ca = new ChaseArea(r); add("Center", ca); resize(300, 300); show(); } } /** 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; } }
In addition to the ``ordinary'' characters, the Event class defines a number of constants for certain keys. These are
DOWN | END | HOME | LEFT | PGDN | PGUP | RIGHT | UP | F1 | ... | F12 |
These can be used in code as
if (evt.key == Event.HOME) ...
modifiers
field is
valid. This is a bitmask of values, where zero means no mask. The possible
masks are
ALT_MASK | CTRL_MASK | META_MASK | SHIFT_MASK |
For key events they have the expected values - for example,
the META
key on a Sun is the `diamond'' key.
For mouse events, they play an unusual role for X, 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 SHIFT_MASK
the button selected is the middle button; with a modifier value of
CTRL_MASK
the button selected is the right button.
(Strictly, these are Button1, Button2 and Button3 respectively.)
arg
value in an event is similar to the call_data
field in Xt callbacks - it carries additional information supplied by the
toolkit about the context in which the event occurred. In Motif the
call_data
structures are often very rich in information.
The AWT event type contains the field arg
which is used for
similar purposes. However, it is much weaker in 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
).
The following table lists the value of arg
for
events of type ACTION_EVENT
Object | Value | Type |
---|---|---|
Button | getLabel() | String |
Checkbox | Boolean(getState()) | Boolean |
CheckboxMenuItem | getLabel() | String |
Choice | getSelectedItem() | String |
List | getSelectedItem | String |
MenuItem | getLabel() | String |
TextField | getText() | String |
arg
value, of type Integer
.
These values are all the new slider location value.
arg
value, of type Integer
, which is the
index selected or deselected.
xev
, that shows
information about X events occurring within it. Of course, this one
does not show X events but AWT Event information.
import java.awt.*; public class EventTest extends Frame { public static void main(String argv[]) { new EventTest(); } EventTest() { add("North", new Label("Hello World")); add("South", new Button("Hello too")); resize(200, 200); show(); } public boolean handleEvent(Event evt) { System.out.println(evt.toString()); return true; } }
To see the output from the Applet in Netscape, you will need to enable the Java console window from the Preferences menu.
The program prints constant integer values (such as id
)
in their literal rather than symbolic form. To interpret these you may
need to have the Event.java
source code handy.
You can vary the program by choosing different widgets - in fact this is quite instructive, because even this program shows a number of bugs in the AWT toolkit, as discussed later.
The following table lists the Motif actions, or the Xlib Protocol messages and how they eventually generate AWT events (the implementation details are discussed later). The first column lists the AWT classes. The second column lists the Motif widget which handles the action/event for the AWT object. There may be more than one Motif widget involved in this because many of the AWT objects are implemented uisng a number of Motif widgets.
The third column lists the Motif callback that triggers AWT event generation. You could try playing with translation tables if you want to affect what Xlib events call what actions if you want - I suspect it will work now but not in future versions of the toolkit!
The fourth column lists what goes on in the Motif implementation by Peer objects. If you only want to use the toolkit and don't want to know implementation details then please ignore this column. To tell the truth, I only put this column in so that I could document the route I am going to have to follow on the next release of the toolkit!
The fifth column lists the final AWT event generated. Some Motif actions
end up generating no event at all because they are handled entirely by the
toolkit. For example the handleResize()
method of
Frame
's peer calls repaint()
which does all the
necessary work without requiring further event processing.
On the other hand, it is indicative of a bug
that WM_DELETE_WINDOW
for
Dialog
generates an event whereas the same event for
FileDialog
doesn't.
Object | Motif Widget | Motif Action/Event | Peer Method | AWT Event |
---|---|---|---|---|
Button | PushButton | activateCallback | action() | ACTION_EVENT |
Checkbox | ToggleButton | valueChangedCallback | action(state) | ACTION_EVENT |
CheckboxMenuItem | ToggleButton | valueChangedCallback | action(when, modifiers, state) | ACTION_EVENT |
Choice | PushButton | activateCallback | action(index) | ACTION_EVENT |
Dialog | DrawingArea | resizeCallback | handleResize(width, height) | none |
PopupShell | WM_DELETE_WINDOW | handleQuit() | WINDOW_DESTROY | |
MapNotify | handleDeiconify() | WINDOW_DEICONIFY | ||
UnmapNotify | handleIconify | WINDOW_ICONIFY | ||
ConfigureNotify | handleMoved(x, y) | WINDOW_MOVED | ||
FileDialog | PopupShell | WM_DELETE_WINDOW | handleQuit() | none |
FileSelectionBox | okCallback | handleSelected(file) | none | |
cancelCallback | handleCancel() | none | ||
Frame | DrawingArea | resizeCallback | handleResize(width, height) | none |
PopupShell | WM_DELETE_WINDOW | handleQuit() | WINDOW_DESTROY | |
MapNotify | handleDeiconify() | WINDOW_DEICONIFY | ||
UnmapNotify | handleIconify() | WINDOW_ICONIFY | ||
ConfigureNotify | handleMoved(x, y,) | WINDOW_MOVED | ||
List | List | defaultActionCallback | action(item_position) | ACTION_EVENT |
singleSelectionCallback | handleListChanged(item_position) | LIST_SELECT or | ||
LIST_DESELECT | ||||
multipleSelectionCallback | handleListChanged(item_position) | LIST_SELECT or | ||
LIST_DESELECT | ||||
MenuItem | PushButton | activateCallback | action(when, modifiers) | ACTION_EVENT |
Scrollbar | Scrollbar | decrementCallback | lineUp(value) | SCROLL_LINE_UP |
incrementCallback | lineDown(value) | SCROLL_LINE_DOWN | ||
pageDecrementCallback | pageUp(value) | SCROLL_PAGE_UP | ||
pageIncrementCallback | pageDown(value) | SCROLL_PAGE_DOWN | ||
dragCallback | dragAbsolute(value) | SCROLL_ABSOLUTE | ||
TextArea | Text | focusCallback | gotFocus() | GOT_FOCUS |
losingFocusCallback | lostFocus() | LOST_FOCUS | ||
TextField | TextField | activateCallback | action() | ACTION_EVENT |
focusCallback | gotFocus() | GOT_FOCUS | ||
losingFocusCallback | lostFocus() | LOST_FOCUS | ||
Window | PopupShell | MapNotify | handleDeiconify() | WINDOW_DEICONIFY |
UnmapNotify | handleIconify() | WINDOWICONIFY |
In addition to these, objects of type Canvas (and hence Panel), Dialog, Frame and Window all use a Motif DrawingArea which has an EventHandler added for a wide set of events. These also generate events according to this table
Xlib Event | Peer Method | AWT Event |
---|---|---|
Graphics Expose | handleRepaint(x, y, width, height) | none |
Expose | handleRepaint(x, y, width, height) | none |
FocusIn | gotFocus() | GOT_FOCUS |
FocusOut | lostFocus() | LOST_FOCUS |
ButtonPress | handleMouseDown(time, data, x, y, xroot, yroot, clickCount, modifiers) | MOUSE_DOWN |
ButtonRelease | handleMouseDown(time, data, x, y, modifiers ) | MOUSE_UP |
KeyPress | handleKeyPress(time, data, x, y, key, modifiers) | KEY_PRESS |
handleActionKeyPress(time, data, x, y, key, modifiers) | KEY_ACTION | |
KeyRelease | handleKeyRelease(time, data, x, y, key, modifiers) | KEY_RELEASE |
handleActionKeyRelease(time, data, x, y, key, modifiers ) | KEY_ACTION_RELEASE | |
MotionNotify | handleMouseDrag(time, data, x, y, xroot, yroot, modifiers) | MOUSE_DRAG |
handleMouseMoved(time, data, x, y, xroot, yroot, modifiers) | MOUSE_MOVE | |
EnterNotify | handleMouseEnter(time, x, y) | MOUSE_ENTER |
LeaveNotify | handleMouseExit(time, x, y) | MOUSE_EXIT |
valueChangedCallback
which is called
from the ArmAndActivate()
, BtnUp()
and
Select()
actions. (See the Motif Programmer's Reference
Manual for this kind of detail.) The translation tables for the
ToggleButton tell how the user can invoke these actions.
This is the ``native'' side of the AWT implementation.
The event generated by the AWT toolkit is an ACTION_EVENT (Table 7) with valid
field arg
(Table 3).
The value of arg
for the Checkbox
object is the boolean value equivalent to calling the method
getState()
on the object (Table 6).
For ACTION_EVENT's the method
action
is called to handle the event, so it is only
necessary to override this method in your application (Table 2).
class MyCheckBox extends Checkbox { public boolean action(Event evt, Object what) { Boolean state = (Boolean) what; if (state.booleanValue()) { System.out.println("Selected"); } else { System.out.println("Deselected"); } return true; } }
action()
is invoked for the Button. How is this done?
Well, there are layers of code here: Xlib, Motif and AWT. By the time
you get through this section, you will appreciate why computers need to
be as fast as they are in order to not appear more sluggish than they
often do!
Xlib directs events to Windows. A low-level Xlib program will sit in an event loop catching events and dispatching them based on event type and the window they occurred in.
Xt sits on top of this, catching events and performing its own event dispatch. This dispatch is to widgets, and goes through a number of complex stages. Once the Xt toolkit has decided which widget the event belongs to it uses the widget's translation table to decide what widget action to map it onto. From there it uses the widget's action table to decide on the C action function to execute. Within the C action the widget implementation will execute all of the callbacks on its callback list. (This ignores the extra complexity caused by the Motif Focus mechanism which puts in another processing level.)
We now enter the realms of the AWT implementation above Motif.
The Peer objects are responsible for creation of Motif widgets at
suitable times. This is done using native C functions with gruesome names
like sun_awt_motif_MTextFieldPeer_create
- meaning the
create
method of the MTextFieldPeer
object
within package sun.awt.motif
. These creation functions
will also add callback functions (or protocol handlers) such as
TextField_focusOut
, which will be executed as normal
Xt callback functions.
When one of these callback functions executes, it makes a dynamic call back into the Java system, specifying the object, the method of that object and the arguments to that method. The Java runtime is responsible for creating the appropriate method call for the object.
This is a general-purpose mechanism for native code to invoke Java methods. The particular object specified for all of the AWT objects is the Peer object. The AWT TextField object is implemented using a Motif TextField widget. The object specified in this dynamic Java call is the Peer object of type TextFieldPeer - actually of type MTextFieldPeer, since this is implementing the Motif binding.
The method called in the Peer object prepares a new Event
based on information contained in this dynamic call and then calls
postEvent
for the object that triggered this whole process.
eg postEvent
is called for the AWT TextField object.
The route from there belongs within the AWT toolkit. For objects of
type Component handleEvent
is called on the object, its
parent, its grandparent (in the widget tree) until one of the methods
returns true
.
There are still bugs in the AWT toolkit, even in the JDK 1.0 release. This shows up (at least) in differing behaviour between the Motif version and the Windows95 version. The existing documentation does not make it clear which behaviour is expected, but I believe that both are wrong in some cases.
The problems show up on some quite trivial programs. Consider a Frame with just a Label in it. In the Frame area mouse motion events occur, and mouse up and down events. Key up and key down events also occur. These are all odd behaviour for something that has container semantics but not - I would have thought - input semantics. The behaviour is identical under X and Windows95 so would appear to be intentional. Under X it is achieved by adding event handlers to the Frame's DrawingArea widget.
For the Label, the X implementation does not get any mouse motion or up/down events, but does get key up/down events. Again, these inputs are odd. Under Windows95 there is a difference - the Label also gets mouse motion events.
If we add a Button to this arrangement (now it is like the event-printing program earlier) the Button also shows a difference between X and Windows95: under X it does not get mouse motion but under Windows95 it does. Under both, key up/down events are received.
The next two articles will address an area that has also received short-shift in documentation: geometry management. The next article will deal with the general principles behind geometry management and the simpler managers. The second will deal with the GridbagLayout class which is a very general manager with the regrettable complexity of Motif's Form, but an equivalent flexibility in allowing very complex arrangements.