[up] [next]
 

Using the GUI Painter

Index


Introduction

The Graphical User Interface (GUI-) Painter of ST/X is a complete tool for the interactive creation of user interfaces. All functions which required to create user interfaces are combined in the GUI Painter. User interfaces are assembled by dragging window components (widgets) from a so called gallery window and dropping them into a so called canvas window. Then, either by using the mouse or by editing position values, widgets can be sized and moved to the desired boundaries and positions within the canvas window. Finally, the model (data) attributes 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 or window spec for short.
Window specs are stored in the application by generating a method, which when invoked, returns the spec as a literalArray. That method is typically called "windowSpec" and found in the "interface specs" category.
A literalArray is an array object from which the original (spec-) object can be reconstructed. Structure-wise, literalArrays are very similar to XML. However, in contrast to XML, they are not text-based, but an array of symbolic, numeric and string constants (i.e. literals). As such, they are much easier (and faster) to parse and process than XML would be.
When the application is started, a windowBuilder reads that specification and creates a hierarchy of widgets as specified therein.

Notice, that the application can already be started and tested while being constructed in the UI-Painter - even if the application's specification or code are only incompletely defined. Typically, widget placement and programming of behavior are done incrementally and interleaved while a GUI application is being developed. At any time during the edit process, it is possible to see what has been defined so far by simply clicking on the start-application icon in the UI-Painter.

As the windowSpec remains part of the application and provides all required information for the UI-Painter, interfaces can be re-edited and modified at any time later, by openig an UI-Painter on a window spec. (double-click on a window-spec-method in the browser).

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 feedback 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 The Start Button of the GUI Painter -button or by selecting the "Tools/GUI Painter"-menu item of the launcher
  2. by double-clicking on a resource method (marked by  An icon representing a canvas resource method) containing a window spec in a System Browser
  3. by evaluating one of

  4.     UIPainter open
        UIPainter openOnClass:anApplicationClass andSelector:aWindowSpecSelector

After startup, the GUI Painter appears with 3 windows as shown in next figures:

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

The Gallery Window of the GUI Painter
The gallery window containing widgets to be dragged into the canvas

The Control Window of the GUI Painter
The window for controlling the widgets, geometry and attributes
 


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 spec 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:
or
    openDialogInterface:withBindings:
and similar messages.

