[up] [next]
 

Using the GUI Painter

Index


Introduction

The Graphical User Interface (GUI) Painter of ST/X is a complete tool for interactively building user interfaces. All tools needed to create user interfaces are combined in the GUI Painter. The user interfaces are assembled by dragging window components (widgets) from a gallery window and dropping them in a canvas window. Then with the mouse, the widgets will be sized and moved to the desired boundaries and positions within the frame of the canvas window. Finally, the model (data) attributes of widgets are defined by creating aspect accessing methods.

While editing an interface, the GUI Painter holds all attribute values of the application window and their containing widgets. This collection of attribute values fully represents a definition of the window application and is called window specification. In order to store that window specification, the GUI Painter generates a resource method of the window specification in the category window specs of the application class protocol, which returns a symbolic representation of the widgets and their properties. This window resource method is used to create and to initialize the view objects and the models objects of the widgets when your application is started. After each adding of a widget or a modification you can save the window specification on the resource method and start the application in order to get a preview of the interface. Later, if an interface needs some modification, the GUI Painter can be started on the window resource method of that interface.

The format of the ST/X's window specification are designed to be upward compatible from VisualWorks * - this means that existing window specifications should work in the ST/X environment. (We depend on user feed backs through verbal descriptions or existing specifications to validate and maintain this upward compatibility - you are welcome to send a note or your window resource methods for validation, when encountering incompatible specifications... .) Since ST/X's views contain more attributes, their specifications may or may not be backward compatible.
 

* VisualWorks is a trademark of ObjectShare, Inc.


Starting the GUI Painter

There are 3 ways to start the GUI Painter:
  1. by clicking on the button The Start Button of the GUI Painteror selecting the menu item Tools/GUI Painter of the launcher
  2. by double-clicking in a System Browser on a resource method (marked by  An icon representing a canvas resource method) containing a canvas specification
  3. by evaluating one of

  4.     UIPainter open
        UIPainter openOnClass:anApplicationClass andSelector:aWindowSpecSelector
 
After starting, the GUI Painter appears with 3 windows as shown in next figures:

The Canvas Window of the GUI Painter
The canvas window (with grid) for assembling your widgets

The Gallery Window of the GUI Painter
The gallery window with the widgets

The Control Window of the GUI Painter
The window for controlling the widgets
 


Description of a Window Application

Where to create the Application Class

The class of your window application should be a direct or indirect subclass of ApplicationModel, in order to inherit the required mechanisms for startup and release. The GUI Painter generates (at least) one resource method for the window specification, which by default is called #windowSpec.

Window specification methods can also be useful outside the ApplicationModel hierarchy. This is useful for example, to open a constructed dialog or popupMenu.
However, in this case, the builder must be provided with additional aspect-binding information (typically provided in a dictionary).
Examples for this kind of use can be found by searching for senders of #openFor:interfaceSpec:withBindings:, #openDialogInterface:withBindings: and similar messages.

