Building a Graphical User Interface
Resources
Open a Resource Finder
Open a new Resource Finder by selecting Browse->Resources in the VisualWorks Launcher window.
The Resource Finder is packaged with the User Interface Painter. If the Resource Finder icon does not appear in the Launcher window, you must load the parcel UIPainter.pcl .
Browse a resource
To open a browser on the selected class in a Resource Finder, use the Browse button. Double-clicking on a class name in the class (left-hand) view brings up a Hierarchy Browser or Class Browser (depending on your Settings preferences) on that class. Double-clicking on a resource opens it with the User Interface Painter.
Filter the display of resource classes
To filter the list to show only the subset of classes added by the user, select View -> User Classes . System classes (i.e., non-user classes) and recently used classes may be displayed by selecting View->System Classes and View->Recently Visited Classes respectively.
The list of resources (right-hand view) can be sorted and displayed either by name or by type: select View->Sort By Name and View->Sort By Type to toggle between these options.
Start an application
Use the Start button to run applications that provide an instance method for `open` , or that adhere to the conventions for subclasses of `ApplicationModel` .
Editing resources
The Add and Remove buttons can be used to add new model classes to the system and remove existing ones (if no selection is made in the right-hand view of the finder) or to add a canvas to an existing class. The Edit button or a double click on a resource name in the right-hand view opens an appropriate editor on the specification selected.
When you add or remove an application class by some means other than the Resource Finder or the canvas's install command, use View->Update to register the change in all Finders.
Create an application model
Use the Class->Add... command to create a new application model. A dialog prompts for the name of the new model class and its superclass (`ApplicationModel` and `SimpleDialog` are provided as defaults). If the new class created understands the message `windowSpec` then the User Interface Painter is opened using that method.
See also:
Application Models
Composing Windows
Composing Windows
Create a new canvas
Use a Canvas to "paint" the design for a window.
To open a new Canvas, click on the Canvas button in Launcher window toolbar, or select Tools->New Canvas . Paint the blank canvas by placing and arranging widgets on it.
See also:
Palette
Place a widget on a canvas
Modify an existing canvas
Modify an existing canvas
To modify an existing window specification, locate it using the Resource Finder (select Browse -> Resources in the Launcher window).
In the Resource Finder, select the class name containing the canvas, select the appropriate specification in the Resources view (`windowSpec` is the default), and click Edit button.
To save any changes you make to a canvas, you must Install it.
See also:
Install a canvas
Specify a user interface look
By default, the canvas will auto-select its look on the target platform. To change the current look of the canvas, select one of the options on the Look menu. Note that the global settings for VisualWorks will override this setting when you open the application.
See also:
Environment Settings
Test a new application window
Install a canvas
Use the Install... button in the GUI Painter Tool to create an interface specification for the canvas.
Specify the name of the application model class in the INSTALL on Class dialog; specify either a new or existing class for the application model.
Specify the selector to contain the interface specification for the canvas (the default selector is `#windowSpec` .)
Select OK to build and install the interface specification. If you enter a new class name, you will be prompted for the name of the superclass in a subsequent dialog (`ApplicationModel` and `SimpleDialog` are provided as defaults).
See also:
Create an application model
Set window properties
Use the property pages on the GUI Painter Tool to edit the properties of the current canvas.
Select on the background to deselect all widgets, to set the properties of the window itself.
See also:
Sizing Windows
Test a new application window
To launch the application under construction in a Canvas, click on the Open button in the GUI Painter Tool. This command is unavailable until you have created an application model for the canvas by installing the canvas.
See also:
Create an application model
Install a canvas
Creating Windows Programmatically
Opening a main window
When the spec name is `#windowSpec` , you can just send `open` to the application class:
Editor1Example open
To open any other spec, use `openWithSpec:` and provide the specification name:
Editor1Example openWithSpec: #windowSpec
Open a secondary window
When the same application model serves one or more secondary windows in addition to the main window, you can open a secondary canvas with this variant. A "secondary" window usually requires that the application has reached the proper state, that its instance variables have been initialized. This usually requires opening the main window before any secondary window.
This example creates a new UIBuilder the first time it is invoked, and stores that builder in an instance variable. When your application needs to access widgets on the secondary canvas later, storing this second builder assures you will have a means of accessing the widgets.
In a method in the application model, create a new `UIBuilder` .
Tell the builder which object will supply its menus, aspects, and other resources by sending it a `source:` message. The argument is typically the application model itself. (Alternatively, you can send a series of `aspectAt:put:` messages to install the resources directly.)
Create the spec object and add the spec to the builder.
Open the window.
openFinder "Open the Search window. If already open, raise to top."