When your application is opened, it fetches this specification (by sending "self windowSpec", and passes the spec to its window-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
where nameOfTheComponent is the nameKey as specified in the UI-Painter.

Widgets and How 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 View or SimpleView. Views do have a model, while SimpleViews do not; SimpleViews are typically used for geometry wrappers, such as frames, boxes, separators etc.
Each widget has a specification class which inherits from UISpecification. The specification objects (or short specs) are temporarily created during the startup phase of the application.

For portability ST/X does not use native widgets of the underlying operating system. Most of a widget's appearance is read from a so called style sheet, which is a textfile found in the "libview/styles" subdirectory. You are free to design your own user interface by copying an existing style-file and modifying it as appropriate. For more details see chapter "Changing the view style appearance" in the "Configuration & Customization"-document. Some visual attributes of the widgets (font style, colors, borders) can also be defined individually in the GUI Painter.

Gallery of Widgets

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

Common Attributes

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

ID (Key)

Each widget has an ID. When an application is started, the windowBuilder uses these IDs as key to remember widgets in a dictionary, which can be accessed via an instance variable of the builder.
This allows programatic access to components within the application, by sending the
    self builder componentAt:aKey
message, passing a widget's ID as argument,
The GUI Painter automatically generates a unique default ID for each widget, which consists of the widgets name concatenated with a sequence number.

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 within the UIPainter.

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 (which we recommend).

Color(s)

For many widgets a foreground, background or other color 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 in addition to a mixer, which offers arbitrary colors to be specified. After selecting the check toggle, an own color can be chosen by selecting the desired colored button. If the check toggle is left off, the widgets default colors (from the style sheet) are used, which is recommended.
 

The Help Tool

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

Layout

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

How to Hold the Data

An application's data is typically held in so called value-model objects. Conceptually, a valueModel consist of a cell that refers to some other object (a string, array or list, for example) and a collection of dependents that are to be notified whenever the reference changes.
There exists a whole hierarchy of valueModels which all provide the same external interface (value as getter and value: as setter message) but behave different internally.
The simplest one of them is ValueHolder, which actually implements the previously described behavior directly. There are valueModels that compute their value (BlockValue) while others refer to other objects and implement a facade, by translating setter/getter messages into other access messages towards the other objects (AspectAdaptor).
All of them have in common that their dependents get informed whenever the value (whether being real or computed) changes.

The dependent widget(s) on the other hand decide whether and how to update (and redraw) whenever their contents (text, image, color etc.) which is represented by valueholders' values changes.

Be aware that most widgets do internally provide both direct interfaces for setting attributes and contents (messages such as foreground:,background:, label: or contents:) and indirect interfaces via valueModels (model:, listHolder:, labelHolder:, foregroundChannel: etc).
The direct interface maybe somewhat easier for very simple user interfaces, but does not scale when applications become more complicated.
For example, consider the case that a number of widgets has to be enabled, disabled or needs a color change depending on some criteria. When using a direct interface, a message has to be sent to each widget in order to affect its state - and, if more widgets are added later, those sending places need to be maintained and updated.
When using valueModels, only a single valueHolder needs to be given a new value and many widgets may react on this change.

ValueModels are also much more convenient when multiple views present the same data on different display screens or use different rendering algorithms.

During startup, the windowBuilder acquires value holders from the application by sending it a corresponding message. These messages are called aspect messages.
For each model (or value holder), your application should provide a corresponding aspect method. As applications may be defined hierarchical (by embedding applications or by opening sub-applicaitons as dialogs), the builder uses a sequence of possible aspect providers which are asked in sequence for an aspect. These are in order:

  1. the application
  2. the class of the application
  3. the masterAppliaction (an instance variable of the class ApplicationModel)
  4. the class of the masterAppliaction
  5. the masterAppliaction of the masterAppliaction
  6. and so on - until masterAppliaction is found to be nil
As multiple widgets may ask the application for the same aspect, it is the responsibility of the application to keep track of which valueHolder is used for which aspect. This can be done either by The builder fetches models via the aspectFor:aModelKey-method, which (by default - if not redefined in your application) itself sends a aModelKey-message to itself (i.e. perform:aModelKey)

So, application programmers may either provide individual access methods for each aspect, or redefine a single aspectFor: method. (which could return aspects from a dictionary which is setup in the applications initialize method).
In some situations, this may be more elegant, especially to avoid implementing many tiny 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".
 
 
Notice that the value-message which is used to access a valueModels value was choosen by intent: it is also understood by blocks and arbitrary other objects (which return themself). Therefore, readonly aspects (which never change) can also be represented by constants or blocks. For example, your application is free to return a string as (constant) aspect.

How and Where to Access Other Attributes

Most widgets internally provide more attributes or more control over their behavior as accessible 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 accessible 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 variable 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 myAspect 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 Embed Other Views

The UI painter knows about (some of) the attributes of the most commong widget types, and presents them in the attributes tabs. However, many other widget types (subclasses of View) exist, which are used less often and are not supported by corresponding ui specifications. Such widgets are included as "Arbitrary Component" in the specification and their attributes must be set manually, either in a postBuild method, or by referencing the widget by name (builder componentAt:id).

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 the DialogBox-class) fits, or which need a component layout, that makes programatic construction (via #addComponent:) difficult, ugly or impossible.

If you do not want to add extra classes for this kind of dialog, you can still use the GUIPainter 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.


Please proceed reading the "Functions of the GUI Painter" and "Examples" documents.

Copyright © 1998 eXept Software AG, all rights reserved


Doc $Revision: 1.65 $ $Date: 2017/07/13 10:04:43 $