Command
class of the
book "Design Patterns" by Gamma, Helm, Johnson and Vlissides.
The library subclasses each of the AWT objects to add a
Command
object
to carry application-specific code.
The Command
class is an interface
class that must be implemented by application code.
Command
objects
can be added to windows objects and their execute
method is called each time an event is invoked within the windows object.
An application will implement this class to provide application code
separated from the GUI objects.
Version 1.0 works with the beta-one awt toolkit. Version 2.0 works with the JDK 1.0 toolkit (Sun changed the specification of postEvent between versions).
Button
generates an
ACTION_EVENT
. Applications or applets that use the AWT
toolkit have to respond in an event driven manner to these
events i.e. there must be a mechanism 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, Xlib allows one to catch each event and branch on event type. Then application-specific code is invoked. Windows acts in a similar way. This is a strictly procedural approach.
Motif and other Xt-based toolkits allow application-specific code to be attached to callback functions. This is more OO in approach in that it attaches application code to the applcation objects. However it does suffer flaws in that it tightly couples GUI objects to application code. This makes it harder to separate view from model. It also makes code organisation quite messy, with application code being easily mixed up with GUI code.
The AWT toolkit is an OO toolkit - how does it fare for event processing?
While it is a superbly crafted toolkit in many ways, it seems to have
adopted a weak OO approach in event processing.
An object will generate an event using the postEvent()
method.
The default event
processing is that each object ``passes the buck'' by ignoring the
event and sending it to its parent in the window hierarchy. It does this
in two different ways: a Component object gets the event by
handleEvent()
and passes it up explicitly; a MenuComponent
object passes it up to a non-MenuComponent in postEvent()
.
This leads to two common ways of handling events: leave them all to the
top-level Frame
or Applet
to manage from
handleEvent
or some of the convenience methods, or to
subclass each object that generates an event and let them manage
their own events.
The first of these leads to application code lying in the
Frame
(or Applet
),
and the Frame
(or Applet
) must be aware of all the
details (such as object names) for all of the GUI elements. This gives
a very rigid and inflexible design.
The second method leads to a large number of classes each with a single member that carries the application code. While this is better OO, it still ties the application code fairly closely to the GUI objects.
Command
objects.
For example, when the application wishes 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
do this.
Each Command object has a method execute
that can be invoked
to perform this application-specific code.
A GUI object does not perform application-specific code itself. What it does is to ``install'' a Command object. When an event of interest to the GUI object occurs it invokes the execute method on its Command object.
This 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.
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 will handle. 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 to be associated with
awtCommand objects, there is a method to set 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 method
execute(Object target, Event evt, Object what)of the
Command
object.
The actual Command
object will be an instance of a subclass
which contains the application code in the execute
method.
If there is no Command
object registered for a particular
type of event, then the original event processing is done i.e.
for Component
objects the method handleEvent
will pass the event to its parent in the GUI tree, while for
MenuComponent
objects the method postEvent
will pass the event to its parent.
This 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:
Command
objects to the GUI objects that generate
events. This will be the most common use.
Command
objects to ancestors of the GUI
objects. This may be appropriate if Command
objects
are shared by many GUI objects. For example, a Command
attached to a CMenu
could handle all the events from
CMenuItem
children.
CButton
in a
Frame
(a CFrame
would do just as well).
When the CButton
is activated, the execute
method of the associated Command
object is run.
This justs prints some information.
import java.awt.*; import java.awtCommand.*; public class HelloWorld extends Frame { public static void main(String argv[]) { new HelloWorld().show(); } HelloWorld() { CButton b = new CButton("Hello World"); b.setActionCommand(new HelloCommand()); add("Center", b); resize(100, 100); } } class HelloCommand implements Command { public void execute(Object target, Event evt, Object what) { System.out.println(target + " " + evt); } }
execute
parametersexecute
method is called with three parameters
Object target
execute
method
e.g. a CList
object.
Event evt
Object what
CButton
with the Command
object being the actionCommand
, what
is the
CButton
's label.
For a CList
with the Command
object being the
selectCommand
, what
is the index of the
item in the CList
that was selected.
Command
object can be used for two distinct user
actions.
The application shows a list of colors next to a label.
When one of the colors is selected the label's foreground
is changed to that color.
Items in the list can be chosen in two ways: by selection with the
left mouse button or by using the up/down keys to traverse the
list with selection by pressing the Return key.
These generate different events, a LIST_SELECT and an ACTION_EVENT.
A single Command
is used to handle both of these by
using it as the actionCommand
and as the
selectCommand
.
When the execute
method runs, the what
field
is used to find the selected data. This may be of different types,
though. From the ACTION_EVENT, the value is the item selected as a
String
. From the LIST_SELECT the value is the index of
the item selected as an Integer
. A runtime test distinguishes
between them.
import java.awt.*; import java.awtCommand.*; class ColorList 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 ColorList().show(); } ColorList() { // a CList showing the color choices CList list = new CList(); for (int n = 0; n < colors.length; n++) list.addItem(colorLabels[n]); // a Command invoked on button select and Return key ColorCommand command = new ColorCommand(this, colorLabels); list.setSelectCommand(command); list.setActionCommand(command); label = new Label("Hello World"); // set geometry add("West", list); add("Center", label); resize(300, 100); } public void setColor(String color) { int n; // search for Color matching string for (n = 0; n < colorLabels.length; n++) { if (colorLabels[n].equals(color)) { break; } } label.setForeground(colors[n]); } } class ColorCommand implements Command { ColorList app; String colorLabels[]; // Constructor stores local info ColorCommand(ColorList app, String colorLabels[]) { this.app = app; this.colorLabels = colorLabels; } public void execute(Object target, Event evt, Object what) { if (what instanceof String) { // got here as actionCommand String color = (String) what; app.setColor(color); } else { // got here as selectCommand int index = ((Integer) what).intValue(); app.setColor(colorLabels[index]); } } }
handleEvent()
for that component, and then successively
to each parent (in the GUI tree). This chain would stop when either th
chain ran out, or (more likely) a component would handle an event
and then return true
from handleEvent()
to signal this. Clean, and simple.
JDK 1.0 really muddied this, for a reason.
In a TextComponent, what was typed may not be what the application wants to see appear in the TextComponent. For example the application may want to convert all characters typed to lower-case. Or it may want to change all characters to an asterix for password entry (TextField has a method just for this - too specialised). Or it may want to discard characters, or replace them by a sequence as in macro expansion. These are examples of why an application may want to change the event presented to a different one.
Events are passed to the Component that generated them, and up through the widget tree. If they pop out of the top of this, then they are fed back into the object via its peer (the peer allows platform-specific code to execute). Right now, this only tries to do things with key events: trap them before they get to the native GUI object, send them through the application code and then feed the (possibly modified) event back into the native GUI object. (This solves the first two example uses, not the third or fourth.)
Several points
handleEvent()
must never return true
,
only false
.
This leads to some stupid looking code, with boolean values of false
being passed around and tested for (always failing, of course).
Methods such as handleEvent()
and action()
should be void, to avoid the temptation to get it wrong,
and to avoid this silly code.
false
from handleEvent()
.
The examples above are unchanged
between version 1.0 and 2.0. However, version 2.0 now allows a sequence
of objects to handle the event: any of the objects in the GUI tree
can handle the event, but can't stop others from handling it too.
This is like the "Chain of Responsibility" pattern in Gamma,
et al,
but without the stopping possibility. I am not sure I like this
change, but the new event model forces it.
There are several instances of code duplication in the awtCommand caused by these mechanisms, which are very definitively not code reuse. These occur in the common code used in CCanvas, CDialog, CFrame, CPanel and CWindow. They all handle a large common set of events.
The awt toolkit does not use code duplication.
How this is done is intriguing: (under X) they all create their native
window windows by calling a C function awt_canvas_create
that creates a DrawingArea and installs event handlers for events.
Code is shared by calling a shared function that lives in its own
file, canvas.c.
Code sharing is performed at the native level by calling a
common global function.
In the awtCommand toolkit, CCanvas
is a subclass of
Canvas
, CDialog
is a subclass of
Dialog
, etc. They can only inherit methods from their
superclasses. But they all need to implement a large number of methods
such as mouseUp
, scrollAbsolute
,
action
, etc, as well as the corresponding methods to
set these command objects.
Multiple inheritance would certainly be a means of sharing code.
This is a rather heavyweight mechanism, though.
A simple #include
mechanism would be more than adequate
to solve the code sharing problems encountered in building this toolkit.
An alternative way is to use an associated object, much like the AWT
toolkit uses peer objects. Jean-Michel Leon
So what I do now is to implement the event handling that the Motif version
implements, except when it seems odd. So the toolkit doesn't handle key
events for TextArea (although Windows95 seems to generate them) because
Motif doesn't. On the other hand, all mouse and key events can only be
handled by Frame, Canvas, Dialog and Window because I don't know where
they should be handled.
So there are discrepancies in event handling on different platforms
for the awt toolkit, and discrepancies between when the awt toolkit
handles events and when this toolkit does. I guess that amounts to at
least one bug. I will fix things in the awtCommand library when I know what
the official line is.
What started me thinking about this was the awtExt package of
Sal Cataudella (http://www.panix.com/~rangerx/)
This implements an Xt callback model for event handling.
Much of the detail of event handling comes from an article I have written
for publication in the Internet journal, the
X Advisor
to appear in the March, 1996 edition.
Status
This library is version 2.0, released in March 1996. It uses the
AWT JDK 1.0 release. Version 1.0 of the library used the beta-one
version of the AWT release.
Bugs
The AWT beta toolkits do weird things like generate key up/down and
mouse motion events for Label objects. Some of these weird behaviours
are different under X and Windows95. Other odd behaviour includes
no key event generation for TextArea or TextField (under X). I don't
know what the intent really is for some of the event generation in
the toolkit. There does not appear to be an implementation-independent
specification of intended behaviour that is sufficiently detailed to
work out what should happen.
Acknowledgments
The idea for this comes straight from the book
``Design Patterns'' by Gamma, Helm, Johnson and Vlissides published by
Addison-Wesley, ISBN 0-201-63361-2. They discuss the Command
pattern to manage user actions. This book is well worth reading.