<* PRAGMA LL *>The
EventSync interface makes it possible to program FormsVBT
graphical user interfaces procedurally, without using callbacks.
Instead of a web of event handlers and interacting state machines,
the programmer creates one or more threads. Each thread handles
a more-or-less independent portion of the user interface, in a
sequential, procedural style. For many programmers, that kind
of style is preferred, because it is more intuitive to them.
The principal idea behind this approach is as follows. Most user interfaces allow several interleaved interactions to take place simultaneously. For example, in the midst of operating a file browser, the user may decide to raise the help window. Generally, such user interfaces can be decomposed into a number of relatively independent sub-GUIs, each of which follows a strictly sequential flow of control. The programmer implements each such sub-GUI as a separate thread, which, for the most part, operates independently of the other sub-GUI threads. Within each thread, the programming is done in the familiar procedural style, to wit: wait for any of several legal user interactions to take place, then do something, based on the particular interaction the user chose.
The EventSync interface supports the above style of programming.
It provides the necessary synchronization between GUI-generated
events and the programmer's sequential threads of control. All
events are handled behind the scenes; the programmer need not
be aware of them at all. In addition, EventSync conveniently
manages FormsVBT Filter components, enabling and disabling
them automatically at the appropriate times.
INTERFACEEventSync ; IMPORT AnyEvent, FormsVBT, Thread; EXCEPTION Error(TEXT); PROCEDURE DetailWait(fv: FormsVBT.T; names: TEXT; VAR event: AnyEvent.T): CARDINAL RAISES {Error, FormsVBT.Error, Thread.Alerted}; <* LL.sup < VBT.mu *>
Activate the components specified innames, then wait for an event from one of them. Pass the event back viaevent, and return an identifying value corresponding to the component that triggered the event.
The
names parameter specifies the set of FormsVBT interactors from
which the user is allowed to choose. The wait ends when the user
activates one of them. Here is an example of what names typically
looks like:
"quitButton=0 retryButton=1 resetButton=2"
Here, quitButton, retryButton, and resetButton are the names of
FormsVBT components. The numbers after the equal signs serve to
identify which component was activated to end the wait. In this
example, if the user pressed the reset button, DetailWait would
return the value 2. The identifying numbers are arbitrary
non-negative integers. They need not be sequential or unique.
Continuing this example, the call to DetailWait might look like
this:
CASE DetailWait(fv, "quitButton=0 retryButton=1 resetButton=2", event) OF
| 0 => (* Code for the "quit" action
| | 1 => (* Code for the "retry" action *)
| | 2 => (* Code for the "reset" action *)
| ELSE <* ASSERT FALSE *> END;
As mentioned above, FormsVBT "Filter" components, if present, are
managed automatically. For example, suppose that the quit button is
specified like this:
| (Filter Dormant
| (Button %quitButton "Quit"))
The quit button will begin in the dormant state, i.e., grayed-out
in appearance, and unresponsive to user gestures. However, when the
"DetailWait" procedure is invoked, the "EventSync" package will
notice that the quit button is a descendent of a "Filter", and will
automatically change its state to "Active". When "DetailWait"
returns, it will restore all filters to their original states. In
this way, the user gets an automatic indication of what his options
are at all times.
This automatic feedback also can be applied to non-interactive
components, such as "Text" components. In the "names" argument, such
components should appear without the equal sign or the identifying
number. For example:
| "quitButton=0 retryButton=1 resetButton=2 alertMessage"
Here, any "Filter" above the "alertMessage" component will be
activated as usual. But "DetailWait" will not expect to receive any
events from that component. Obviously, at least one of the
components mentioned in "names" needs to have an equal sign and
identifying number; otherwise, the wait will never end.
"DetailWait" is alertable. It restores all filters to their original
states before raising its "Thread.Alerted" exception.
Often, detailed information about the triggering event is not needed.
The simpler "Wait" procedure can be used, in that case. *)
PROCEDURE Wait(fv: FormsVBT.T; names: TEXT): CARDINAL
RAISES {Error, FormsVBT.Error, Thread.Alerted};
<* LL.sup < VBT.mu *>
Like DetailWait, except that the triggering event is not passed back
to the caller.
END EventSync.