<* 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 *>LikeDetailWait
, except that the triggering event is not passed back to the caller.END EventSync.