Background
Interactors provide a model for event-handling
that allows a programmer to organize events into high-level behaviors
rather than treating them as a series of low-level mouse and keyboard
events. The original interactors model was based on the observation
that most events in graphical user interfaces can be categorized into
one of a small number of behaviors that include:
- Selection from a group of choices
- Moving or resizing an object
- Editing a text string
- Creating a new object
- Rotating an object
- Sketching an object
This observation allowed Brad Myers, the creator of the interactors
model, to create a set of behavior classes that contained a rich
set of parameters that a user could set to customize the behavior
without having to do any programming. For example, he devised a
class for a move behavior that had parameters which among other
things controlled which events started and stopped the behavior,
which objects the behavior operated on, which coordinates were set
(just the x, just the y, or both), whether the object being moved
was or was not snapped to a grid, and whether the
object itself was moved as interim feedback or whether an interim
feedback object was used instead.
Design Strategy
Interactors are implemented using the strategy pattern.
- Each behavior is encapsulated in a class.
- The class typically contains a set of properties that allow the
user to customize the behavior without performing any programming.
- Since the imagination of the programmer will almost always exceed
that of the designer, the classes also typically contain methods
that a programmer can override and which will be called as the
behavior executes. Typically a behavior has the following six
methods:
- Start method: Called when the behavior is just starting.
- Running method: Called each time an event is received as the
behavior executes.
- Stop method: Called when the behavior is finishing.
- Abort method: Called if the behavior is aborted.
- Outside method: Called if the mouse cursor moves outside the
behavior's operating region. For example, this method might
be called if the mouse cursor moves outside the window.
- BackInside method: Called if the mouse cursor moves back inside
the behavior's operating region after being outside it.
Determining which Method Gets Called
A behavior determines which method to call based on the type of
event that it receives. Events can be divided into four types:
- Start event(s): The event(s) that initiate the behavior.
- Running events: The events that cause the behavior to perform
some action as it is running. A behavior typically is not
interested in all events as it runs. For example, a move
behavior wants to know about drag events but probably does
not care about keyboard events.
- Stop event(s): The event(s) that cause the behavior to terminate.
- Abort event: The event that causes the behavior to abort and
return the interface to its original state before the behavior
started.
Additionally a behavior may have an operating region. If the mouse
cursor moves outside this region, then the outside action should be
called and should continue to be called as long as running events are
received while the cursor is outside the region. When the cursor
re-enters the region, the back inside action should be called.
Objects On Which An Interactor Operates
There are three possible ways in which a designer of an interactors class
might have the programmer specify the set of objects on which the interactor
operates:
- Attach interactors to objects: This design requires that each object
provide an addInteractors method. The object could either keep a list
of the interactors attached to it or notify an interactors object of
the addition so that the interactors object could maintain the list.
- Attach objects to interactors: This design requires that each interactor
provide an addObject method. The method could either add the object to the
list of objects handled by this interactor, or alternatively, maintain
a hash table of objects and their associated interactors and add the
interactor to the appropriate list.
- Provide descriptive parameters that specify the type of objects
on which the interactor should operate. For example, a parameter might
state that the interactor should operate on all objects in a
container or all rectangles in a container. The designer might
also provide a "custom" parameter that would allow the programmer
to write a method that takes an object as a parameter and returns
true or false to indicate whether the behavior should operate on
the object.
The first option matches the way users attach event listeners to Swing
components and hence seems most natural for Java implementations. However
the other designs might work well in other languages. Additionally, the
descriptive parameter approach can provide a powerful way to specify sets
of objects without forcing the programmer to remember to associate an
object with an interactor each time an object is created.
Protocol with Graphical Objects
An interactor may either directly modify the objects on which it operates
or allow the programmer to determine how to modify the objects by overriding
one or more of the interactor's action methods. Typically the interactor
will provide a property that allows the programmer to specify how the
graphical objects should be modified. For example, a move interactor might
provide a box_xy and a line_xy property to allow the
programmer to specify which coordinates should be changed by the interactor
(e.g., X, Y, Both, or Custom). If the X, Y, or
Both options were supplied, then the interactor would directly
set the corresponding properties in the object it is operating on.
If the Custom option was specified, then the interactor would
allow the programmer to perform the modifications using the start, running,
and stop actions.
In order to modify an object's properties the interactor needs to know
which methods it can use to perform the modifications. Hence the interactor
will typically define an interface that specifies the set of methods that
objects on which it operates needs to implement. For example, a move interactor
would need to have get and set methods
for the left and top properties in order to work on
box-like objects and it would need to have get and set methods
for the x1, y1, x2, and y2 properties in order to work
on line-like objects.
Exclusivity and Priority Levels
Interactors can either be exclusive, in which case they will be the only
interactor executing, or non-exclusive, in which case they can run in
parallel with other interactors. If a non-exclusive interactor is running
then an exclusive interactor should not start running since it would assume
that it was the only interactor running and problems might arise.
Since interactors might vie for the same event, and since interactors
can prevent others from running depending on their exclusivity property,
it often helps to give interactors priorities. Interactors with higher
priorities will receive events first and have the ability to act on them.
If an interactor is a "one-shot" interactor that starts and stops on the
same event, then the interactor might complete its processing and allow
the event to be passed to any other interested interactors. Alternatively
the interactor might want to continue its execution, in which case lower
priority interactors might not get a chance to act on the event.
Implementation of Interactors
Three tasks must be accomplished in order to implement an interactors
model:
- Implement a finite state machine for each interactor
- Implement a Point-in-Obj method for each object that takes
a point and returns true/false depending on whether or not
the point lies within that object.
- Implement an event-handler that takes events as input and
assigns them to the appropriate interactor.
Finite State Machine
Each interactor implements the same type of finite state machine, although
each interactor has its own private copy of the finite state machine. Since
every finite state machine is implemented using the same code, the finite
state machine code should be placed in the root class for interactors.
The finite state machine method should implement the following
state table:
Current State | Event | Action to Execute | New State |
Start | Start Evt | Start Action | Running |
Running | Stop Evt | Stop Action | Start |
Running | Running Evt | Running Action | Running |
Running | Abort Evt | Abort Action | Start |
Running | Outside Operating Region | Outside Action | Outside |
Outside | Running Event and Outside Operating Region | Outside Action | Outside |
Outside | Back Inside Operating Region | Back Inside Action | Running |
Outside | Stop Evt | Stop Action | Start |
Outside | Abort Evt | Abort Action | Start |
Event Handler
As noted earlier, the event handler needs to receive events from the
window manager and dispatch them to the correct interactor. In order
to receive events from the window manager it may be necessary to
attach the event handler to a window. For example, in Java one would
create mouse and keyboard listeners whose methods called the interactors'
event handler. The event handler would presumably be a method in a top-level
interactors object. It might be a static method in the interactors root
class or it might be contained in an interactors' implementation class, in
which case the appropriate implementation object would need to receive the
call.
The event handler should implement the following algorithm:
- If an interactor(s) is already running, give the event to that
interactor(s) (call its finite state machine method). Once the
interactor finishes its event processing, call the display
manager to update the display.
- If an interactor is not running, find the object that contains
the mouse cursor.
For each interactor associated with that object determine if
its start event matches the input event (each interactor
might have a query method that takes the event and returns
true or false depending on whether the event is the start
event).
If one of the interactors' start events matches the event,
call the interactor's finite state machine method,
passing the event and object
as parameters. Save the object so that it is available on
subsequent iterations, and save the interactor, so that
we remember that there is an interactor that is running.
Once the
interactor finishes its event processing, call the display
manager to update the display.
- When an interactor's stop event is received, remove the
interactor from the list of executing interactors.
Action Methods
The action methods all do some generic stuff:
- Start Action
- Save any information that may be altered in an object. This
will allow the application to restore this information if
something goes wrong.
- Compute the appropriate information
- Store the information in either the interim feedback object
or the object to be changed
- Make the interim feedback object visible (if it exists).
- RunningAction
- Compute the appropriate information
- Store the information in either the interim feedback object
or the object to be changed
- Stop Action
- Compute the appropriate information
- Store the information in the object to be changed
- Make the interim feedback object invisible
- Call the final function, if there is one.
Advantages and Disadvantages of Interactors
The interactors model has both a number of advantages and a number
of disadvantages. The advantages include:
- Creates reusable behaviors: Behaviors are separated from
graphics, so either can be
changed without affecting the other. This means that
you get the separation envisioned in the MVC model because
the behaviors represent the controller and the graphics
represent the view. Thus a programmer may be able to
create a library of behaviors that can be used in multiple
applications
- Reduces bookkeeping: The interactors runtime environment
implements the "traffic cop" code that handles the
distribution of events to event handlers and that handles
contention for events among event handlers. It also keeps
track of the current state of an interactor (e.g., whether it
is in a start, stop, running, or outside state).
- Aggregates code in one place: All the code for an interactor
can be placed in the same class, whereas in Java's event
listener model, it may be necessary to distribute the
code across multiple event listeners. Conversely, code becomes
more readable because the code for multiple behaviors
does not have to be jammed into a single listener in order
to facilitate traffic control among the behaviors.
The disadvantages include:
- The interactors model does not mimic the event listener model
of languages like Java or operating systems like Windows. However,
interactors are supposed to be a higher level behavioral model
so that is why they do not exactly mimic the listeners. The
drawback though is that there is a longer learning curve.
- In languages that do not have good module mechanisms, like C++,
the interactors will probably need to be friends of the
graphical objects, so they can query and set parameters
in the graphical objects. The friend mechanism in C++ can be
rather cumbersome to use