When your application is opened, it fetches this specification (by sending "self windowSpec", and passes the spec to its ui-builder. The builder recursively walks throughout the definition, and creates corresponding widgets on the fly.
As widgets are built, named widgets are placed into a dictionary, and can later be accessed by the application via "self builder componentAt: nameOfTheComponent".
 

The Widgets and how do they work

The widgets of ST/X consist of 2 basic objects: The view objects (or short views) are instances of view classes which are subclasses of the abstract superclass SimpleView. Each widget has a specification class which are a subclass of the superclass UISpecification. The specification objects (or short specs) are temporarily created in the startup phase of the application.

For portability ST/X does not use native widgets of the working operating system. The widgets takes its visual attributes from a so called style sheet. This style sheet is a file (see directory "resources" for files *.rs) which lists all properties of the user interface. You are free to design you own user interface by copying an existing rs-file and modifying it. (For more details see chapter "Changing the view style appearance" in document "Configuration & Customization".) Some visual attributes of the widgets (font style, colors, borders) can individually be changed by the GUI Painter.

The GUI Painter of ST/X provides the interface developer with all widgets for building a complete window application. The widgets are accessible by the gallery window. Because the similar meaning of some widgets they are divided into sections. The sections and their widgets are as following:
 

Table of the Widgets

Button Widgets Menu Widgets Text Widgets List Widgets Grouping Widgets Misc Widgets
Button Tool Bar Label List Box Arbitrary Component
Model Button PopUp List Entry Field Table Framed Box SubSpecification
Arrow Button Combo List Text Editor Tree List Horizontal Panel SubCanvas
Radio Button Combo Box HTML Browser File Tree List Vertical Panel Slider
UpDown Button Ext. ComboBox Area Panel Thumb Wheel
Toggle Tab Header Variable Horiz. Panel Progress Indicator
Check Box Note Book Variable Vert. Panel Separator
Check Toggle Gallery Region
Application Window (canvas)

Common attributes

Besides the individual attributes of the widgets there are some common attributes:

The ID

Each widget must have an ID. The builder of the application keeps track of the widgets by these keys (they are kept in the components-dictionary, in an instance variable of the builder).
This allows programatic access to widget components within the application; send the componentAt:aKey-message, passing a widgets ID as argument, to retrieve the widget (view instance).
The GUI Painter generates a default ID for each widget, which consists of the name of the widget and a sequence number at the end.

Do not worry about the names of the view and specification classes of the widgets. The class naming has historical reasons (for portability to VW). To avoid using 'complicated' names for the widgets, user friendly names are used in this documentation and the within the UIPainter.
 

The Font

For all text-displaying widgets, an appropriate font style can be chosen from a font panel:

A View of a Font Panel

It offers a palette of common styles. This palette can be modified in the class FontMenu. After selecting the check toggle, an own font style can be chosen by selecting one of the Family/Face/Style/Size buttons.
If the check toggle is left off, the default font (from the viewStyle sheet) will be used.
 

The Color of the Widget

For some widgets a foreground/background/lamp color (depends on the widget) can be defined. The colors can be chosen from a color panel:

A View of a Color Panel

It offers a palette of often used standard colors. This palette can be modified in the class ColorMenu. After selecting the check toggle, an own color can be chosen by selecting the desired colored button. If no color is selected, the widgets take default colors from the style sheet.
 

The Help Tool

For each widget a help key plus a help text can be defined in the section Help of the control window. For more information load the document: "Using the Help Tool".
 

The Layout of the Widget

For each widget there are layout types for the boundaries of the widget which can be selected and changed. For more information load the document: "The Layout of the Widgets".
 
 

How to hold the Data

The holding of the data of the window application is based on the using of the model objects (or short models). The models are instances of a subclass of class Model (especially of the ValueHolder class). Value holders do not only hold values (String instances, Array instances, etc.), but they also hold a collection of their dependents. The most taken dependents are views "connected" to an unique value holder. Each time you have to change the value of the value holder all depend views are informed about the change. The views themselves do decide whether and how to update (and redraw) their belonging contents (texts, images, or other figures).

This is necessary if you want to change the contents of the view objects dynamically during the run time, and if you want to present the data in several view objects (on different clients) at the same time.

The value holders are acquired by the builder of the application during startup. For each model (or value holder), your application may provide a corresponding access method. These accessing methods are called aspect methods for short. The place of implementation depends on your application. The builder looks for it in different objects. These are in order:

  1. in your application
  2. in the class of your application
  3. in the masterAppliaction (an instance variable of the class ApplicationModel)
  4. in the class of the masterAppliaction
  5. in the masterAppliaction of the masterAppliaction
  6. and so on - until masterAppliaction is found to be nil
Since the builder fetches the models via the aspectFor:aModelKey-method, there is an alternative way: redefinition of this method, defining an instance variable which holds an identityDictionary, and putting all models in that instance variable. This may be a more elegant way, especially to avoid implementing many accessing methods (see the examples).

The whole framework heavily depends on the model-view design pattern, so you should know and understand the Model class and especially the ValueHolder classes.

The following figure summarizes these relations:

The picture is not completely correct, in that the aspect access (from the instance of the class UIBuilder) is actually via your application - however, most applications use the fallBack via the "bindings" dictionary which is provided by the class ApplicationModel; therefore, the figure gives a picture of the typical setup.
 
For more details of value holders see document "Understanding and Using ValueModels".
 
 

How and where to access other attributes

Most widgets provide internally more attributes or more control over their behavior as accessable via the GUI painter.
For example, a button can be setup with individual press- and release-actions, many widgets provide for more color attributes and so on.

These attributes must be modified programatically - either during startup of the application (i.e. right before the window is made visible) or dynamically as a result of other activities.

During startup, a number of hook-methods are invoked, which can be redefined by the programmer. These hooks (i.e. messages sent to the application) are:

An example for a #postBuildWith: method is shown below. That method defines a buttons active colors (which are not otherwise accessable via the GUI painter).
Notice, that the button widget is accessed by name-ID - the name-ID as defined in the widgets 'Basic' section:
    postBuildWith:aBuilder
	|myButton|

	"/ fetch a widget by its ID
	myButton := aBuilder componentAt:#myButton.
	myButton activeForegroundColor:Color red.
	myButton activeBackgroundColor:Color yellow.

How to get informed when an aspect changes

Although the GUI painter allows for a callBack to be specified for many widgets' actions, there are some which cannot be specified directly, but may be of interest to your application code.
For all valueHolder aspects, an interest can be setup in the aspect method. For example, consider an instance varaible named 'myAspect', which is used as aspect for some widget; your application may want to be informed whenever the value changes (for example, due to some widget interaction). To set up such a behavior, use the following code:
    myAspect
	myAspect isNil ifTrue:[
	    myAspect := ValueHolder new.
	    myAspect onChangeSend:#myAspectChanged to:self.
	].
	^ myAspect
the above example arranges for the #myAspectChanged method to be invoked whenever the value in myAspcet changes (due to some widget interaction or however).

Notice, that this is functionally the same setup as arranged when a callBack is installed in the GUI painter.

How to cleanup and shutdown the application

To close down your application, use the (inherited) method #closeRequest.
This method should be invoked by either a menu item, or a close-Button.
It will also be automatically invoked if you close the window via the window-manager (i.e. you clock on the close-Icon in the windowFrame).

Normally, there is no need to redefine this method, unless you want to let the user confirm the close (for example, if there is any unsaved data); in this case, redefine the #closeRequest method as follows:

    closeRequest
	self hasAnyUnsavedData ifTrue:[
	    (self confirm:'Close without saving ?') ifFalse:[
		^ self
	    ]
	].
	super closeRequest
The above method suppresses the window-closing if there is unsaved data, and the user does not confirm the dialog with 'OK'.

This inherited closeRequest method simply invokes the closeDownViews method, which does a hard-shutdown. You are not supposed to redefined closeDownViews - but you may of course invoke it if you want your application to shutDown without a closeRequest.

How to embed subApplications

Applications can be nested - that means, that a GUI as created with the builder can be used as a building block for other applications.
There are two ways to reuse a windowSpec:

How to create a GUI-Dialog without an extra class

Often, simple to medium-complex dialogs are required, for which either none of the standard dialogs (see common dialogs in DialogBox) fits, or which need a component layout, that makes programatic construction (via #addComponent:) difficult or impossible.

If you dont want to add extra classes for this kind of dialog, you can still use the GUIBuilder and windowSpec framework.

First, construct the dialogs window spec using the GUIPainter, and save it as some class method of your application.
The code to open the dialog (in your application) should be written as:


	...
	bindings := IdentityDictionar new.
	for eachAspect do:[:aspect |
	    bindings at:#aspectSymbol put:theAspectHolder
	].
	(SimpleDialog
	    openDialogInterfaceSpec:(self class specSelectorsName)
	    withBindings:bindings)
	ifTrue:[
	    ...
	].
	...
i.e. provide all bindings as required by the GUI-dialog in a single dictionary.
Missing aspect bindings will be trated as non-existing, and usually defaulted to nil.
There is no need for every binding to be a valueHolder; lists or string-valued aspects my also be provided as-is (unless they change during the dialogs lifetime).
Also, blocks may be provided, to add dynamic behavior (thanks to blocks and any object responding to the #value messages)

Notice, that in the above case can be used in any context - even if the invoking object hs no relationship to the ApplicationModel framework.
If the dialog is to be opened from within an applicationModel instance, you should use the following:

	(self
	    openDialogInterfaceSpec:(self class specSelectorsName)
	    withBindings:bindings)
or, shorter:
	(self
	    openDialogInterface:#specSelectorsName
	    withBindings:bindings)
In this case, the opened dialog gets the onvoking application as its "masterApplication", which means that missing aspects (in the bindings dictionary) and resources be requested from your application as a fallBack.

The #openDialog*: family of methods all return the value of the acceptChannel (which need not be provided in the bindings dictionary, since it is added by SimpleDialog).
Therefore, the code in the ifTrue: clause is evaluated if the dialog was closed with an OK button.

In seldom cases, a postBuild action is required, to fix things before the interface is opened (for example, to change certain widget attributes, which are not controllable via the windowSpec).
To arrange for this callback, use the following code as a guide:

	...
	bindings := IdentityDictionar new.
	...
	dialog := SimpleDialog new.
	dialog postBuildBlock:[:builder | .... do whatEver is required ...].
	dialog
	    openFor:self "or nil"
	    spec:(self class specSelectorsName)
	    withBindings:bindings)
	ifTrue:[
	    ...
	].
	...
corresponding hook-blocks can be installed as preBuild and postOpen actions.
(pretty similar to the #postBuildWith: and #postOpenWith: methods which are invoked for regular applications.)

For concrete examples in the system, look for senders of "open*withBindings" or references to the SimpleDialog class.



Copyright © 1998 eXept Software AG, all rights reserved


Doc $Revision: 1.47 $ $Date: 2000/04/10 08:51:57 $