| bldr | (self finderBuilder notNil and: [self finderBuilder window isOpen]) ifTrue: [self finderBuilder window raise] ifFalse: [ self finderBuilder: (bldr := UIBuilder new). bldr source: self. bldr add: (self class interfaceSpecFor: #finderSpec). bldr window application: self; beSlave. self adjustSearchScope. self searchStatus value: 0. (bldr componentAt: #searchStatus) widget setMarkerLength: 5.

bldr openAt: (self originFor: bldr window nextTo: #findButton)].

(self builder componentAt: #listView) takeKeyboardFocus.
Get a window from a builder
When you ask an application model to open an interface specification, the application model creates an interface builder, which in turn creates the specified window and its contents. Your application can manipulate the window by obtaining the window from the builder and then sending it messages.
Get the application builder, which is returned when the application is opened.
Get the window from the builder.
| bldr win | bldr := Editor2Example open. win := bldr window . win label: 'Editor'.
Online example: Editor2Example
Window Scrollbars
Add scrollbars to a window with the Canvas Tool
In the window's canvas, make sure no widget is selected.
Open a Properties Tool using the Properties button, or properties on the <Operate> menu in the canvas.
In the new Properties tool, choose the Details page, and enable the desired scrollbars.
Apply
the properties, close the Properties Tool, and Install the canvas.
Add/remove scrollbars programmatically
You can add and remove scrollbars programmatically, but only if the window had the scroll bars initially, as created in the canvas. To hide scroll bars initially, hide them before opening the window.
After opening the window, remove the scroll bars that are meant to be displayed later.
Ask the window's component to add scroll bars.
| win | win := ApplicationWindow new. win component: (BorderDecorator on: Object comment asComposedText). win open.

win component noVerticalScrollBar ; noHorizontalScrollBar . win display.

Cursor wait showWhile: [(Delay forSeconds: 5) wait].

win component useVerticalScrollBar ; useHorizontalScrollBar .
Window Menu Bar
Add a menu bar to a window with the Canvas Tool
Adding a menu bar has two parts: turning on a menu bar property for the window and creating the menu.
In the canvas for the window, make sure no widget is selected by clicking on the background of the canvas.
Select the Basic page in the GUI Painter Tool, and turn on the Enable switch for the Menu Bar property.
In the Menu field, enter the name of the menu-creation method defined for the menu using the Menu Editor.
Apply
the properties, close the Properties Tool, and Install the canvas.
Use the Menu Editor either before or after enabling the canvas.
Each first-level entry in the menu appears in the menu bar, but only when it has a submenu.
Window Appearance
Set a window's label
You can modify a window's label using the canvas or, for dynamic control, by sending a message to the window.
In the canvas for the window, make sure no widget is selected.
In the GUI Painter Tool, fill in the window's Label property with the desired label.
Apply
the properties and Install the canvas.
Set a window's icon
Under window managers that support iconified windows, you can assign an icon to replace the standard VisualWorks icon.
Get the window from its builder.
Send an `icon:` message to the window, with an icon as argument.
| helpIcon win | helpIcon := Icon figure: ToolbarIconLibrary thought20x20 image shape: ToolbarIconLibrary thought20x20mask image. helpIcon cleanFigure. win := (Editor2Example open) window. win icon: helpIcon.
Set the color of a canvas
Note: For look policies other than OS/2 or Windows, widgets inherit the background color of the window until you make them opaque and apply a different color to their backgrounds.
In the canvas for the window, make sure no widget is selected.
Open the Color page in the GUI Painter Tool.
Windows have four color zones: Foreground , Background , Selection foreground , and Selection background . By default, these are set to none , which means that the window inherits its colors (the window from the window manager). To change a color, select the desired color from the color chart and click on the zone you wish to define. To revert to none , click on the zone a second time. Use the slider controls to fine-adjust the color. Use the vertical slider to work with a color-cube model. Use the horizontal sliders to work with a hue-saturation-value model.
To display the policy for the most recently active canvas or currently selected widget, toggle the Read button. The policy is displayed in the color zones at the bottom of the tool.
Apply
the color properties, and Install the canvas.
See also:
Set a widget's colors
Sizing Windows
Set initial window size
While editing the canvas, use the window manager to adjust the canvas to the preferred size.
Make sure no widget is selected in the canvas. This selects the canvas itself.
Select the Layout->Window->Preferred Size in the Canvas Tool menu bar to set the initial size of the window.
Install
the canvas.
Constrain the window size
When the interface becomes unusable below a certain minimum size, or when larger than a certain maximum size, you can impose limits on the size.
While editing the canvas, use the window manager to resize the canvas to its intended minimum size.
Make sure no widget is selected in the canvas.
To set the minimum size, select the Layout->Window->Min Size .
To set the maximum size, select the Layout->Window->Max Size .
Install
the canvas.
Make a window's size fixed
Some windows work best at a constant size. To make the size fixed:
While editing the canvas, use the window manager to resize the canvas to its intended minimum size.
Make sure no widget is selected in the canvas.
To make this size fixed, select the Layout->Window->Fixed Size .
Install
the canvas.
Clear all window size constraints
In the window's canvas, make sure no widget is selected.
Select the Layout->Window->Clear All command in the canvas's menu.
Install
the canvas.
Get a window's dimensions
Get the window from its builder.
Send messages to the window asking for any desired dimension, using the messages shown in the example:
| win min max box width height | win := (Editor2Example open) window.

min := win minimumSize. max := win maximumSize. box := win displayBox. width := box width. height := box height.
Set a window's opening position
Build the window interface without opening it, by sending an `allButOpenInterface:` message to an instance of the application.
Get the window from the interface builder.
Send an `openIn:` message to the window with a rectangle specifying its origin and extent.
| bldr win | bldr := Editor2Example new allButOpenInterface: #windowSpec. win := bldr window. win openIn: (50@50 extent: win minimumSize).
Controlling a Window Programmatically
Set initial window size
Build an interface up to the point of opening the window.
Get the window from the interface builder.
Ask the window to open with a specified size (extent).
| bldr win | bldr := Editor2Example new allButOpenInterface: #windowSpec. win := bldr window. win openWithExtent: 500@220.
Online example: Editor2Example
Constrain window size
Get the window from its builder.
To set the minimum size, send a `minimumSize:` message with an extent to the window.
To set the minimum size, send a `minimumSize:` message with an extent to the window.
| bldr win | bldr := Editor2Example new allButOpenInterface: #windowSpec. win := bldr window. win minimumSize: 100@100; maximumSize: 500@300; open.
Change window size
Get the window from its builder.
Send a `displayBox:` message to the window with a rectangle specifying its size and location.
| bldr win | bldr := Editor2Example open. win := bldr window. win displayBox: (100@100 extent: 400@220).
Move an open window
Get the window from its builder.
Send a `moveTo:` message to the window to reset its origin to a specified point, using screen coordinates.
| win | win := (Editor2Example open) window. (Delay forSeconds: 1) wait. win moveTo: 300@50.
Set a window's label programmatically
Get the window from its builder.
Send a `label:` message to the window, with the new label as argument.
| win | win := (Editor2Example open) window. win label: 'Editor'.
Set a window's color
You can change window background color programmatically for run-time control. Foreground and selected foreground and background colors are set by widgets.
Get the window from its builder.
Send a `background:` message to the window, with the color as argument.
| win color | win := (Editor2Example open) window.

ColorValue constantNames do: [ :colorName | (Delay forMilliseconds: 200) wait. color := ColorValue perform: colorName. win background: color. win display]
Online example: Editor2Example
Refresh a window's display
Under normal conditions, a window redisplays its contents whenever those contents change or whenever an overlapping window is moved. Sometimes you need to redisplay a window programmatically, as when you want to display an intermediate state of the window before a drawing operation has been completed.
Get the window from its builder.
Send a `display` message to the window.
| win | win := (Editor2Example open) window. 5 timesRepeat: [ (Delay forMilliseconds: 400) wait. win display ].
Online example: Editor2Example
Get the active window
The `ScheduledControllers` object keeps track of all controllers, including the active controller. You can ask the active controller for its associated window.
The active window is the top component of the active controller's view.
| win | win := ScheduledControllers activeController view topComponent. win moveTo: 20@20.
Get the window at a location
When your application performs an operation on a window that is pointed to by the user (using the mouse), you can access the window.
Get the window at a point by sending a `windowAt:` message to the default `Screen` with the point as argument.)
| sensor pt window | sensor := ScheduledControllers activeController sensor. Cursor bull showWhile: [sensor waitButton]. pt := sensor globalCursorPoint.

window := Screen default windowAt: pt .

window component flash.
Close a window
When an application model is running one or more windows, you can close it (or all of them at once, if there is more than one) by sending `closeRequest` to the application.
| editor | editor := Editor2Example new. editor openInterface: #windowSpec. (Delay forSeconds: 1) wait. editor closeRequest .
Clean up before closing a window
When an application window has been asked to close, it first sends a `changeRequest` message to its application model. If the model answers `false` , the window won't close; if it answers `true` , the window proceeds to close itself. This gives the model a chance to take any cleanup actions before the window closes.
To specify cleanup actions, implement a `changeRequest` method in your application model which answers `true` when the window can close and `false` otherwise.
changeRequest ^super changeRequest ifFalse: [false] ifTrue: [(self modified or: [self magnifiedBitView controller updateRequest not]) ifTrue: [Dialog confirm: 'The image has been altered, but not installed. Do you wish to discard the changes?'] ifFalse: [true]]
Expand/collapse a window
Window managers typically provide a means of collapsing (iconifying) a window and expanding it back to its normal state. You can also control that behavior programmatically.
Get the window from its builder.
To minimize the window, send it a `collapse` message .
To restore the window, send it an `expand` message.
| win | win := (Editor2Example open) window. win display. (Delay forSeconds: 1) wait. win collapse . (Delay forSeconds: 1) wait. win expand .
Hide/show a window
Because a window requires extensive resources, it can be slow to open. When your application needs to open and close a window repeatedly, it may be better to hide, or `unmap` it, and then simply `map` it again to redisplay it.
Get the window from its builder.
To hide the window, send it an `unmap` message.
To show the window again, send it a `map` message.
| win | win := (Editor2Example open) window. win display. (Delay forSeconds: 1) wait. win unmap . (Delay forSeconds: 1) wait. win map .
Set Relations between Windows
Make a window a slave
In a multiwindow application, it is often useful to close all secondary windows when the main window closes. In this situation, the main window is called the
master window
and the secondary windows are called
slave windows
.
Get the master window from its builder.
Send the master window an `application:` message with an application as argument (usually itself), to tell the window which application model to inform of its events.
Send the master window a `beMaster` to make it master.
Get each slave window from its builder.
Send each slave window an `application:` message with an application as argument, to tell the window which application model will relay events from the master window.
Send each slave window a `beSlave` message to make it a slave.
| app masterWin slaveWin | app := Editor1Example new. masterWin := (app openInterface) window. masterWin label: 'Master'; application: app; beMaster .

slaveWin := (Editor2Example open) window. slaveWin label: 'Slave'; application: app; beSlave .
Make windows equal partners
Equal partner windows all close when any one of them closes.
Get each window from its builder.
Send an `application:` message, with an application as argument, to each window telling it which application will relay messages.
Send a `bePartner` message to each window.
| app win1 win2| app:= Editor1Example new.

win1 := (app openInterface) window. win1 label: 'Partner 1'; application: app; bePartner .

win2 := (Editor2Example open) window. win2 label: 'Partner 2'; application: app; bePartner .
Specify events to sent by windows
By default, master and partner windows broadcast `#close` , `#collapse` , and `#expand` events. You can remove any of those events and/or add `#bounds` , `#enter` , `#exit` , `#hibernate` , `#reopen` , and `#release` events.
Send a `sendWindowEvents:` message to a master or partner window, with an array of event symbols to send.
| app masterWin slaveWin | app := Editor1Example new.

masterWin := (app openInterface) window. masterWin label: 'Master'; application: app; beMaster; sendWindowEvents: #( #close #collapse #expand #hibernate #reopen).

slaveWin := (Editor2Example open) window. slaveWin label: 'Slave'; application: app; beSlave.
Specify events to be received by windows
By default, slave and partner windows respond to `#close` , `#collapse` , and `#expand` events. You can remove any of those events and/or add `#bounds` , `#enter` , `#exit` , `#hibernate` , `#reopen` , and `#release` events.
Send a `receiveWindowEvents:` message to a slave or partner window, with an array of event symbols to receive.
| app masterWin slaveWin | app := Editor1Example new.

masterWin := (app openInterface) window. masterWin label: 'Master'; application: app; beMaster.

slaveWin := (Editor2Example open) window. slaveWin label: 'Slave'; application: app; beSlave; receiveWindowEvents: #( #close).
Create Custom View Classes
Create a view class
A view displays text or graphics representing all or part of a data model. Each of the existing widgets uses a view to display a data model. When an existing widget does not serve your purpose, you can create a custom view.
In a System Browser, display the class-definition template by selecting a class category and making sure no class is selected.
In the template, specify the name of the view's superclass (in the example, `UI.View` ).
Replace `"NameOfClass"` with the new class's name (`SketchView` ).
Supply variable names, if any, and then accept the definition.
Smalltalk.UI defineClass: #SketchView superclass: #{UI.View} indexedType: #none private: false instanceVariableNames: '' classInstanceVariableNames: '' imports: '' category: 'Examples-Help'
Online example: SketchView
Connect a view to a domain model
As a subclass of `DependentPart` , every view inherits an instance variable for storing its model. Sending a `model:` message to the view, typically when the view is created, stores the model in this instance variable, where it can be accessed easily.
A side effect of the `model:` message is that the view is registered as a dependent of the model. This link sets the stage for the view to update its display when the model changes.
Tell the view which object to use as its domain model. This is done in an initialization method or, as in the example, the application model (`CustomViewExample` ) can notify the view whenever the domain model changes.
changedSketch self sketchView model: self sketches selection.
If the view needs to take action when its model is changed, such as redisplaying itself, override the inherited model: method (as in `SketchView` ).
model: aModel super model: aModel. self invalidate.

"Tell the controller where to send menu messages." self controller performer: aModel.
Online example: CustomViewExample , SketchView
Define a view's contents
A view's purpose is to display text or graphics. It does so in a method named `displayOn:` , which is sent to the view whenever circumstances require that it update its display.
It displays the text and/or graphics on a `GraphicsContext` , which is an object that windows and other display surfaces use for rendering objects.
In a displaying protocol, add a `displayOn:` method to the view. The argument is a `GraphicsContext` .
In the `displayOn:` method, get the required data from the model (in the example, a set of line segments, each represented as a collection of points).
In the `displayOn:` method, display the appropriate text or graphics, based on the data from step 2 (in the example, each collection of points is displayed as a `Polyline` ).
displayOn: aGraphicsContext self model isNil ifTrue: [^self].

self model strokes do: [ :stroke | aGraphicsContext displayPolyline: stroke].
Online example: SketchView
Update a view when its model changes
When the domain model changes its state, it is responsible for notifying all of its dependents. It does so by sending a variant of the `changed:with:` message to itself. The first argument is a `Symbol` indicating what was changed, and the second argument is the new value.
The `changed:with:` message is inherited, and it sends an `update:with:` message to each dependent, passing along the same two arguments.
In any method in the domain model that changes the model in a way that affects the view, send a variant of the `changed:with:` message to the model. (In the example, `Sketch` sends three such messages, one when it adds a point and the others when it erases some or all of its contents.)
add: aPoint "Add aPoint to the current stroke."

self strokes last add: aPoint. self changed: #stroke with: self currentLineSegment.

eraseLine "Erase the last stroke that was drawn."

self strokes isEmpty ifFalse: [ self strokes removeLast. self changed: #erase with: nil].

eraseAll "Erase my contents."

self strokes removeAll: self strokes copy. self changed: #erase with: nil.
In the view, implement a variant of the `update:with:` method to take the appropriate action in response to a change in the model. (In the example, the same `update:with:` method responds to either of the `changed:with:` messages sent by the model.)
update: anAspect with: anObject "When a point is added to the model..." anAspect == #stroke ifTrue: [anObject asStroker displayOn: self graphicsContext].

"When the model erases its contents..." anAspect == #erase ifTrue: [self invalidate].
Online example: Sketch , SketchView
Connect a view to a controller
An active view, one that responds to mouse or keyboard input, uses a controller to process mouse and keyboard input. A view is closely allied with its controller, so an inherited mechanism installs the desired controller when the view is created. You can control which type of controller is installed.
Create a `defaultControllerClass` method for the view. This method returns the name of the desired controller class.
defaultControllerClass ^SketchController
Online example: SketchView , SketchController
Make a view passive
Return `NoController` from the `defaultControllerClass` method:
defaultControllerClass ^NoController
Redisplay an entire view
Send `invalidate` to a view to cause the entire view to be treated as a damage rectangle, and so be repainted.
Send `invalidate` to the view. This is typically done in a view method that changes the model (as in the example).
model: aModel super model: aModel. self invalidate.

"Tell the controller where to send menu messages." self controller performer: aModel.
Online example: SketchView
Redisplay part of a view
Send `invalidateRectangle:` to the view. The argument is a rectangle that represents all or part of the view's bounding box. The bounding box can be accessed by sending `bounds` to the view.
Redisplay a view immediately
Send `invalidateRectangle:repairNow:` to the view. The first argument is a rectangle that represents all or part of a view's bounding box. The second argument is `true` when immediate redisplay is desired, and `false` for the default behavior.
Add a view to an interface
A view-holder widget is provided on the Palette for integrating a custom view into a canvas. This view holder enables you to treat your custom view like a standard widget in that you can paint its layout and apply borders and scroll bars. However, your application is responsible for connecting the view to a domain model.
Use a Palette to place a view-holder widget on the canvas.
In the view holder's `View` property, enter the name of the application-model method that supplies an instance of the desired view (`sketchView` ).
If the application model will need to access the custom view while the application is running, use a System Browser to create an instance variable (`sketchView` ) in which to store the custom view.
Create the application-model method that you named in step 2 (`sketchView` ). This method typically answers the contents of the instance variable that you created in step 3.
sketchView ^sketchView
In an `initialize` method in the application model, create an instance of the custom view. If appropriate, connect the custom view to a data model. (In the example, there is no model to be connected until the user adds the first `Sketch` object.)
initialize sketches := SelectionInList with: OrderedCollection new. sketches selectionIndexHolder onChangeSend: #changedSketch to: self.

sketchView := SketchView new.
Online example: CustomViewExample , SketchView
Menus
Open a menu resource
To edit menu resource methods in the Menu Editor, use the Menu->Load... command.
In the LOAD from Class dialog, specify the name of an application model.
Either click OK or use the <Select> button to click in the Selector: view. A list of selectors will appear that you can choose from.
Choose a particular menu and click OK . The menu will be loaded into the Menu Editor.
To edit menu resource methods starting in the Resource Finder, browse to a specific method and use the Edit command.
Open a menu widget
To edit a menu that has been applied to a widget, use the Menu->Read command.
Create a new menu
To clear the current menu and create a new one, use the Menu->New command.
See also:
Create a new menu item
Install a menu
Use the Menu->Install... command to create a specification for the menu.
Specify the name of the application model class in the INSTALL on Class dialog; you may specify either a new or existing class for the application model.
Specify the selector to contain the interface specification for the menu; you may specify either a new or existing selector.
Select OK to build and install the menu specification. If you enter a new class name, you will be prompted for the name of the superclass in a subsequent dialog (`ApplicationModel` and `SimpleDialog` are provided as defaults).
Apply a menu to a widget
Use Menu->Apply... to install the menu directly into the (single) selection on the currently active canvas. The selected widget must have a menu property. If there is no current selection in the active canvas, the menu bar for the canvas is treated as being selected.
See also:
Open a menu widget
Install a menu bar on a canvas
Use Menu->Apply... to install the menu as a menu bar on the currently active canvas. To enable the Menu->Apply... command:
The menu being applied must be fully hierarchical. Use Edit->New Item and Edit->New Submenu Item to build the menu hierarchy.
Set the Menu Bar property for the canvas (click on the Properties button in the Canvas Tool, enable Menu Bar on the Basics page, and then select Apply & Close .)
Select Menu->Apply... in the Menu Editor. A dialog will prompt to confirm and/or supply the class and selector for the menu bar resource method.
Create a new menu item
Use Edit->New Item to create a new item at the top level of the menu. To create a new item at a sub-level, select the desired level before clicking Edit->New Item .
In the right-hand view of the Menu Editor, select the Basic properties page. Use the Label property to specify the text of the menu item, and the Value property to specify the method that performs the menu item's action.
See also:
Set a menu action
Create a hierarchical menu item
Use Edit->New Submenu Item to create a new sub-menu item. The new item will be created in a level below the currently selected element (if any).
See also:
Set a menu action
Remove a menu item
Use Edit->Delete to remove the currently selected menu item.
Set a menu action
In the right-hand view of the Menu Editor, select the Basic properties page. Use the Value property to specify the name of the method in the application model that performs the menu item's action.
NOTE: The Menu Editor does not support the use of BlockClosures as menu actions; to use these, you must modify the action methods manually.
Change the hierarchy of a menu
To move the currently selected item (and all of its submenus) in the menu hierarchy, use Move -> Up to shift it one level up and Move->Down to shift it one level down.
To make the currently selected menu item a submenu of the item above it, use Move->Right .
To move the selected menu item one level out in the menu hierarchy, use Move->Left .
Create a keyboard shortcut
Edit the menu properties to add a keyboard shortcut:
Select the menu item in the left-hand view. In the right-hand view, click on Details with the <Select> button.
Specify the Shortcut character on the Details page.
To invoke the keyboard shortcut, press and hold the <Alt> key while pressing the shortcut character.
Alternately, use the " & " character in the Label field on the Basic properties page. The character in the menu label that is preceded by an " & " character will function as a mnemonic key; e.g., entering &cut in the label field will set " c " as the shortcut key for that item.
Create a graphical menu item
To add a graphical image to a menu item, edit the menu properties:
Select the menu item in the left-hand view. In the right-hand view, click on Details with the <Select> button.
Specify the name of the class and resource method in the Label image files on the Details page. The label image resource may be created and installed using the Image Editor.
Set a toggle switch for a menu item
Edit the properties for a menu item to enable and disable it programmatically:
Select the menu item in the left-hand view. In the right-hand view, click on Defaults with the <Select> button.
Set the On/Off indicator property to control whether a check box appears next to the name of the menu item. Set the Initially enabled property to control the initial state of the menu item. Use Initially hidden to visually remove the menu item.
Set a menu item to be accessible to code
Edit the properties for a menu item to make it programmatically accessible:
Select the menu item in the left-hand view. In the right-hand view, click on Basic with the <Select> button.
Specify a Smalltalk symbol in the ID property. Use this symbol to refer to the menu item.
Check the appearance of the menu
Use the View and Test commands to preview the menu.
Use View->Sample Menu Bar to check the appearance of a menu bar.
Use Test to check the appearance of a pull-down or pop-up menu.
Toolbars
Add a tool bar
To use a tool bar, add the following parts to your
application
:
Create
resource method
s containing the button images (See: Create a tool bar button graphic).
Create a tool bar menu that identifies the button images with specific actions (See: Create a tool bar menu).
Create an
application class
method that reimplements `interfaceSpecFor:` (See: Install the tool bar specification).
Enable the tool bar to be shown in the window from the canvas Property Tool (See: Show the tool bar in a window).
Create a tool bar button graphic
Use the Image Editor to create tool bar button graphics and save them as resource methods.
NOTE: the tool bar height will be the height of the largest image for any of the tool bar buttons. It is not necessary to add a decorative border to the image since the tool bar button object will provide its own.
See also:
Menus
Create a bitmap image
Create a tool bar image without a background
Create a tool bar image to show enablement
Create a tool bar image without a background
To make the background of the button image invisible, either choose the image background to be the same color as the button or create an `OpaqueImage` that masks the background from the image. An `OpaqueImage` contains the image and a mask that uses two colors, black and white, to indicate opaque and transparent regions respectfully. Use the Image Editor to create a mask for your button image.
Create new
resource method
s for the button image, the mask, and the composite `OpaqueImage` .
Open an Image Editor by selecting Tools->Image Editor from the Launcher.
From the Image Editor select Image->Load . Enter `ToolCommandExample` and select `openIconImage` from the selector list.
Select the color white from the Image Editor palette.
Select Draw->Replace Color from the Image Editor menu bar then click on the background of the image. This changes the image background to all white.
Select Draw->Color to B&W . This changes all off white colors black. Touch up the image as necessary, changing white pixels on the interior of image if you want these areas to not appear transparent.
Select Image->Store B&W Mask from the menu bar.
Install the resulting mask to your application as a class resource method. Name it `openIconMask` .
Create a new application class method that answers an `OpaqueImage` using the `figure:shape:` message and provide as arguments the image and mask for the icon.
openIcon ^OpaqueImage figure: self openIconImage shape: self openIconMask.
Online example: ToolCommandExample
See also:
Create a bitmap image
Create a tool bar button graphic
Create a tool bar image to show enablement
To create tool bar buttons which can be toggled between an enabled and disabled state, use instances of `OpaqueImageWithEnablement` instead of the `Image` or `OpaqueImage` . An `OpaqueImageWithEnablement` includes a separate mask to show the disabled image. In most cases you should use the first mask for the second disablement mask.
openIcon ^OpaqueImageWithEnablement figure: self openIconImage shape: self openIconMask. inactiveMask: self openIconMask.
Online example: ToolCommandExample
See also:
Create a bitmap image
Create a tool bar button graphic
Create a tool bar image without a background
Create a tool bar menu
Use the Menu Editor to create the tool bar menu. A tool bar button is associated with each menu item you create.
Set the following properties for each menu item:
Select the Basic page of the properties editor.
Enter a label in the Label field.
In the Value field, enter the method name to perform the action when the item is selected.
If the tool bar button is to be programmatically enabled or hidden, enter the unique symbol to identify this item in the ID field.
On the Details page, enter an image accessor method name in the Selector field. If the accessor is not in your application class or its superclass, enter the class name where this method resides in the Class field.
Also on the Details page, ensure that the item has initial enablement and visiblity set.
Do not add no submenu items. To create a separation distance between tool bar buttons, add a line in the specification. Once the specification is completed install it to a class resource method in your application.
For an example of how to create a tool bar programmatically, look at the method toolBar in the online example `ToolCommandExample` .
Online example: ToolCommandExample
See also:
Menus
Install the tool bar specification
To install the tool bar you will need to reimplement `interfaceSpecFor:` .
On the class side of your application, include the following method:
interfaceSpecFor: aSymbol | spec | spec := super interfaceSpecFor: aSymbol. aSymbol == #windowSpec ifTrue: [spec window toolBar: #toolBar]. ^spec
Online example: ToolCommandExample
Show the tool bar in a window
To show a tool bar the menu bar options must be enabled.
Open the Canvas Tool on your application and open the Properties Tool on the window.
On the Basics page, check the box labeled Enable within the Menu Bar box. The Menu field may be left blank if you are not including a menu bar in the window.
Enable or disable individual buttons programmatically
Send the message `componentAt:` to access the tool bar object from the application builder; supply the `ID` symbol for the tool bar as the argument. Use `componentAt:` to access any button from the tool bar object, again using the `ID` symbol of the button object. To change the state of these button objects, use the messages `enable` or `disable` .
configureMenu "Disable or enable the menu items depending on whether a file is selected."

| menu submenu toolBar | menu := self builder menuAt: #fileMenu. submenu := (menu menuItemLabeled: 'File') submenu. toolBar := self builder componentAt: #toolBar.

self files selection isNil ifTrue: [ (submenu menuItemLabeled: 'Open') disable. (submenu menuItemLabeled: 'Delete') disable. (toolBar componentAt:#openIcon) disable. (toolBar componentAt:#deleteIcon) disable] ifFalse: [ (submenu menuItemLabeled: 'Open') enable. (submenu menuItemLabeled: 'Delete') enable. (toolBar componentAt:#openIcon) enable. (toolBar componentAt:#deleteIcon) enable]
Online example: ToolCommandExample
Bitmap Images
Create a bitmap image
The Image Editor allows you to draw a bitmap image and store it in a compilable resource method. The Image Editor is best suited for producing small images, such as for cursor shapes or icons.
To use the Image Editor, you must have the `UIPainter` parcel loaded.
Open an Image Editor, for example, by choosing Tools->Image Editor from the Launcher window.
Paint the desired image in the scrollable pixel grid. To do this, click on a color and then click on each pixel to be painted that color.
Use the Image Editor's Install button to a create method that returns the image, typically a class method in a `resource` protocol of an application model class.
Capture an image from the screen
In an Image Editor, choose the Image->Capture command. The cursor changes to a cross-hair.
Press the <Select> mouse button at the upper-left corner of the desired rectangle, drag to the lower-right corner, and then release the mouse button.
If desired, edit the captured image by changing the color of individual pixels.
Use the Image Editor's Install button to create a method that returns the image, typically a class method in a `resource` protocol of an application model class.
Edit an image resource
To edit image and icon resource methods in the Image Editor, browse to a specific method and use the Edit command in the Resource Finder.
Create an image from a ".bmp" file
See also:
Load an image from a file
Display a masked image on a label or button
A graphic label may be displayed in place of text for an action button, radio button, check box, or label.
Select the action button, radio button, check box, or label in the canvas and open the Properties Tool.
Turn on the widget's Label is Image property.
In the Message property field, enter the name of the method that returns a graphic image (usually a
resource method
).
Apply
the properties and Install the canvas.
Drawing with the Image Editor
To draw by hand, use the <Select> button in the gridded portion of the Image Editor. The currently selected color will be used to draw on the image. To select different colors, use the palette in the lower-right corner of the view.
Use Edit->Cut , Copy , Paste , Accept and Cancel to manipulate regions of the image; none of these commands make permanent changes until the image is installed.
To color larger regions of the image, use the Draw->Fill Rectangle command. Click the <Select> button once on the grid to set an origin point, and a second time to set an extent. The specified rectangle will be filled with the currently selected color.
Change colors using the Image Editor
To change all occurrences of a single color, use the Draw->Replace Color command. Select a new color from the Image Editor's palette, and the use the <Select> button to pick a color in the image. The new color will replace the color that you select in the image.
To convert a color image to black & white, or to produce a mask, use the Draw->Color to B&W command.
See also:
Produce a mask with the Image Editor
Produce a mask with the Image Editor
To convert a color image into a mask, use the Draw->Color to B&W command in the Image Editor. Any non-white colors will be converted to black.
To convert a color image into a mask only when installing it in an application model, enable Image->Store B&W Mask , and then use the Image->Install... .
Enabling Image->Store B&W Mask will change the Image Editor palette to display only black and white buttons; use these buttons to select the mask and transparency colors.
Install an image as a resource
Use the Image->Install... command in the Image Editor to create an interface specification for the image.
Specify the name of the application model class in the INSTALL on Class dialog. Either a new or an existing class may be entered for the application model.
Specify the selector to contain the interface specification for the image (e.g., `#paletteIcon` .)
Select OK to build and install the interface specification. If you enter a new class name, you will be prompted for the name of the superclass in a subsequent dialog (`ApplicationModel` and `SimpleDialog` are provided as defaults).
Application Models
Browse an application model
To browse the application model for the current canvas, make sure no widget is selected and click on the Browse... button in the Canvas Tool (or select browse on the canvas <Operate> menu.) A hierarchy browser will open on the application model class.
Create an application model
Create a new application model for your canvas using the Install... button (or install... on the <Operate> menu.)
Specify the name of the application model class in the INSTALL on Class dialog; specify either a new or existing class for the application model.
Specify the selector to contain the interface specification for the canvas (the default selector is `#windowSpec` .)
Select OK to build and install the interface specification. If you enter a new class name, you will be prompted for the name of the superclass in a subsequent dialog (`ApplicationModel` and `SimpleDialog` are provided as defaults).
Once you have finished the layout of the new user-interface, you will typically edit the behavior of the application model using the System Browser.
Browse an accessor method
To browse the methods that access the
value model
for a widget (an aspect of the application model), select the desired widget and click on the Browse... button in the Canvas Tool (or select browse on the canvas <Operate> menu.) A method list browser opens with each method in the application model that refers to the relevant instance variable.
See also:
Create a value model
Create a value model
To define a
value model
for a particular widget:
Install the canvas in an application model.
Open a Properties Tool and define the widget's Aspect .
Click the Define button in the Canvas Tool to open a Define Dialog. Confirm the aspects and actions that you want to create in the application model.
Toggle the check mark next to each item on the list that you do not want to re-define (the Define command will overwrite each accessor method indicated with a check mark.)
To add minimal initialization code for each aspect, select the Add Initialization check box.
See also:
Defining user interface support
Browse an accessor method
Defining User Interface Adaptors
Defining user interface support
Use the Define Dialog to user interface support methods in an application model. After using the Canvas and Properties tools to compose and configure an interface, use the Define Dialog to create
information holders
(instance variables),
actions
(access methods), and
initialization methods
.
Open a Define Dialog
To open the define dialog, select a widget on the canvas and select Define... from the <Operate> menu or on the Canvas Tool's menu bar.
To control the scope of the define dialog, you can select either a single widget, a group of widgets, or none (which is equivalent to selecting all.)
Create actions and information holders
The Define Dialog lists all the actions (access methods) and information holders (instance variables) referenced in the selected interface components.
To create actions and information holders for every element on the list, click on OK . To skip any item on the list, deselect it by clicking to remove the check mark next to the item (using the <Select> button.)
Create initialization code
To create a new initialization method in the application model, enable the Add Initialization switch in the Define Dialog. When this switch is selected (the default), the define dialog will create a method named `initialize` that assigns default values to the various information holders (instance variables) in the Application Model.
Adapting Domain Models to Widgets
Set up a simple value model (ValueHolder)
Send an `asValue` message to the data object that is to be contained (in the example, the number 0 is asked to return a value holder containing itself).
initializeID accountID := 0 asValue. accountID onChangeSend: #changedID to: self.
Online example: Adaptor1Example
See also:
Create a value holder on an empty string
Create a value holder on an empty string
Online example: Adaptor1Example
Send a `newString` message to the `ValueHolder` class. This is equivalent to the expression `String new asValue` , and the choice of which to use is a matter of personal preference.
initializeName name := ValueHolder newString. name onChangeSend: #changedName to: self.
Online example: Adaptor1Example
See also:
Create a boolean value holder
Create a decimal value holder
Create a boolean value holder
Send a `newBoolean` message to the `ValueHolder` class. This is equivalent to the expression `false asValue` .
Create a decimal value holder
Send a `newFraction` message to the `ValueHolder` class. This is equivalent to the expression `0.0 asValue` .
Adapt part of a domain model (AspectAdaptor)
Data widgets are commonly used for presenting data that is held by some object in the domain model. In such cases, the appropriate value model is an `AspectAdaptor` , which is a reference to remote data.
An aspect adaptor has a subject, which is the relevant domain model, and an aspect, which is the name of the instance variable that holds the relevant data.
This example below shows how to set up an aspect adaptor with a subject channel, a value holder from which the aspect adaptor will obtain its subject.
In an `initialize` method in the application model, initialize an instance variable (`selectedCustomer` ) with a value holder that holds the domain model (a `Customer1Example` ).
In an aspect method (`accountID` ), send a `subjectChannel:` message to the `AspectAdaptor` class. The argument is the value holder you created in step 1.
Tell the aspect adaptor which aspect of the domain model to monitor by sending a `forAspect:` message to the adaptor. The argument is a `Symbol` , typically the name of the desired instance variable (`accountID` ) in the domain model.
initialize customers := SelectionInList new. customers selectionIndexHolder onChangeSend: #changedCustomer to self.

selectedCustomer := Customer1Example new asValue accountID | adaptor | adaptor := AspectAdaptor subjectChannel: self selectedCustomer. adaptor forAspect: #accountID.

adaptor onChangeSend: #redisplayList to: self. ^adaptor
Online example: Adaptor2Example
Address an adapter subject
Send a `subject:` message to the `AspectAdaptor` class (instead of a `subjectChannel:` message). The argument is a domain model (instead of a value holder containing the domain model).
Use non-standard accessors
Use these steps when a domain model provides accessor methods whose names are different from the instance variable they access-- for example, when the aspect is an instance variable called `income` , and its accessors are `getIncome` and `putIncome:` .
After creating the adaptor, send an `accessWith:assignWith:` message to it. The first argument is the name of the domain model's method that accesses the desired value. The second argument is the name of the method that assigns a new value. (In the example, the message names are `address` and `address:` , and they access an instance variable named `address` , so the same effect can be achieved via `forAspect: #address` ).
address | adaptor | adaptor := AspectAdaptor subjectChannel: self selectedCustomer. ^adaptor accessWith: #address assignWith: #address:
Online example: Adaptor2Example
Monitor programmatic changes
Send a `subjectSendsUpdates:` message to the adaptor with the argument `true` . This causes the adaptor to register itself as a dependent of the subject (in the example, a `Customer1Example` ).
In the domain model class (`Customer1Example` ), edit every method that alters the data value directly (that is, without going through the adaptor), so that it sends a `changed:` message to `self` . The argument is the aspect that has been changed (`#phoneNumber` ). This causes the dependent adaptor to be notified when the domain model makes the relevant change.
phoneNumber | adaptor | adaptor := AspectAdaptor subjectChannel: self selectedCustomer. adaptor forAspect: #phoneNumber.

adaptor subjectSendsUpdates: true.

^adaptor

formatUSPhoneNumber | rawPhone rawSize areaCode prefix suffix separator | rawPhone := self phoneNumber select: [ :ch | ch isDigit]. rawSize := rawPhone size. areaCode := ''. prefix := ''. suffix := ''. separator := '-'.

rawSize > 0 ifTrue: [areaCode := rawPhone copyFrom: 1 to: (3 min: rawSize)]. rawSize > 3 ifTrue: [prefix := separator, (rawPhone copyFrom: (4 min: rawSize) to: (6 min: rawSize))]. rawSize > 6 ifTrue: [suffix := separator, (rawPhone copyFrom: (7 min: rawSize) to: (rawSize min: 10))].

self phoneNumber: areaCode, prefix, suffix. self changed: #phoneNumber.
Online example: Adaptor2Example and Customer1Example
Synchronize updates (buffering)
Frequently, it is useful to delay updating a particular widget's value until other widgets in the same series are ready to be updated. Using a `BufferedValueHolder` enables you to arrange a trigger channel that is monitored by all of the widgets in the series.
The trigger channel is a value holder that contains `true` or `false` . When `true` , all of the dependent adaptors update the model. Putting `false` in the trigger channel discards the buffered values, canceling the update.
In the application model, create an instance variable (in the example, `updateTrigger` ) to contain the true/false value that triggers updates.
Create an accessing method for the variable.
updateTrigger ^updateTrigger
Initialize the variable to a value holder containing `false` .
initialize customers := SelectionInList new. customers selectionIndexHolder onChangeSend: #changedCustomer to: self.

selectedCustomer := Customer1Example new asValue.

updateTrigger := false asValue.
For each widget in the series, place the widget's value model in a `BufferedValueHolder` by sending a `subject:triggerChannel:` message to the `BufferedValueHolder` class. The first argument is the widget's value model (in the example, an `AspectAdaptor` ). The second argument is the trigger channel (`updateTrigger` ).
accountID | adaptor bufferedAdaptor | adaptor := AspectAdaptor subjectChannel: self selectedCustomer. adaptor forAspect: #accountID.

bufferedAdaptor := BufferedValueHolder subject: adaptor triggerChannel: self updateTrigger.

^bufferedAdaptor
Provide a button, a menu command, or other device with which the user can indicate that the series of values have all been edited as much as necessary (in the example, completion is indicated using an `OK` button that triggers an `accept` action).
In the action method (`accept` ), send a `value:` message to the trigger channel (`updateTrigger` ). The argument is `true` .
accept self updateTrigger value: true.

self redisplayList.
Online example: Adaptor3Example
Discard the buffered values
Send a `value:` message to the trigger channel (`updateChannel` ). The argument is `false` . This is typically done after confirming that the user wants to abandon the edited data.
changedCustomer | chosenCustomer selector | chosenCustomer := self customers selection.

chosenCustomer isNil ifTrue: [ self selectedCustomer value: Customer1Example new. selector := #disable] ifFalse: [ self selectedCustomer value: chosenCustomer. selector := #enable].

"Discard changes that were not OK'd." self updateTrigger value: false.

"Enable/disable selection-sensitive widgets." #( #accountID #name #address #phoneNumber #ok) do: [ :componentName | (self builder componentAt: componentName) perform: selector].
Online example: Adaptor3Example
Adapt a collection (SelectionInList)
A list or notebook widget is designed to work with a `SelectionInList` , which contains a value holder for holding the collection to be displayed. When the domain model supplies a simple collection, you can set up a `SelectionInList` to adapt to it, as shown in the basic step.
In the example below, the application model is `Adaptor4Example` , and the domain model is a `Customer2Example` , which holds an `OrderedCollection` of customers.
Put the domain model's collection in a `SelectionInList` by sending an `adapt:aspect:list:selection:` message to the `SelectionInList` class. The `adapt:` argument is the domain model (in the example, `collectionModel` ). The `aspect:` argument is typically the name of the domain model's collection variable. The `list:` argument is the name of the domain model's method that returns the collection. The `selection:` argument is the name of the domain model's method that sets the selection in the collection.
initialize collectionModel := Customer2Example new. customers := SelectionInList adapt: collectionModel aspect: #customers list: #customers selection: #selectedCustomer:.

customers selectionIndexHolder onChangeSend: #changedCustomer to: self.

selectedCustomer := Customer1Example new asValue.
Online examples: `Adaptor4Example` and `Customer2Example`
Adapt a collection element
Sometimes a widget is used to display a single element in a collection.
An `IndexedAdaptor` has a subject (the collection) or subject channel (when the collection is in a value holder) and an index number (the position of the desired element in the collection).
Send a `subjectChannel:` message to the `IndexedAdaptor` class, with the value holder containing the collection as the argument. If the collection is not contained in a value holder, send a `subject:` message instead, with the collection as the argument.
Send a `forIndex:` message to the adaptor. The argument is the position of the desired element in the collection.
xAxis | adaptor | adaptor := IndexedAdaptor subjectChannel: self vector. ^1adaptor forIndex: 1
Online example: Adaptor5Example
Create a custom adaptor (PluggableAdaptor)
Occasionally it is convenient to use a custom adaptor that performs a block of actions each time its value is accessed or changed. A `PluggableAdaptor` provides this flexibility.
A `PluggableAdaptor` takes three blocks, which enable it to perform custom actions at three junctures in the flow of communications between the widget and the domain model:
The `getBlock:` controls what happens when a value is fetched from the model by the widget.
The `putBlock:` controls what happens when a value is sent to the model by the widget.
The `updateBlock:` controls when the widget updates itself based on an update message sent by the model.
Create the custom adaptor by sending an `on:` message to the `PluggableAdaptor` class. The argument is the domain model.
Send a `getBlock:putBlock:updateBlock:` message to the adaptor. The first block takes one argument: the domain model. The second block takes two arguments: the model and the value to be assigned. The third block takes three arguments: the model, the aspect of the model that was changed, and a parameter that corresponds to the argument of a `changed:with:` message, when applicable.
initialize accountID := 1. paddedID := PluggableAdaptor on: self. paddedID getBlock: [ :model | | paddedString | paddedString := model accountID printString. 6 - paddedString size timesRepeat: [paddedString := '0', paddedString]. paddedString] putBlock: [ :model :value | model accountID: value asNumber] updateBlock: [ :model :aspect :parameter | false]
Online example: Adaptor6Example