To use FormsVBT, you need a copy of SRC Modula-3 (Version 3.3 or later) and an X server for your system. If you have these, you may want to compile and run the example programs as you read this chapter.
The first example program is in the file Hello.m3:
MODULE Hello EXPORTS Main; IMPORT FormsVBT, Trestle; VAR fv := FormsVBT.NewFromFile("Hello.fv"); BEGIN Trestle.Install(fv); Trestle.AwaitDelete(fv) END Hello.
The program builds a form (sometimes called a ``dialog box'' or a ``user interface'') whose description is contained in a file named Hello.fv. It installs the form in a top-level window, and then waits until that window is deleted by the user. The window installed by the program is shown in the left half of Fig. [fig:hello] .
The ``Hello FormsVBT!'' example program.
The initial version is on the left; the second version on the
right.
The file Hello.fv contains the following S-expression:
(VBox (Text "Hello FormsVBT!") (Bar) (HBox (Text "Left") (Bar) (Text "Right")))
The top-level component is a VBox. A VBox takes an arbitrary number of ``children'' (sub-components) and arranges them vertically from top to bottom. This VBox has 3 children: Text, Bar, and HBox. A Text displays a text string, a Bar draws a line orthogonal to the orientation of its parent, and an HBox arranges its children horizontally, from left to right. The HBox has 3 children, two Texts and one Bar.
The standard way that you compile and link your programs is to use m3build. The m3makefile for the ``Hello FormsVBT!'' application is as follows:
import (formsvbt) implementation (Hello) program (Hello)
Then you can compile and link the Hello program by typing the shell-command m3build -S in the directory containing the source code.
Actually, most Modula-3 programmers follow the convention of storing all of the source files for an application in a directory called src. The m3build command, when run from src's parent directory, stores all of its derived files (including the executable) in a subdirectory whose name depends on the platform on which you are running. For example, on DECstations, the derived directory is DS; on an Alpha running OSF, the directory is AOSF. When you follow this directory structure, you should invoke m3build without any arguments.
Here's a slightly fancier version of the interface (shown in the right half of Fig. [fig:hello] ):
(Rim (Pen 20) (Border (Pen 1) (Rim (Pen 2) (Border (Pen 2) (VTile (Text "Hello FormsVBT!") (HBox (LabelFont (PointSize 240)) (Color "White") (Text (BgColor "Pink") "Left") (Bar) (Text (BgColor "VividBlue") "Right")))))))
The top-level component is a Rim whose Pen property has a value of 20. A Rim must contain exactly one child (a Border in this case), and it surrounds its child with some background space. Here, the Rim provides 20 points of background space between each edge of the window manager's window frame and the rest of the interface. A Border is just like a Rim, but draws with the foreground color instead of the background color. We replaced the VBox with a VTile, and deleted its Bar child. A VTile is like a VBox, but it also automatically inserts a dividing bar between its children; by dragging the dividing bar, the user can control the division of space among the children. In this example, the HBox has been given two properties, Color and LabelFont. These control the foreground color and font used by the HBox and all of its descendants. Similarly, the BgColor property changes the background color used.
The fancy version of ``Hello FormsVBT!'' is in the file HelloFancy.fv. To run the application using this file, either modify the application to use HelloFancy.fv or rename the file HelloFancy.fv to be Hello.fv. Alternatively, you might find it enjoyable to run the FormsVBT interactive UI builder, formsedit. Just type the shell-command
formsedit HelloFancy.fv
Exercise 1 Write the FormsVBT S-expression for T^4, a Trestle Tiling Monster of Order 4. (See the Trestle Tutorial, Fig. 2 on page 5.)
A resource is constant data needed by an application program at runtime; often it is ``loaded'' at startup time. Almost all FormsVBT programs have resources, such as the .fv (pronounced ``dot ef vee'') files that specify the user interface. Other typical resources specific to an application include bitmaps, cursors, and help-texts.
When an application is built, its resources can be ``bundled'' with the executable image. The primary benefit of this feature is that applications are self-contained with respect to the resources they need. Thus, you can copy an executable to a remote site and you won't need to copy the resource files and install them in the same place as they were when the application was built. Also, your application will be insulated against changes in library resources.
The easiest way to do this is to name the resources and the bundle in the m3makefile, as in this example:
import (formsvbt) resource (Hello.fv) bundle (HelloBundle) implementation (Hello) program (Hello)
The second line declares that there is a resource named Hello.fv. The third line has the effect of collecting all the named resources (only one in this case) and creating an interface called HelloBundle that provides access to them. The program would then be modified to look like this:
MODULE Hello EXPORTS Main; IMPORT FormsVBT, HelloBundle, Rsrc, Trestle; VAR path := Rsrc.BuildPath(HelloBundle.Get()); fv := NEW (FormsVBT.T).initFromRsrc ("Hello.fv", path); BEGIN Trestle.Install(fv); Trestle.AwaitDelete(fv) END Hello.
The call to HelloBundle.Get returns a bundle that is used to create a resource-path, which is then searched by the initFromRsrc method.
But what if you want the application to use new resource files? For example, you might have changed some details of the .fv file that don't require any changes to the application code. Do you have to rebuild the entire application?
Fortunately, the answer is no. However, you do need to tell FormsVBT that you want it to look for those resources in the file system before it looks for them among the resources that were bundled into the application. You do this by changing the resource-path so that it includes one or more directories before the bundle.
The convention is to use environment variables whose names are spelled by combining the program's name with the string "PATH". This variable should be set to a list of directory-names, each separated by a colon. So, if you want to run the Hello program using the Hello.fv file that's in Smith's home directory instead of the one that's bundled with the application, you would type something like this shell command:
setenv HelloPATH /user/smith
In the program, you would construct a resource-path that included this directory by adding the name HelloPATH, prefixed with a dollar sign:
MODULE Hello EXPORTS Main; IMPORT FormsVBT, HelloBundle, Rsrc, Trestle; VAR path := Rsrc.BuildPath("$HelloPATH", HelloBundle.Get()); fv := NEW (FormsVBT.T).initFromRsrc ("Hello.fv", path); BEGIN Trestle.Install(fv); Trestle.AwaitDelete(fv) END Hello.
Syntactically, there are three types of components in FormsVBT: leaves, filters, and splits. A leaf has no children; a filter has exactly one child; and a split has any number of children.
The FormsVBT leaf components include passive objects like texts and pixmaps, as well as interactive objects like scrollbars and type-in fields.
A filter modifies its child's looks or behavior in some way. We've seen how a Border draws a border around its child. Another common filter is Boolean. It adds a check box to the left of its child and makes the box and the child sensitive to mouse clicks. It's important to realize that the child may be any arbitrarily complex arrangement of components, although a Text component is the most common.
The purpose of most splits is to divide the display area among its component-children (sub-components). In addition to the horizontal and vertical splits that we've seen, FormsVBT provides a temporal split (TSplit) to display exactly one child at any given time, and a z-axis split (ZSplit) to display children as overlapping subwindows.
Components are written as lists containing the component's type, followed by some number of properties, followed by some number of sub-components. Properties are written as lists containing a keyword and a value. For example, in the S-expression:
(HBox (LabelFont (PointSize 240)) (Color "White") (Text "Left") (Bar) (Text "Right")
the parent-component's type is HBox. This component has two properties; the first property has the keyword LabelFont and the value (PointSize 240); the second has the keyword Color and the value "White". It has three sub-components: (Text "Left"), (Bar), and (Text "Right").
The value of each property is type-checked when the description is parsed. The possible types include strings, integers, and real numbers, as well as more complicated types like color and font specifications.
So far, we have seen two kinds of properties. Class properties, like Pen, are defined in conjunction with specific components, and are allowed only on components of that class. Inherited properties, like Color and LabelFont, may be specified for any component, though they are not relevant to all component types. The inherited properties have the feature that a value specified for one component becomes the default value for all descendants of that component. Thus an inherited property applies not to one component, but to an entire subtree.
FormsVBT supports a third type of property, universal properties. A universal property can be specified on any component, and its value applies only to that component. <--! %Thus, universal %properties are like class properties in the sense that their values %apply to the particular component on which the property is specified. %Universal properties are also like inherited properties in the sense %that they can be specified for all components. However, the value of a %universal property does not propagate; it applies only to the %component on which it is specified. % To summarize, the FormsVBT language offers a rich set of composition mechanisms % and a variety of predefined objects, allowing easy specification and % implementation of complex user interfaces. This philosophy is quite similar to % that of InterViews~\cite{interviews}, a popular C++ toolkit running on X. % Nearly all of the primitive objects found in the VBTkit and Trestle window % system, upon which FormsVBT is based, have counterparts in InterViews. -->
Exercise 2: In HelloFancy.fv, wrap a Scale component around the top-level Rim. The Scale has two class properties: HScale and VScale. What happens when the values of both of these properties are set to 1.75? What happens when you nest Scale filters?
A more interesting application is a three-cell calculator. Readers may wish to compare the FormsVBT implementation of this example with that of SUIT [suit] . The user can enter two numbers and an arithmetic operation to perform on the two numbers. The result is computed and displayed whenever the user selects a new arithmetic operation or types a new number. Fig. [fig:calc3cell] shows the application in action.
The user interface is described by the following FormsVBT expression:
(Shape (Width 300 + 100 - 50) (Height + 25) (Rim (Pen 20) (VBox (HBox (VBox Fill (Numeric %num1 =5) Fill) (Radio %functions =add (VBox (Choice %div "divide") (Choice %mul "multiply") (Choice %sub "subtract") (Choice %add "add"))) (VBox Fill (Numeric %num2 =2) Fill) (Text "=") (Text %result LeftAlign "")) (Glue 10) (HBox Fill (Guard (Button %exit "QUIT")) Fill))))
The tokens that start with percent signs are names assigned to components. For example, the Text component where the application stores the result of each computation is named result. An application can access only named components at runtime.
The three-cell calculator application.
This form contains the following components that we have not seen before:
In FormsVBT, as in most GUI toolkits, an application is structured as an initialization routine, which runs in one thread, and a collection of event-handling procedures, which run in other threads. When an application is run, it initializes dialogs and then transfers control to the toolkit. The main thread waits until the toolkit returns control, which it does when all the dialogs have been deleted.
Here is the complete application for the three-cell calculator (see the file Calc3Cell.m3):
MODULE Calc3Cell EXPORTS Main; IMPORT Fmt, FormsVBT, Text, Trestle, VBT; PROCEDURE NewForm (): FormsVBT.T = VAR fv := FormsVBT.NewFromFile ("Calc3Cell.fv"); qcl := NEW (FormsVBT.Closure, apply := Quit); ccl := NEW (FormsVBT.Closure, apply := Compute); BEGIN FormsVBT.Attach (fv, "exit", qcl); FormsVBT.Attach (fv, "num1", ccl); FormsVBT.Attach (fv, "num2", ccl); FormsVBT.Attach (fv, "functions", ccl); RETURN fv END NewForm; PROCEDURE Quit (cl : FormsVBT.Closure; fv : FormsVBT.T; name: TEXT; time: VBT.TimeStamp) = BEGIN Trestle.Delete (fv) END Quit; PROCEDURE Compute (cl : FormsVBT.Closure; fv : FormsVBT.T; name: TEXT; time: VBT.TimeStamp) = VAR answer: REAL; first := FLOAT (FormsVBT.GetInteger (fv, "num1")); second := FLOAT (FormsVBT.GetInteger (fv, "num2")); fn := FormsVBT.GetChoice (fv, "functions"); BEGIN IF Text.Equal (fn, "add") THEN answer := first + second ELSIF Text.Equal (fn, "sub") THEN answer := first - second ELSIF Text.Equal (fn, "mul") THEN answer := first * second ELSIF Text.Equal (fn, "div") THEN answer := first / second END; FormsVBT.PutText (fv, "result", Fmt.Real (answer)) END Compute; BEGIN VAR fv := NewForm(); BEGIN Trestle.Install(fv); Trestle.AwaitDelete(fv) END END Calc3Cell.
The parameters to an event-handler (e.g., Quit and Compute in the Calc3Cell program) identify the dialog (fv) in which the event happened and the name of the interactor causing the event.
The event-handler's first parameter, named cl in this example, is a FormsVBT.Closure that is specified when the event-handler is attached. Its apply method is the event-handler. The standard way of passing additional information to the event-handler is to create a subtype of FormsVBT.Closure, with new fields, and possibly new methods, for handling the new information. The time parameter is a timestamp associated with the user event that caused the event-handler to be invoked. The timestamp is needed for certain operations, like acquiring the keyboard focus.
We say that a component ``generates an event'' when the user does something in a component that causes the event-handler to be invoked. The semantics of what causes an event to be generated is specific to each component.
The Three-Cell Calculator application creates a form and passes it to Trestle, the window manager, which ``installs'' it, just as the ``Hello FormsVBT!'' application did. Here, as part of building a form from the S-expression in file Calc3Cell.fv, we also attach event-handlers to the components to which the application will respond. The Quit event-handler, which is attached to the component named exit (the button labeled ``QUIT''), deletes the window from Trestle. The Compute event-handler, which is attached to both of the Numeric components as well as the radio buttons, retrieves the values stored in both Numeric components, determines which arithmetic function the user selected, performs the operation, and then displays the result.
Exercise 3 Add your favorite operator to the application and to the user interface. (If you're undecided about which operator is your favorite, try GCD.)
The Three-cell Calculator S-expression illustrates a number of common abbreviations that help make the FormsVBT language more readable.
A percent sign is an abbreviation for the Name property. That is, the FormsVBT parser reads %xyz exactly as if it were (Name xyz).
An equals sign is an abbreviation for the property called Value. That is, the FormsVBT parser reads = xyz exactly as if it were (Value xyz). By convention, any component whose value can be changed interactively by a user has a Value property.
Components that display some type of object, like a string or a pixmap, specify the object using a property called Main. For example, to display a pixmap from a file named Trumpet, you'd say (Pixmap (Main "Trumpet")). However, the Main property can be abbreviated by omitting the keyword Main and the associated parentheses, e.g., (Pixmap "Trumpet").
A Text component that has no properties other than Main can be further abbreviated simply by giving a string. For example, (Text (Main "QUIT")) can be reduced to (Text "QUIT") and then to "QUIT". Other examples of this are the children of the four Choice components in the last program. If you want to specify any properties on a Text component (such as a name, font, color, or alignment), you can abbreviate Main, but you still need to write (Text ...).
Boolean properties have a value of either TRUE or FALSE. The default value of all Boolean properties is FALSE. Mentioning the name of a Boolean property is an abbreviation for specifying a true value. For example, in the Three-Cell Calculator, the token LeftAlign is an abbreviation for (LeftAlign TRUE).
Finally, leaf components without any properties can be written without parentheses, e.g., Fill.
The following chart summarizes these abbreviations:
(Text "t") | "t" |
(Name n) | %n |
(Value v) | =v |
(Main m) | m |
(boolprop TRUE) | boolprop |
(proplessleaf) | proplessleaf |
Exercise 4 The following interface contains
a textual label, a type-in field, and a button:
The interface is 250x75 points, and it uses Button, Frame, Pixmap, Rim, Shape, Text, and TypeIn components, in addition to some HBoxes, VBoxes, Glues, and Fills. Appendix [ap:longcatalog] describes the class-specific properties for each component. Write a concise FormsVBT expression for this form.
FormsVBT provides two additional ways to make S-expressions more readable. First, an S-expression can be split across multiple files (resources). To insert a file named HelpDialog.fv, just include the expression
(Insert "HelpDialog.fv")
wherever you want the file to be inserted. The Insert expression can appear anywhere in an S-expression; logically, it is replaced by the contents of the named file before the S-expression is parsed.
The second way to make the form more readable is by using macros. Syntactically, a macro is an inherited property with the name Macro. For details on macros, see Section [sec:language-macros] .
One of the ways that user interface toolkits like FormsVBT simplify the construction of interactive, graphical applications is by forcing a separation of the interaction-specific parts from the application-specific parts. This allows the interface designer to concentrate on the design of the interface and the application programmer on the implementation of the application-specific code.
In FormsVBT, the only UI components known to the application are those that are given names. The application is insensitive to the layout of components and to the existence of all unnamed components. There is even some insulation between the application and the UI for named components: one component may be replaced with another whose behavior with respect to the application is the same.
For instance, in the Three-Cell Calculator interface from Section [sec:calcIntro] , we could replace the Text-component named result with any other component that can store text, such as a Typescript. A Typescript would capture a history of all values computed by the application. (We would also need to delete the LeftAlign and Main properties to make this change.)
We could also replace the radio buttons with items in a pulldown menu by replacing this expression
(Radio %functions (VBox (Choice %div "divide") (Choice %mul "multiply") (Choice %sub "subtract") (Choice %add "add")))
with the following expression:
(Menu "?" (Radio %functions =add (HBox (VBox (Choice MenuStyle %div "divide") (Choice MenuStyle %mul "multiply")) (VBox (Choice MenuStyle %sub "subtract")) (Choice MenuStyle %add "add")))))
See Fig. [fig:calc3cell-menu] .
A modified UI for the three-cell calculator application.
The cursor (not visible in the figure) is over the string ``multiply.''
The first child of a Menu
The contents of this menu emphasizes an earlier point about
composition. A Menu does not impose any structure on the
contents of the menu. One merely composes a Menu out of 2
children: a child that is the anchor button, and a child that appears
when the anchor button is activated. A ``traditional'' pulldown menu
is a VBox whose children are MButtons.
Exercise 5 Change the program so that a symbol for the
current operator is displayed instead of the question mark. Hint:
Assign name to the quoted question mark, by using the expanded format
(Text %op "?"), and call FormsVBT.PutText
to change what is displayed in a Text component.
The three-cell calculator will crash if we try to divide by 0. Let's
change the application to pop up a dialog box warning the user if
there is an attempt to divide by 0. We need to modify the
Compute event-handler by adding a test for a divisor equal to
zero just before the division:
The call to FormsVBT.PopUp will cause the named dialog to appear.
It is easy to add a dialog named errorWindow to the
calculator's S-expression that was given in Section
[sec:calcIntro] .
The S-expression becomes the following:
A ZSplit takes an arbitrary number of children and displays
them as overlapping windows. The first child is the background; it is
always visible. The visibility and location of the other children are
under program control. The ZChassis wraps a ``banner'' around its
child; the banner is responsive to mouse activity for the common
window controls of closing, moving, and resizing. A call to
FormsVBT.PopUp will cause a specified child of a ZSplit
to appear. By default, a ZChassis is not initially visible.
Another common use of subwindows is to allow a user to specify
additional information for a command. For example, the ``Save As...''
button found in many applications pops up a dialog box, which is a
subwindow, with a way to enter the name of a file. A button like
``About Bazinga...'' pops up a subwindow containing information about
the application called Bazinga.
In situations like these, it's a burden on the programmer to
write an event-handler that simply calls FormsVBT.PopUp.
To simplify this common case, FormsVBT provides a
PopButton. This component is just like a Button,
but before its event-handler is called, it causes a designated
subwindow to appear. In practice, applications often don't need
to attach any event-handler to a PopButton.
For grins, we'll now change the original three-cell calculator
user interface so that the radio buttons are in a subwindow that
is completely controlled by the user. Clicking on the "?" menu
will cause the subwindow to appear. The window can be closed
and repositioned without any application code. We need make
two small changes to the original S-expression given in
Section
[sec:calcIntro]
to add subwindows. First, replace
the radio buttons by a button that causes a subwindow to pop-up.
That is, change
to
Second, move the Radio expression into a subwindow, by
enclosing it in a ZChassis, and wrapping a ZSpit
around the root. Now, Calc3Cell.fv looks like this:
Exercise 6
Add an ``About Three-Cell Calculator...'' button. It should pop-up a
subwindow with appropriate information. If you want to put the button
inside of a pull-down menu, use PopMButton.
When a subwindow appears, the rest of the form and all other
subwindows remain active. In the case of the operator-subwindow in
Section
[sec:subwindows]
(i.e., the ZChassis named
fnWindow), this behavior was desirable. However, this behavior may not
be desirable for the error-message subwindow. That is, some
application writers would like to force the user to explicitly close
the error message subwindow before continuing to interact in the
application. In the UI jargon, this is called a modal
dialog.
A simple way to do this is to bring up the error subwindow
as before, but also to ``deactivate'' the background---make it
unresponsive to user actions---while the subwindow is displayed.
When the dialog is finished, we ``re-activate'' the background.
A FormsVBT component called Filter is used to set the
reactivity of its child to be active (the default case),
passive (mouse and keyboard events are not sent), dormant (like
passive, but it also grays out the child and changes the cursor),
or vanished (like passive, but also draws over the child in the
background color, thereby making it invisible).
Changing the modeless subwindow in the calculator so that it
is modal requires only a trivial change. First, add a
Filter just inside the ZBackground. Name this
component zbg. Second, in the application, add
after the call to FormsVBT.PopUp. Finally, you need to register an
event-handler for the ZChassis named errorWindow.
The event handler will be invoked when
the subwindow is closed; it contains the following line:
You might also wish to eliminate the banner on the subwindow. To do so, change
the ZChassis to be a
ZChild, and add a CloseButton somewhere
in the subwindow. The CloseButton button will cause the
subwindow in which it is contained to be taken down.
Fig.
[fig:modal]
shows the modified application.
Exercise 7
Make the error window in the three-cell calculator modal in the
manner suggested in this section: In the .fv file, add a
Filter inside the ZBackground, change the error
subwindow from a ZChassis to a ZChild, and add a
CloseButton to the error window. In the .m3 file, change
the application code so that the background is made passive when the
error window appears, and re-activated after error window disappears.
Exercise 8
When the error dialog appears while the subwindow containing operators
is visible, the operators are not deactivated, although the main form
is deactivated. Change the form so that everything except the
error subwindow is made passive. Don't modify the application! (Hint:
Use two ZSplits, one the background child of the other.)
It's easy to hook up the FormsVBT text-editing widget to an application to
make a bona fide text editor. The file-viewer application, shown in
Fig.
[fig:viewer] ,
contains a type-in area on the top for entering the name
of a file and a fully functional text editor that occupies the bulk of the
window. The text editor is in read-only mode.
The S-expression for the application, in the file Viewer.fv, is quite
simple:
The application is structured as in the three-cell calculator
application in Section
[sec:calcIntro] . A NewForm
procedure converts the S-expression into a runtime object and registers
event-handlers. Only one event-handler is needed here; ReadFile is
attached to the type-in field fileName. It is invoked whenever you type
a carriage return in the type-in field. The code is straightforward:
The event-handler first retrieves the string you typed into the type-in field
named fileName. It then calls an internal procedure GetFile to
retrieve the contents of a file by that name, and finally stores the contents
into the text-editor widget. If an error is encountered while trying to
retrieve the contents of the file, ReadFile catches the exception that
is raised and just erases the contents of the type-in field and the text
editor. The application is shown in Fig.
[fig:viewer] .
Exercise 9
Add a Reset button to the left of the Quit button.
Clicking on this button should clear the contents of the type-in field. For
extra credit, interpret a double click to also clear the contents of the
editor. To detect a double-click, you will need to examine the
VBT.MouseRec that is available from FormsVBT.GetTheEvent to the
Reset button's event-handler.
Exercise 10
Add a pop-up to signal when the file could not be opened,
rather than clearing the type-in field.
If you substitute FileBrowser for TypeIn, you'll
be able to traverse the file system by double-clicking on
directories (those items ending with a slash) in a browser. The
file browser generates an event when you double-click on
a file. Note that the application does not need be changed at
all!
While it's nice to be able to traverse the hierarchy by mousing
around in the file browser, there are times when it is more
desirable simply to type in a pathname. No problem. We'll just
add a type-in field to the S-expression. Here's the new
S-expression (see Fig.
[fig:viewer2] ):
(A negative value for the inherited property ShadowSize is a
convention for telling FormsVBT to give feedback using a flat, 2-d style
rather than a Motif-like, 3-d style.)
We also need to change the
application slightly to register the ReadFile
event-handler with the type-in field (i.e.,
fileNameString) as well as with the file browser
(i.e., fileName). In addition, procedure ReadFile
itself needs to be changed trivially to initialize fname
from the interactor that caused the event-handler to be invoked:
So far so good, but there's no tie between the file browser and the type-in field.
Exercise 11
Implement event-handler methods for the file browser
(fileName) and the type-in field (fileNameString) to
keep them synchronized. That is, typing a path into the type-in field
should cause the browser to change the directory it is displaying.
The directory displayed by the file browser is set by calling
FormsVBT.PutText, passing in the name of the directory
to be displayed. What happens if you specify a directory that doesn't exist?
If you didn't do the last exercise, now is your last chance...
It turns out that FormsVBT already provides a component that ties
a type-in field to a file browser. The component is called a
Helper, and it has a class-specific property called For
that names the file browser to which
it is tied. So, if you change the expression
to
and replace the initialization of variable fname in the original
program as described above,
then the type-in field and the file browser will stay synchronized,
without any application-code intervention.
Exercise 12
What happens when you replace ``Helper''
by ``DirMenu''? What happens when you tie a file browser to both a
Helper and DirMenu?
Subwindows
...
ELSIF Text.Equal (fn, "div") THEN
IF second = 0.0 THEN
FormsVBT.PopUp (fv, "errorWindow");
RETURN
END;
answer := first / second
END;
...
(ZSplit
(ZBackground (Shape ...))
(ZChassis %errorWindow
(Title "Error Message")
(Rim (Pen 20)
(Text %errorText "Can't divide by zero."))))
(Radio %functions ...)
(PopButton (For fnWindow) "?")
(ZSplit
(ZBackground (Shape ...))
(ZChassis %errorWindow ...)
(ZChassis %fnWindow
(Radio %functions ...)))
Modal Dialogs
FormsVBT.MakePassive(form, "zbg")
FormsVBT.MakeActive(form, "zbg")
The three-cell calculator application with a modal
dialog.
A File Viewer
(Rim (Pen 10) (Font (WeightName "Bold"))
(VBox
(HBox
(Frame Lowered (TypeIn %fileName))
(Glue 10)
(Button %exit "QUIT"))
(Glue 10)
(Shape (Height 200 + inf) (Width 300 + inf)
(Frame Lowered (TextEdit ReadOnly %editor)))))
PROCEDURE ReadFile (cl : FormsVBT.Closure;
fv : FormsVBT.T;
name: TEXT;
time: VBT.TimeStamp) =
VAR fname := FormsVBT.GetText (fv, "fileName");
BEGIN
TRY
FormsVBT.PutText (fv, "editor", GetFile (fname));
EXCEPT
Rd.Failure =>
FormsVBT.PutText (fv, "editor", "");
FormsVBT.PutText (fv, "fileName", "");
END;
END ReadFile;
A simple file viewer application.
The file viewer application again, but now, file names
can be specified in the type-in field at the top or using the file browser
at the left.
(Rim (Pen 10) (ShadowSize -1)
(BgColor "White") (LightShadow "Black")
(DarkShadow "Black")
(VBox
(HBox
(Frame Lowered (TypeIn %fileNameString))
(Glue 10)
(Button %exit "QUIT"))
(Glue 10)
(HBox
(Shape (Width 100)
(Frame Lowered (FileBrowser %fileName)))
(Glue 10)
(Shape (Height 200 + inf) (Width 300 + inf)
(Frame Lowered (TextEdit ReadOnly %editor))))))
VAR fname := FormsVBT.GetText (fv, name);
(TypeIn %fileNameString)
(Helper (For fileName))