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:

  1. Selection from a group of choices
  2. Moving or resizing an object
  3. Editing a text string
  4. Creating a new object
  5. Rotating an object
  6. 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.

  1. Each behavior is encapsulated in a class.

  2. The class typically contains a set of properties that allow the user to customize the behavior without performing any programming.

  3. 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:

    1. Start method: Called when the behavior is just starting.
    2. Running method: Called each time an event is received as the behavior executes.
    3. Stop method: Called when the behavior is finishing.
    4. Abort method: Called if the behavior is aborted.
    5. 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.
    6. 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:

  1. Start event(s): The event(s) that initiate the behavior.
  2. 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.
  3. Stop event(s): The event(s) that cause the behavior to terminate.
  4. 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:

  1. 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.

  2. 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.

  3. 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:

  1. Implement a finite state machine for each interactor
  2. 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.
  3. 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 StateEventAction to ExecuteNew State
StartStart EvtStart ActionRunning
RunningStop EvtStop ActionStart
RunningRunning EvtRunning ActionRunning
RunningAbort EvtAbort ActionStart
RunningOutside Operating RegionOutside ActionOutside
OutsideRunning Event and Outside Operating RegionOutside ActionOutside
OutsideBack Inside Operating RegionBack Inside ActionRunning
OutsideStop EvtStop ActionStart
OutsideAbort EvtAbort ActionStart

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:

  1. 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.

  2. 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.

  3. 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:

  1. Start Action

  2. RunningAction

  3. Stop Action


Advantages and Disadvantages of Interactors

The interactors model has both a number of advantages and a number of disadvantages. The advantages include:

  1. Creates reusable behaviors: A programmer may be able to create a library of behaviors that can be used in multiple applications

  2. The same behavior can be applied to multiple types of objects, but only one instance of the behavior object is needed.

  3. 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.

The disadvantages include:

  1. 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.

  2. 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