Widgets
Widget Basics
Place a widget on a canvas
Use the Palette to choose the widget you would like to add to the canvas, and then click once on the canvas. A representation of the widget appears at the cursor point; click a second time to drop it into place on the canvas.
To place multiple copies of the same widget, use the
multi-place
button (upper-right corner of the Palette Tool). With this button on, each subsequent click on the canvas will generate a new copy of the selected widget.
Change the size of a widget
To modify a resizable widget, drag its selection handles to resize it on the canvas (note that check boxs, labels, and radio buttons cannot be resized by dragging.) To make a multiply-selected group of widgets all equal in height, width, or both, use the Arrange->Equalize... command in the GUI Painter Tool menu (or one of the iconic equalize buttons.)
To arrange for a widget to adjust its size in relation to that of the window, use the Layout->Relative command in the Canvas Tool menu. Both the horizontal and vertical dimensions of the widget are adjusted when the widget size is set to be Relative .
To set a widget's size to be fixed in relation to that of the window, use the Layout->Fixed command in the Canvas Tool.
Set a widget's boundaries
To convert an unbounded widget into a bounded one, use the Layout->Be Bounded command in the GUI Painter Tool menu.
Alternately, select the Bounded button in the GUI Painter Tool, Position page:
To reverse the operation, use the Layout->Be Unbounded command in the Canvas Tool menu.
Set a widget's properties
To focus the GUI Painter Tool on a particular widget, select the widget. The GUI Painter Tool can also be used to set properties on a group of widgets, or a multiple selection.
Positioning Widgets
Position a widget
Use click-and-drag to position a widget on the canvas. To toggle a fence around the edge of the canvas, preventing any element from being dragged outside its bounding box, use the Grid -> Keep In Window command in the GUI Painter Tool.
To arrange for a widget to adjust its position in relation to that of the window, use the Layout->Relative command in the Canvas Tool menu. Both the horizontal and vertical positioning of the widget are adjusted when the widget location is set to be Relative .
Align a group of widgets
To adjust the alignment of widgets on the canvas, select a series of widgets, and use the Arrange->Align... command in the <Operate> menu.
In the Align dialog, use on horizontal line when aligning widgets side-by-side, and on vertical line when aligning widgets in a column. Select first selection to align the widgets using the first selected widget; select merged box to align the widgets using the merged bounding box of all the selected widgets. Widgets are aligned through edges or centers.
See also:
Adjust the space between widgets
Adjust the space between widgets
To adjust the spacing of widgets on the canvas, select a series of widgets, and use the Arrange->Distribute... command in the <Operate> menu.
In the Distribute dialog, use left to right when spacing widgets side-by-side, and top to bottom when spacing widgets in a column. Specify whether widgets should be spaced between edges or centers. For constant spacing between edges you must specify the number of pixels to place between each pair of widgets.
See also:
Align a group of widgets
Group a number of widgets
To group a number of widgets, use <Shift> click or click-and-drag to select them, and then select Arrange->Group in the <Operate> menu.
To ungroup the widgets, select Arrange->Ungroup .
Make a widget's origin fixed
Making a widget fixed is useful when the window's size is fixed. When the window's size is variable, this approach works best for a button or other fixed-size widget that is located along the left or top edges of the window.
Select the widget and go to the Position page in the GUI Painter Tool.
Set the ( L )eft and ( T )op Proportions to 0.
Set the ( L )eft and ( T )op Offsets to the desired distance from the window's left and top edges, specified in pixels.
Set the ( R )ight and ( B )ottom proportion and offset parameters to give the desired size and scaling behavior:
For a fixed-size widget, set right and left, and top and bottom proportions equal, and the size specified by offsets.
For a variable size widget, set the right and bottom proportions larger than the left and top, and make additional size adjustments with offsets.
Apply
the properties.
Online example: Size1Example (start the example and then resize the window to see the effect)
Make a widget's origin relative
To allow the size of widgets to scale as the window changes size, use a relative origin.
This causes the widget to move farther away from the left and/or top edges of the window when the window grows, and closer when the window shrinks.
Select the widget and go to the Position page in the GUI Painter Tool.
Set the ( L )eft and ( T )op Proportions to the scaling factor for the widget to the window. This sets vertical and horizontal baselines for the widget's orgin.
Set the ( L )eft and ( T )op Offsets to shift the widget from that vertical and horizontal baselines.
Set the ( R )ight and ( B )ottom proportion and offset parameters to give the desired size and scaling behavior:
For a fixed-size widget, set right and left, and top and bottom proportions equal, and the size specified by offsets.
For a variable size widget, set the right and bottom proportions larger than the left and top, and make additional size adjustments with offsets.
Apply
the properties.
Online example: Size1Example
Position a widget relative to the right or bottom edge
You can fix a widget's position relative to the right and/or bottom edge of its containing window. Typically, the widget itself is a fixed size.
Select a widget and go to the Position page in the GUI Painter Tool.
Set the Bounded positioning button, if necessary (for labels and buttons).
Set the proportions:
To follow the right edge, set the right and left Proportion values to 1.0.
To follow the bottom edge, set the top and bottom Proportion values to 1.0.
Set the offsets:
To set a fixed space from the right edge, set the right offset to a negative number of pixels (e.g., -5), and the left offset to an appropriate value to set the width.
To set a fixed space from the bottom edge, set the bottom offset to a negative number of pixels (e.g., -5), and the top offset to an appropriate value to set the height.
Apply
the changes.
For example, a reasonably sized action button that stays five pixels from the right bottom corner would have these settings:
All proportions set to 1
Left offset: -55
Top offset: -30
Right offset: -5
Bottom offset: -5
Set an unbounded widget's position
Unbounded widgets (such as buttons and labels) do not have fixed left, right, top, and bottom edges, because they adjust their sizes to fit a changing label. Instead of positioning these widgets by edge position, you position them by a reference point, which is, by default, the widget's origin (top-left corner).
Select an unbounded widget and go to the Position page in the GUI Painter Tool.
Set the Unbounded positioning button, if necessary:
Set the reference point for the widget by changing the x and y values. The most common reference points are:
0.0, 0.0 -- top-left corner (origin) 0.5, 0.5 -- center 1.0, 1.0 -- bottom right corner
Set the X and Y Proportions to 0 to give the widget a fixed position, or to a scaling factor to cause it to move in a dimension as the window scales.
Set the X and Y Offsets to adjust the widget's position relative to the scaling position.
Apply
the properties.
Set a widget's position programmatically
Get the widget's wrapper from the application model's builder.
For a relative shift in position, send a `moveBy:` message to the wrapper. The argument is a `Point` whose `x` and `y` values indicate the number of pixels by which the widget is to be shifted.
moveArrowUp | wrapper | wrapper := (self builder componentAt: #arrow).

"If the arrow is not too high, raise it another notch." wrapper bounds origin y > 30 ifTrue: [wrapper moveBy: 0@-5]
To apply an absolute position, send a `moveTo:` message to the wrapper. The argument is a `Point` whose coordinates are the desired position of the widget.
resetArrow | wrapper | wrapper := (self builder componentAt: #arrow). wrapper moveTo: self arrowOrigin
In `MoveExample` , a graphic label is repositioned by three buttons, giving the effect of a pointer on a meter. The Up and Down buttons shift the position relative to the prior position, while the Reset button moves the widget to an absolute position.
Online example: MoveExample
Align a group of widgets
In a canvas, select the widgets to be aligned.
In the Canvas Tool, select the Arrange->Align command.
In the Align dialog, select on horizontal line when aligning side-by-side widgets. When aligning widgets in a column, select on vertical line .
In the Align dialog, select first selection when the widgets are to be aligned with the first widget that was selected. Select merged box to align the widgets on a line halfway between the two most extreme positions within the group of widgets.
In the Align dialog, select the edges, or the centers, to be aligned.
Install
the canvas.
Distribute a group of widgets
To make the spaces between a group of widgets equal:
In a canvas, select the widgets to be spaced.
In the Canvas Tool, select the Align->Distribute command.
In the Distribute dialog, select left to right for widgets that are to be spaced in a horizontal row. Select top to bottom for columnar distribution.
In the Distribute dialog, select the type of spacing. For constant spacing between edges , you must specify the number of pixels to place between each pair of widgets.
Install
the canvas.
Sizing Widgets
Size a widget on the canvas
When a widget is placed on the canvas and selected, it has selection "handles."
To resize the widget visually, select and drag one of its handles until the widget is the correct size.
Set a widget's size in pixels
Select the widget in the canvas, and open the Position page in the GUI Painter Tool (or use Layout->Constrained... ).
Select the Bounded widget positioning button if there's an option (for buttons and labels only):
Set all Proportion settings to 0.
Set ( L )eft, ( R )ight, ( T )op, and ( B )ottom Offset values to set the widget's size:
width in pixels = R - L
height in pixels = B - T
The width and height must each be at least 1.
Click Apply to accept the change.
The widget's size is currently fixed. To make it relative but keeping this current size, select Layout->Relative .
Make widget sizes equal
To make several widgets all be the same size:
Select the widgets to equalize. You can drag a selection box around the widgets, or shift-click to add widgets to the selection.
Select Arrange->Equalize... in the canvas menu.
Select the equalization options in the dialog and click OK .
Make a widget's size and position fixed
To make a widget's size and origin constant when the window changes size:
In a canvas, select the widget whose size is to be fixed.
Select Layout->Fixed in the canvas menu.
This sets all proportion parameters for the widget to 0, and sets its size in terms of offsets only. You can make these settings manually in the GUI Painter Tool or using the Layout->Constrained... dialog.
Make a widget's size and position relative
To make a widget's size and origin relative when the window changes size:
In a canvas, select the widget whose size is to be fixed.
Select Layout->Relative in the canvas menu.
This sets all offset parameters for the widget to 0, and sets its size in terms of proportions only. You can make these settings manually in the GUI Painter Tool or using the Layout->Constrained... dialog.
Set a widget's origin and size
Use the Proportion and Offset settings in the Position page of the GUI Painter Tool to adjust a widget's size and position.
The Proportion identifies a vertical (for R and L ) or horizontal (for T and B ) line the specified percentage between the extremes (0 and 1). Offsets are specified relative to that line, and may be positive or negative integers.
Select the widget and go to the GUI Painter Tool Position page.
Set the Bounded positioning button, if necessary (for labels and buttons).
Set Proportion values between 0 and 1 (inclusive) to set the baseline for the widget's edges between the window's ( L )eft, ( R )ight, ( T )op, and ( B )ottom border. For example, 0.3 sets the baseline 1/3 of the way between the window borders.
To make a widget's size fixed , set the Proportion values for the top and bottom equal, and for the right and left equal.
Vertical and horizontal proportions can be different. The widget's size will be specified entirely by offsets.
Set the position of each of the widget's edges relative to its baseline using offset values, in pixels. Positive values are to the right or below the baseline; negative values are to the left or above the baseline.
Apply
the changes.
Make a widget's size relative
To allow a widget to expand and shrink as with the window, set the widget size to be relative.
To set a widget to size itself in 1-to-1 ratio with the window, simply select the widget and pick the Layout->Relative command.
For more complicated situations, or for more precise control, you can set properties on the Position page of the GUI Painter Tool.
Online example: Size1Example
See also:
Make a widget's size and position relative
Make widget bounded/unbounded
You can make an unbounded widget bounded, so its size and position can be managed precisely. Care must be taken, however, because font (especially of different platforms) or label changes may cause the label to expand beyond the widget's boundaries.
In a canvas, select the unbounded widget.
Select Layout->Be Bounded .
Alternatively, select the Bounded button in the GUI Painter Tool, Position page, and Apply the change.
To return the widget to unbounded, select Layout->Unbounded , or select the Unbounded button:
Change widget's size programmatically
To resize a widget at runtime:
Set the widget's ID property.
Get the widget's wrapper from the application model's builder, by sending a `componentAt:` message to the builder, with the widget's ID as argument.
Send a `bounds` message to the wrapper to get the widget's existing size.
Create a rectangle having the desired origin and extent, using the widget's bounding rectangle to derive the new values.
Send a `newBounds:` message to the wrapper, with the rectangle as the argument.
expandBox | wrapper oldSize newSize | wrapper := self builder componentAt: #box . oldSize := wrapper bounds .

"If the box is bigger than the window already, do nothing." oldSize origin x < 0 ifTrue: [^nil].

"Expand the bounding rectangle by 10 pixels on each side." newSize := Rectangle origin: oldSize origin - 10 corner: oldSize corner + 10 .

"Assign the new bounding rectangle to the widget wrapper." wrapper newBounds: newSize .
Online example: Size3Example
Widget Appearance
Set a widget's label
Edit the Label properties on the Basics page.
Set String to contain the label that appears on the widget, and Message to be the action method.
To have the application provide the text of the label, enable the Supplied By Application property.
Select Apply & Close .
Change a widget's font
Select the widget.
Choose the desired font on the Details page of the GUI Painter Tool.
Set or remove a widget's border
Select a widget in a canvas.
To apply a border to the widget, turn on its Border property on the Details panel of the GUI Painter Tool.
To remove the border, turn off the Border property on the Details panel of the GUI Painter Tool.
Apply
the properties and Install the canvas.
Set a widget's font property
You can use the Font menu in the widget's properties to choose an alternative font. The built-in fonts are:
Default
Fixed
, for a fixed-width font that is useful when you want to align text in columns
Large
, for a font that is slightly larger than the default
Small
, for a font that is slightly smaller than the default
System
, for a font that matches the current platform's system font, when available
Set a widget's font programmatically
In a method in the application model, get the widget from the application model's builder.
Create an instance of `TextAttributes` corresponding to the new font. If the font exists in the fonts menu, you can send a `styleNamed:` message to the `TextAttributes` class. The argument is the name of the font (for example, `#large` for the system's Large font).
Get the label from the widget by sending a `label` message; get the text of the label by sending a `text` message to it. Then install a blank text temporarily as a means of erasing the old label if the new font is smaller.
Install the new font in the widget by sending a `textStyle:` message to the widget. The argument is the `TextAttributes` you created in step 2.
Reinstate the original label by sending a `labelString:` message to the widget.
changedFont | widget newStyle oldLabel | widget := (self builder componentAt: #label) widget. newStyle := TextAttributes styleNamed: (self labelFont value).

"Erase the existing label in case its font is larger than the new one." oldLabel := widget label text. widget labelString: ''.

"Install the new font." widget textStyle: newStyle."

"Reinstate the original label." widget labelString: oldLabel.
Online example: Font1Example
Hide/display a widget
Select the widget.
On the Details page of the GUI Painter Tool, set the Initially Invisible property to set the widget's visibility when the window opens.
Get the widget's wrapper by sending `componentAt:` to the builder.
To hide the widget, send a `beInvisible` message to the wrapper.
To make the widget visible again, send a `beVisible` message to the wrapper.
changedListVisibility | wrapper desiredState | wrapper := self builder componentAt: #colorList . desiredState := self listVisibility value.

desiredState == #hidden ifTrue: [ wrapper beInvisible ].

desiredState == #disabled ifTrue: [ wrapper beVisible . wrapper disable].

desiredState == #normal ifTrue: [ wrapper enable. wrapper beVisible].
Online example: HideExample
Disable/enable a widget
You can disable a widget, causing it to be displayed in gray.
This deactivates the controller so the widget does not respond to user input.
Select the widget.
On the Details page of the GUI Painter Tool, set the Initially Disabled property to set the widget's visibility when the window opens.
Get the widget's wrapper by sending `componentAt:` to the builder.
To disable the widget, send a `disable` message to the wrapper.
To make the widget active again, send an `enable` message to the wrapper.
changedListVisibility | wrapper desiredState | wrapper := self builder componentAt: #colorList . desiredState := self listVisibility value.

desiredState == #hidden ifTrue: [wrapper beInvisible].

desiredState == #disabled ifTrue: [ wrapper beVisible. wrapper disable ].

desiredState == #normal ifTrue: [ wrapper enable . wrapper beVisible].
Online example: HideExample
Set a widget's icon
Select the widget.
On the Basics Page of the GUI Painter Tool, set the Label properties to be the name of a method that supplies a graphic, enable the Label Is Image property.
Select Apply & Close .
Set the widget tab order
By default, the tab order between widgets is the order in which they were added.
You should disable tabbing for a text editor if the editor should interpret the Tab key as a character.
To add a widget to the tab sequence, check its Can Tab property on the Details page of the GUI Painter Tool.
To set the tab order, select the first widget in the sequence, then hold down the Shift key while you select each widget in the desired tabbinbg order.
Select the Arrange->Bring To Front command.
Install
the canvas.
Coloring Widgets
Set a widget's colors
In a canvas, select a widget(s) you wish to color.
In the GUI Painter Tool, select the Color page in the GUI Painter Tool.
Widgets have four color zones: Foreground , Background , Selection foreground , and Selection background . By default, these are set to none , which means that the widget inherits its colors from its window).
Select the desired color from the color chart and click on the zone you wish to define. To revert the zone to none , click on the zone a second time. Use the slider controls to fine-adjust the color. The vertical slider work with a color-cube model, while the horizontal sliders work with a hue-saturation-value model.
Repeat for each color zone.
To display the policy for the most recently active User-Interface Painter window or currently selected widget, toggle the Read button. The policy will be displayed in the color zones at the bottom of the tool.
Apply the properties.
Set a widget's colors programmatically
Get the widget's wrapper by sending a `componentAt:` message.
Get the widget's `LookPreferences` from its wrapper.
Create a copy of the preferences by setting a color zone value, by sending `foregroundColor:` , `backgroundColor:` , `selectionForegroundColor:` , or `selectionBackgroundColor:` , with a color as argument.
Install the new `LookPreferences` by sending a `lookPreferences:` message to the wrapper.
foregroundColor: aColor "For each sample widget, change the indicated color layer."

| wrapper lookPref | self sampleWidgets do: [ :widgetID | wrapper := ( self builder componentAt: widgetID ). lookPref := wrapper lookPreferences foregroundColor: aColor . wrapper lookPreferences: lookPref ].
Online example: ColorExample
Accessing Widgets
Make a widget <Tab> selectable
Select the widget.
Enable the Can Tab setting on the Details page of the GUI Painter Tool.
See also:
Change tabbing order
Change tabbing order
To set the order of tab-selectable widgets on a canvas, hold down the <Shift> key and select the desired order of all the widgets; then select the Arrange->Bring To Front command in the Canvas Tool. The new tabbing order will take effect when you Install the canvas.
Access a widget programmatically
Using the GUI Painter Tool, set the widget's ID property to an identifying name for the widget (in the example, `#comment` ). Apply the properties and install the canvas.
In a method in the application model that needs to access the widget, send a `componentAt:` message to the application model's builder with the widget's ID .
Send a `widget` message to the object returned by step 2.
alignCenter | widget style | widget := (self builder componentAt: #comment) widget . style := widget textStyle copy. style alignment: 2. widget textStyle: style. widget invalidate.
In some cases, the application model must send messages to the wrapper instead of the widget.
Online example: Editor1Example
See also:
Access a widget's wrapper programmatically
Refresh a widget programmatically
To force a widget to redraw itself, send the message `#invalidate` to the widget.
Using the GUI Painter Tool, set the widget's ID property to an identifying name for the widget (in the example, `#comment` ). Apply the properties and install the canvas.
In a method in the application model that needs to access the widget, send a `componentAt:` message to the application model's builder with the widget's ID .
Send a `widget` message to the object returned by step 2.
alignCenter | widget style | widget := (self builder componentAt: #comment) widget. style := widget textStyle copy. style alignment: 2. widget textStyle: style. widget invalidate.
Online example: Editor1Example
Access a widget's wrapper programmatically
Some widget properties are controlled by the widget's wrapper rather than the widget itself.
Using the GUI Painter Tool, set the widget's ID property to an identifying name for the widget. Apply the properties and install the canvas.
In a method in the application model, send a `componentAt:` message to the application model's builder with the widget's ID .
Send appropriate messages to the wrapper object.
changedListVisibility | wrapper desiredState | wrapper := self builder componentAt: #colorList . desiredState := self listVisibility value.

desiredState == #hidden ifTrue: [wrapper beInvisible].

desiredState == #disabled ifTrue: [ wrapper beVisible. wrapper disable]. desiredState == #normal ifTrue: [wrapper enable; beVisible].
Online example: HideExample
Widget Dependencies
Add a dependency
To declare a dependency, send an `onChangeSend:to:` message to the widget's value holder. The first argument is a message selector to send to the second argument, which is typically the application model itself.
A dependency is typically declared in the `initialize` method.
initialize colorNames := SelectionInList with: ColorValue constantNames. selectedColor := String new asValue. fieldIsDependent := false asValue.

"Arrange for the application model to take action when the check box is turned on or off." fieldIsDependent onChangeSend: #changedDependency to: self.
Online example: DependencyExample
Remove a dependency
Send a `retractInterestsFor:` message to the widget's value holder. The argument is the object that registered the interest, typically the application model itself.
To re-establish the dependency, register the interest again.
changedDependency "Turn on or off the dependency link between the list and the input field, depending on the value of the check box."

| valueModel | valueModel := self colorNames selectionIndexHolder.

self fieldIsDependent value ifTrue: [ valueModel onChangeSend: #changedSelection to: self ] ifFalse: [ valueModel retractInterestsFor: self ].
Online example: DependencyExample
Bypass all dependencies
Your application can bypass dependencies, but since this disables the automatic update features of value models you must explicitly update views programmatically.
Send a `setValue:` message to the widget's value holder instead of the usual `value:` message. The argument is the widget's new value.
Get the widget from the application model's builder and ask the widget to update itself with the new value.
changedB "Use setValue: to bypass dependents, thus avoiding circularity." self bSquared setValue: (self b value raisedTo: 2).

"Since dependents were bypassed when the model was updated, update the view manually." (self builder componentAt: #b2) widget update: #value.
Online example: FieldConnectionExample
Labels
Add a label to a canvas
Use a label widget to place a single line label on a canvas.
Place a label widget on the canvas, and select it.
On the Basics page of the GUI Painter Tool, enter the label text in its Label property field.
Click Apply , and Install the canvas.
For formatted text, use a `ComposedText` or `Text` object in a graphic label.
Online example: LogoExample
See also:
Add a multi-line label to a canvas
Change a text label at runtime
Add a graphic label to a canvas
Add a multi-line label to a canvas
To add a multi-line label to a canvas, either use a separate label widget for each line, or use a read-only text widget.
See also:
Add a label to a canvas
Add a text editor to a canvas
Add a graphic label to a canvas
Use a graphic label to add a graphical element to an interface. The graphic can be static, or can change while the application is running.
Place a label widget on the canvas, and select it.
On the Basics page of the GUI Painter Tool, enter the name of the resource method for the graphic in in the Label field.
Check the Label is Image property.
Click Apply , and Install the canvas.
Create the graphic image resource method using the Image Editor or other means, and install it in the application model. Name the resource method with the name in the Label property.
For a large graphic, insert the graphic in a view holder.
Online example: LogoExample
See also:
Add a label to a canvas
"Working with Graphics and Images" in the VisualWorks GUI Developer's Guide.
Change a text label at runtime
You can change the content of a text label while the application is running.
Get the widget from the application model's builder with a `componentAt:` message.
Send a `labelString:` message to the widget, with the new label string as argument.
For formatted text, use a `ComposedText` or `Text` object in a graphic label.
Online example: LogoExample
See also:
Change a graphic label at runtime
Change a graphic label at runtime
Get the widget from the application model's builder with a `componentAt:` message.
Send a `label:` message to it, with the new graphic as argument.
animateLogo "Display the logo in successively larger sizes (as a way of demonstrating dynamic updating of a graphic label)."

| logo widget view animationRegion | logo := self class logo. widget := self builder componentAt: #logo. animationRegion := widget bounds. view := self builder composite.

10 to: 1 by: -1 do: [ :factor | (Delay forMilliseconds: 100) wait. widget label: (logo shrunkenBy: factor @ factor). view invalidateRectangle: animationRegion repairNow: true]
Online example: LogoExample
Set a label's font, emphasis, and color
You can set certain font characteristics programmatically, and change them dynamically, by setting the label text to a `Text` or `ComposedText` object.
Online example: LogoExample
See also:
Change a graphic label at runtime
Reuse a label
If you plan to use the same label repeatedly, such as a company name or logo, you can store it in a registry.
To use a label in a registry, enter the label's lookup key in place of the resource method name in the widget's Label property.
The system checks a registry if it does not find a resource method.
See also:
Build a registry of labels
Build a registry of labels
There are two label registries, one for strings, and another for graphics, including `Text` and `ComposeText` objects.
Labels are usually registered in a class-initialization method, so the registration occurs whenever the class is loaded into an image.
To register a graphic image, send a `visualAt:put:` message to `ApplicationModel` . The first argument is the lookup key for the label, and the second is the graphic resource method.
To register a string label, send a `labelAt:put:` message to `ApplicationModel` . The first argument is the lookup key for the label, and the second argument is the label string.
initialize "LogoExample initialize"

"Register the graphic image for the trademark symbol." ApplicationModel visualAt: #trademark put: self trademark.

"Register the textual version of the trademark symbol." ApplicationModel labelAt: #tm put: '(TM)'.
Execute the initialization method to update the registry.
Online example: LogoExample
See also:
Reuse a label
Remove a registered label
Remove a registered label
To save memory, you should remove unneeded labels from the registry.
Get the desired registry by sending a `visuals` message to the `ApplicationModel` class for the graphic labels registry, or a `labels` message for string labels.
Send a `removeKey:ifAbsent:` message to the registry. The first argument is the name of the label, and the second argument is an action block, which may be empty.
"Visual registry" | registry | registry := ApplicationModel visuals. registry removeKey: #trademark ifAbsent: [].

"Labels registry" registry := ApplicationModel labels. registry removeKey: #tm ifAbsent: [].
Input Fields
Add an input field to a canvas
Use an input field for both entering and displaying data. A value model manages the field's data.
Select the Input Field widget in the palette and place it on a canvas. Use the widget handles to size and position the field.
On the Basics page of the GUI Painter Tool, enter the field's Aspect property with the name of the method that will return the field's value model.
Apply
the properties and Install the canvas.
Use the canvas's define command to add an instance variable and accessor method for the field's aspect property. Check the Add Initialization box if lazy initialization is acceptable.
To set an appropriate initial value, add an `initialize` method.
initialize month := (Date nameOfMonth: 1) asValue. year := 1900 asValue.

dateRange := (0@1) asValue. dateRange onChangeSend: #changedDate to: self.
Online example: Slider2Example
Align an input field's contents
Set the field's Align property to Left (to start the data at the left side of the field), Center (to center the data), or Right (to place the data against the right margin).
Make an input field read-only
Turn on the field's Read Only property.
Restrict a field's input size
In the field's Size property, enter the number of characters that you want to allow. (When the user tries to enter characters beyond that limit, they are ignored.)
See also:
Restrict a field's input type
Restrict a field's input type
You can set the specific data type to be entered in an input field, and the display format that will be used.
Select the input field widget.
On the Basics page of the GUI Painter Tool, select the acceptable input data type in the Type property combo box. This property converts the user's input string to the selected object type.
Optionally, select a Format property provides, which formats the displayed data as selected.
Apply
the properties and Install the canvas.
Format displayed data in an input field
You can format the string represention of a field's value model for specific purposes (e.g., phone numbers, monetary units, and percentages).
Select the input field widget.
On the Basics property page, set the field's Format property as desired. Note that the field's Type property setting determines the kinds of available formats, if any.
Apply
the properties and Install the canvas.
Specify a custom display format
Use a `TypeConverter` to display a number in a specified format. Specify the format as a string that uses the same conventions as the predefined formats.
Use a Palette to add a field to the canvas.
Enter Aspect property name. Apply the property and Install the canvas.
Use the define command to create the instance variable and aspect accessor method for the aspect.
Edit the aspect accessor method to initialize the value to a `TypeConverter` that uses the desired format string.
price ^price isNil ifTrue: [price := (TypeConverter onNumberValue: 0 asValue format: '$###,###,###.##')] ifFalse: [price]
Online example: FieldTypeExample
See also:
Class comments for the `NumberPrintPolicy` , `TimestampPrintPolicy` , and `StringPrintPolicy` classes.
Validate an input field
To restrict and validate an input field, you can arrange for a typed entry to be validated either when the user accepts it, or immediately after an invalid character is typed.
Use a
change validation callback
to prevent input from being passed to a value model, and an
exit validation callback
to keep focus in the field until the input is corrected.
See also:
"Configuring Widgets" in the VisualWorks GUI Developer's Guide.
Change an input field's pop-up menu
You can substitute a custom menu for the default menu.
Select the input field.
Enter the name of the method that will supply a custom menu in the field's Menu property.
Create the menu-creating method named in step 2, adding it to the application model in a `menu messages` protocol.
You can create the menu using the Menu Editor, or by using any `MenuBuilder` messages.
Create any new methods named in the custom menu, adding them in the `menu messages` protocol.
Online example: FieldMenuExample
See also:
Menu Editor
Disable an input field's pop-up menu
Disable an input field's pop-up menu
Select the input field.
Enter the name of the method that will return an empty menu (such as `noMenu` ) in the field's Menu property.
Create the method to return a block containing `nil` .
noMenu ^[nil]
When asked for its menu, the field will evaluate this block, and no menu will be displayed.
See also:
Change an input field's pop-up menu
Dependencies Between Fields
Create a One-Way Connection
Add a `postBuildWith:` method to the application model in an `interface opening` protocol.
In the `postBuildWith:` method, register an interest in the field that originates updates, naming a method to be invoked when that field is changed (`changedA` ).
postBuildWith: aBuilder self a onChangeSend: #changedA to: self.
Add the change method (`changedA` ) to the application model in a `change messages` protocol. That method updates the dependent field's model.
changedA self aSquared value: (self a value raisedTo: 2).
Online example: FieldConnectionExample
Create a Two-Way Connection
Add a `postBuildWith:` method to the application model (`FieldConnectionExample` ) in an `interface opening` protocol.
In the `postBuildWith:` method, register interests in both fields, naming the method to be invoked when each field is changed (`changedB` , `changedBSquared` ).
postBuildWith: aBuilder self b onChangeSend: #changedB to: self. self bSquared onChangeSend: #changedBSquared to: self.
Add the change methods (`changedB` , `changedBSquared` ) to the application model in a `change messages` protocol.
changedB "Use setValue: to bypass dependents, thus avoiding circularity."

self bSquared setValue: (self b value raisedTo: 2).

"Since dependents were bypassed when the model was updated, update the view manually." (self builder componentAt: #b2) widget update: #value.

changedBSquared "Use setValue: to bypass dependents, thus avoiding circularity."

self b setValue: (self bSquared value raisedTo: (1/2)).

"Since dependents were bypassed when the model was updated, update the view manually."

(self builder componentAt: #b) widget update: #value.
Online example: FieldConnectionExample
Combination Box
Add a combo box to a canvas
Use a combo Box widget when you need an input field restricted to a set of standard entries.
Add a combo box widget to the canvas, leaving it selected.
On the Basics page of the GUI Painter Tool, fill in the combo box's Aspect property with the name of the method (in the example, `shipper` ) that returns a value model for the combo box.
In the combo box's Choices property, enter the name of the method (`shipperChoices` ) that returns a collection of entry choices.
In the combo box's Type property, choose the type of input the widget is to accept (see: Restrict a field's input type.) Set the Format property, as appropriate (see: Format displayed data in an input field.)
Apply
the properties and Install the canvas.
Use the Define button to create the instance variable (`shipper` ) and accessing method (`shipper` ) for the aspect.
shipper ^shipper
Create the method that you named in step 3 (`shipperChoices` ). The method returns a value holder containing the list of valid entries. The value holder can be held in an instance variable (as in the example).
shipperChoices ^shipperChoices
Create an `initialize` method to set the field's aspect and choices variables. Initialize these variables with a value model and a value holder containing data of the type specified in step 3. The value holder contains a list of valid entries.
initialize

| list | shipper := 'Courier' asValue.

list := List new. list add: 'Courier'; add: 'FedEx'; add: 'UPS'; add: 'USPS'. shipperChoices := list asValue.
Online example: ComboBoxExample
See also:
Set a combo box to display a list of objects
Set a combo box to display a list of objects
You can arrange for a combo box to display a list of choices that are arbitrary objects. You do this by supplying a print method and a read method that translate the relevant objects into displayable elements and back.
On the Basics page of the GUI Painter Tool, set the Type property of the combo box to `Object` .
Fill in the Print property with the name of a method that converts the relevant objects to strings (in this example, `employeeToString:` ). The name must end with a colon.
Fill in the Read property with the name of a method for converting strings to objects of the desired type (in this example, `stringToEmployee:` ). The name must end with a colon.
In the application model, create a print method with the name you specified in step 2 (`employeeToString:` ). This method accepts an object from the choices list as an argument (in this case, an instance of `Employee` ).
In the print method, return a `String` that represents the object from the choices list. In this example, display the name of the `Employee` . The string is displayed in the combo box's pull-down list and also in the combo box's field when the choice is selected.
employeeToString: anEmployee "Return a String for representing the Employee in the combo box's list and field."

^anEmployee name.
Create a read method with the name you specified in step 3 (`stringToEmployee:` ). This method accepts a `String` argument.
In the read method, return an object for the given `String` . In this example, determine whether the `String` is the name of an `Employee` in the choices list; if so return that `Employee` . Otherwise, create a new `Employee` and add it to the choices list.
stringToEmployee: aString "Return an Employee corresponding to the given String. If the String corresponds to the name of an Employee on the choices list, return that Employee. Otherwise, create a new Employee and add it to the list."

| theEmp | theEmp := self employeeChoices value detect: [:each | each name = aString] ifNone: [nil].

theEmp isNil ifTrue: [theEmp := Employee new name: aString. self employeeChoices value addLast: theEmp].

^theEmp
Online example: ComboConversionExample
Controlling Input Field Display
Highlight part or all of the input field
In a method in the application model, ask the field's wrapper to `takeKeyboardFocus` . This highlights the entire field contents.
Tell the field's controller the indices (character positions) of the substring that is to be highlighted.
addPart "Put a template in the partID field, then highlight the suffix."

| wrapper | self partID value: 'MW-0000'.

wrapper := self builder componentAt: #part1. wrapper takeKeyboardFocus. wrapper widget controller selectFrom: 4 to: 7.
Online example: FieldSelectionExample
See also:
Position the insertion point
Position the insertion point
In a method in the application model, ask the field's wrapper to `takeKeyboardFocus` .
Tell the field's controller the character position at which to place the insertion point.
addPart2 "Put a template in the partID2 field, then position the insertion point."

| wrapper | self partID2 value: 'MW-'.

wrapper := self builder componentAt: #part2. wrapper takeKeyboardFocus. wrapper widget controller selectAt: 4.
See also:
Highlight part or all of the input field
Spin Buttons
About Spin Buttons
A spin button allows a user to select a value from a determined range. The selected value is returned as a selectable object type and format.
A spin button model must represent a number, whenever its Type property is set to either String , Text , or Symbol . In these cases the model must respond to `asNumber` and not answer `zero` unless the model itself represents 0.
The Low Value is the minimum setting the button will spin to, and the High Value is the maximum setting. The spin button range is unbounded unless these values are set.
The Interval is the amount each button press increments or decrements the spin button value. Any number may be entered for the Interval, but only positive numbers make sense. The default Interval value is 1.
Adding a Spin Button
Add a spin button widget to the canvas.
In the Aspect property, enter a name for the aspect.
(Optional) Set the Type , Format , Low Value , High Value , and Intervale properties
These properties set the returned object type and format, and the range and increment for the presented values.
Apply
the properties and install the canvas, and use define to add the aspect instance variable and accessor method.
Create or edit the `initialize` method to initialize the aspect variable with a value holder and an initial value.
Buttons and Check Boxes
Add a set of radio buttons to a canvas
Use radio buttons to select exactly one item from a limited set of choices.
Select a group of new radio buttons on the canvas.
On the Basics page of the GUI Painter Tool, change the Label property for each to name its particular choice (in the example, "Dialog" and "Transcript").
For all buttons, enter the same Aspect property (`outputMode` ).
For each button, enter a different Select property (`#dialog` and `#transcript` ). This is the symbol that is stored in the Aspect value holder whenever the button is selected.
Apply
the properties and Install the canvas.
Use the canvas's define command to add an instance variable for the aspect that is shared by the buttons (`outputMode` ), and to create a method for accessing the aspect variable (`outputMode` ), in an `aspects` protocol.
Create an `initialize` method to assign the aspect variable to hold a value holder containing one of the valid Select symbols (`#dialog` ). Your choice of symbol determines which radio button will be selected as a default.
outputMode ^outputMode

initialize super initialize. outputMode := #dialog asValue. showMinutes := true asValue. showHours := true asValue. showSeconds := true asValue.
Online example: ButtonExample
See also:
Add a check box to a canvas
Add an action button to a canvas
Relocate a default widget label
All labeled widgets provide for a fixed, default label location. To place the label in a different location, turn off the widget's Label property and use a separate label widget.
Add a check box to a canvas
Use a check box to turn on or off an attribute.
Use a Palette to add a check box to the canvas.
For each check box, enter a descriptive name in its Label property (for example, "Hours").
For each checkbox, fill in its Aspect property with the name of the method that accesses the check box's value holder (`showHours` ). This value holder will contain `true` when the check box is selected and `false` when it is not selected.
Apply
the properties and Install the canvas.
Use the canvas's define command to create an instance variable in which to store the check box's value holder (`showHours` ), and the method(s) named in step 3 (`showHours` ) in an aspects protocol.
showHours ^showHours
In the `initialize` method, initialize the variable to a value holder containing `true` if you want the check box to be selected by default and `false` otherwise.
initialize super initialize. outputMode := #dialog asValue. showHours := true asValue. showMinutes := true asValue. showSeconds := true asValue.
Online example: ButtonExample
See also:
Add a set of radio buttons to a canvas
Add an action button to a canvas
Add an action button to a canvas
Use a Palette to add an action button to the canvas, leaving it selected.
In the GUI Painter Tool, fill in the button's Label property with a descriptive label (in the example, "Tell time").
Fill in the button's Action property with the name of the method that performs the action (`#tellTime` ).
Apply
the properties and Install the canvas.
Create the method named in step 3 (`tellTime` ) in an `actions` protocol.
tellTime | t tString | t := Time now. tString := String new.

"Assemble the time string based on the check boxes." self showHours value ifTrue: [tString := tString, t hours printString]. self showMinutes value ifTrue: [tString := tString, ':', t minutes printString, ':'] ifFalse: [tString := tString, '::']. self showSeconds value ifTrue: [tString := tString, t seconds printString].

"Send the time string to the output channel set by the radio buttons." self outputMode value == #transcript ifTrue: [Transcript show: tString; cr] ifFalse: [Dialog warn: tString]
Online example: ButtonExample
See also:
Add a set of radio buttons to a canvas
Add a check box to a canvas
Add a placeholder action
Add a placeholder action
To cause an action button to display a dialog reminding you that the method has not yet been implemented, enter `unimplemented` in the button's Action property.
Designate a default button
To make a button the default action in a dialog (e.g., the button activatedby pressing <Return>) , enable the button's Be Default property.
See also:
Size a button as the default
Size a button as the default
In some window managers, such as Windows and OSF Motif, a default button has a different appearance from other buttons which may affect the button's dimensions and alignment with other widgets.
To make a nondefault button take on the sizing of a default button, turn on the button's Size as Default property.
Supply a graphic label for a button
Select the button in the canvas.
In the GUI Painter Tool, turn on the button'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.
Turn off a button's highlighting
You can disable highlighting of a button when the user clicks on it or tabs to it.
Select the button in the canvas.
In the GUI Painter Tool, turn off the button's Bordered property.
In a method in the application model (typically `postBuildWith:` ), get the widget from the application model's builder and send a `hiliteSelection:` message to it. The argument is `false` .
postBuildWith: aBuilder | oddButtons |

"Make the main window a master window." aBuilder window application: self; beMaster.

"Turn off highlighting for the nonrectangular buttons." oddButtons := #( #prevPageButton #nextPageButton ). oddButtons do: [ :buttonName | (aBuilder componentAt: buttonName) widget hiliteSelection: false]. "Disable the appropriate buttons." self adjustButtons.

"Set keyboard hook for special shortcut keys." aBuilder keyboardProcessor keyboardHook: [ :ev :ctrl | self keyPress: ev].
Lines, Boxes, and Ovals
Separate widgets with a line
Use a dividing line to visually separate sets of widgets. Lines are one pixel thick. For a thicker line, use a filled region widget.
Use a Palette to add a divider to the canvas.
On the Basics page of the GUI Painter Tool, select either Horizontal or Vertical for the orientation property. Apply the property.
Use the widget handles to size and position the divider.
Install
the canvas.
See also:
Add a region widget to a canvas
Group widgets with a box
Use boxes to cluster widgets in logical groups. Box lines are one pixel thick. For a thicker line, use a filled region widget.
Use a Palette to add a box to the canvas, leaving the box selected.
Use the widget handles to size and position the box.
To provide a label, fill in the Label property field in the GUI Painter Tool.
Apply
the change and Install the canvas.
See also:
Add a region widget to a canvas
Add a region widget to a canvas
Use rectangular and elliptical regions for grouping other widgets.
Use a Palette to add a region to the canvas.
On the Basics page of the GUI Painter Tool, select either Rectangle or Ellipse as the widget's shape.
Set the desired Border Thickness property.
On the Color page, select any colors for the region.
Use the widget handles to size and position the region.
Apply
the properties and Install the canvas.
To label the region, use a label widget.
See also:
Label widget
Resizing Splitter
About the Resizing Splitter
The Resizing Splitter widget allows users to resize widgets in the GUI. For example, in the GUI Painter Tool, a Resizing Splitter is between the hierarchical list of widgets and the tabbed notebook.
You can change the relative size of both by selecting and dragging the area between them. The cursor shape changes while it is passing over a Resizing Splitter, indicating that the border is movable.
Because this widget is really only intended for direct user manipulation, no programmatic interface is described in this section.
Typically, the Resizing Splitter will be one of the last widgets you add to the canvas, since you add it between other widgets whose size you want to allow the user to control.
Adding a Resizing Splitter
Select the Resizing Splitter and drop it on the canvas between the widgets you want to resize.
Initially the widget is proportioned horizontally, and the the Horizontal check box is marked, for separating widgets top to bottom. To separate widgets left to right, uncheck the Horizontal check box and resize the widget accordingly.
In the Left/top widgets: properties field, enter the IDs of the widgets to the left of or above the widget. Similarly, in the Right/bottom widgets: field, enter the IDs of the widgets to the right or below the Resizing Splitter.
Since there may be (frequently are) more than one widget on one side of a widget, these fields take space-separated widget ID names. Only widgets in the lists are resized as the Resizer Splitter is moved. IDs can be listed either simply as identifiers ( TreeView1 TextEditor2 ) or as symbols ( #TreeView1 #TextEditor2 ).
Accept
and install the canvas.
Setting Widget Positioning
To make sure the widgets resize appropriately as the ResizIng Splitter is moved, you will need to set the widget positions carefully. Use the Position pages for the affected widgets' properties for precise control.
The Resizing Splitter can either have a border or be borderless. With a border it can double as a separator line, and so enhance the look of your GUI. Without a border it is invisible, and so simply serves as an area to grab for resizing. Even if you want it to be invisible in the final product, leaving it with a border can be useful while arranging your GUI.
Sliders
Add a slider to a canvas
Add a slider widget to the canvas, leaving it selected.
In the GUI Painter Tool, fill in the slider's Aspect property with the name of the method (`destination` ) that will supply a value model for the slider.
Set the slider's orientation, to either Vertical or Horizontal , on the Details page.
Optionally change the Start (0), Stop (4000), and Step (10) properties, which control the endpoints of the range and the increment by which the marker will move. The default Start is `0` and the default Stop is `1` . The default Step is `nil` , giving the effect of continuous marker motion.
Apply
the properties and Install the canvas.
Use the canvas's define command to create an instance variable to hold the slider's value model (`destination` ), and to create a corresponding access method.
Create an `initialize` method which assigns the variable with a value holder whose initial value is the current year.
initialize "Destination" destination := Date today year asValue.

"Current year" currentYear := Date today year asValue.

"Trip meter" tripRange := RangeAdaptor on: currentYear stop: 4000 grid: 1.
Online example: Slider1Example (the `Destination` slider)
Make a slider vertical/horizontal
In the GUI Painter Tool, check the slider's Vertical property on the Details page (by default, a slider has a horizontal orientation.)
On the canvas, size and scale the slider to appropriate dimensions for its new orientation.
Apply
the properties and Install the canvas.
Make a slider read-only
Sliders are normally input devices, but can be used purely as an output device.
In the GUI Painter Tool, fill in the slider's ID property with an identifying name (`tripRange` ).
In a method in the application model (typically `postBuildWith:` ), get the slider component from the builder and disable it.
postBuildWith: aBuilder "Disable the trip meter, making it read-only." (aBuilder componentAt: #tripRange) disable.
In `Slider1Example` , we use a read-only slider as a meter to display the progress of the user's time-traveling adventure.
Online example: Slider1Example
Connect a slider to an input field
A slider provides only approximate values. Frequently, an input field is used to display or accept an exact value.
Add a field to the canvas, leaving it selected.
In the GUI Painter Tool, fill in the field's Aspect property with the same name that the slider uses for its Aspect (`destination` ).
In the field's Type property, select Number .
Apply
the properties and Install the canvas.
Online example: Slider1Example (the `Destination` slider and field)
Display a transformed value in the field
A slider always manipulates a numeric value, but you can make it simulate a nonnumeric value (such as months in the year) by using a field to display a transformed value.
In the field's Aspect property, enter a different method name than the slider's Aspect (in the example, the slider's Aspect is `dateRange` while the field's Aspect is `month` ).
In the field's Type property, select the type that corresponds to the transformed value (in the example, a month name will be displayed, so we use a `String` type field).
Use the canvas' define command to create the field's instance variable (`month` ) and accessing method (`month` ).
In a method in the application model (typically `initialize` ), initialize the field's variable.
In the `initialize` method, arrange for a change message (`changedDate` ) to be sent to the application model when the slider's value changes.
initialize month :=(Date nameOfMonth: 1) asValue. year := 1900 asValue.

dateRange := (0@1) asValue. dateRange onChangeSend: #changedDate to: self.
Create the change method (`changedDate` ) in the application model. This method is responsible for changing the field's value based on the slider's new value.
changedDate "Convert the y-axis value to a month." | y x | y := self dateRange value y. y := (12 - (y * 12) asInteger) max: 1. self month value: (Date nameOfMonth: y).

"Convert the x-axis value to a year." x := self dateRange value x. x := 1900 + (x * 100) asInteger. "(100 years)" self year value: x.
Online example: Slider2Example (the `Month` field)
Change a slider's range dynamically
When the slider's range is unchanging, you can use the slider's Start , Stop , and Step properties to set the range and the step value.
In a method in the application model (typically in an `initialize` method), initialize the slider's aspect variable with a `RangeAdaptor` by sending the instance creation message (`on:start:stop:grid:` ). The first argument (`currentYear` ) is a value holder containing the number that the slider manipulates. The `grid` argument is the step value.
initialize "Destination" destination := Date today year asValue.

"Current year" currentYear := Date today year asValue.

"Trip meter" tripRange := RangeAdaptor on: currentYear start: 0 stop: 4000 grid: 1.
Whenever the range or step must change, send a `rangeStart:` , `rangeStop:` , or `grid:` message to the adaptor. (In the example, this is done in the `engage` method.)
engage "Start the time trip."

| startingYear destinationYear direction | startingYear := self currentYear value. destinationYear := self destination value.

destinationYear == startingYear ifTrue: [^Dialog warn: 'Please select a new destination.'].

"Set the endpoints on the trip meter." self tripRange rangeStart: startingYear; rangeStop: destinationYear; grid: 1.

"Reset the meter to the starting position." currentYear value: startingYear.

"Set up a step value for the loop that follows (-1 = backward in time)." destinationYear > startingYear ifTrue: [direction := 1] ifFalse: [direction := -1].

"For each year of time travel, update the current year." startingYear to: destinationYear by: direction do: [ :yr | currentYear value: yr].
Online example: Slider1Example (the `Trip Meter` slider)
Change the slider marker length
In a message in the application model (typically `postBuildWith:` ), get the slider widget from the builder and send a `setMarkerLength:` message to it, with the length in pixels as argument.
postBuildWith: aBuilder

(aBuilder componentAt: #dateRange) widget beTwoDimensional; setMarkerLength: 10.
Online example: Slider2Example
Make a two-dimensional slider
By default, a slider operates in one dimension. You can arrange for a slider to manipulate a point in two dimensions and then use the x-axis and y-axis components of that point to control two separate parameters.
In a method in the application model (typically `initialize` ), initialize the slider's variable to an instance of `Point` that is held by a value holder.
initialize month := (Date nameOfMonth: 1) asValue. year := 1900 asValue. dateRange := (0@1) asValue. dateRange onChangeSend: #changedDate to: self.
In a `postBuildWith:` method, get the slider from the builder and ask it to `beTwoDimensional` .
postBuildWith: aBuilder

(aBuilder componentAt: #dateRange) widget beTwoDimensional;" setMarkerLength: 10.
Online example: Slider2Example
Connect a two-dimensional slider to two fields
In a method in the application model (typically `initialize` ), arrange for a change message (`changedDate` ) to be sent to the application model when the slider's value changes.
initialize month :=(Date nameOfMonth: 1) asValue. year := 1900 asValue.

dateRange := (0@1) asValue. dateRange onChangeSend: #changedDate to: self.
Use a System Browser to create the change method (`changedDate` ) in the application model. This method splits the slider's value into its x-axis and y-axis components. Each component is a value between `0` and `1` and is transformed as needed to produce a suitable value for the related field.
changedDate "Convert the y-axis value to a month." | y x | y := self dateRange value y. y := (12 - (y * 12) asInteger) max: 1. "12 months" self month value: (Date nameOfMonth: y).

"Convert the x-axis value to a year." x := self dateRange value x. x := 1900 + (x * 100) asInteger. self year value: x.
Percent Done Bar
About the Percent Done Bar
Also sometimes called a Progress Bar, this widget gives a visual indication of how far a process has advanced. It's commonly used for file copy procedures, but can be useful for any number of lengthy processes to give the user an indication of how much work has been done and how much more there is to go.
Basic Properties
Only the Basic properties are specific to this widget.
Aspect
The instance variable that determines the percentage. The value is a numeric value between 0 and 1.
Orientation
Horizontal
indicates progress horizontally.
Vertical
indicates progress vertically.
Both
indicates progress both vertically and horrizontally.
Area
shows percentage in terms of area covered, rather than linearly.
Reverse
reverses the horizontal and/or vertical direction.
Starting Point
The grid with five radio buttons sets the starting point: top, bottom, left, right, or center. If Both is selected for the orientation, then the starting point is either a corner or the center.
Adding a Percent Done Bar
Add a Percent Done Bar widget to the canvas and select it.
In the Aspect property, enter a name for the aspect.
Set the progress orientation: Horizontal , Vertical , or Both .
Optionally, select Reverse , to reverse the direction of progress indication. Select Area to indicate progress as the percentage of area filled.
Select a starting point radio button.
Apply
the properties and install the canvas. Use define to add the aspect instance variable and accessor method.
Click Map
About the Click Map
The Click Map widget allows you to define areas of a graphic image and specify the action to invoke if the user clicks on each area.
Using a Click Maps for a button bar makes it possible to use any graphic image to represent the row of buttons. The image may include text, graphics, and a variety of colors. All of the buttons may be part of a single image, guaranteeing their relative layout.
Adding a Click Map
To add a click map widget to a canvas:
Choose the click map widget in the Palette and place it on a canvas.
Set the following Basic properties and Apply them:
ID
: A unique identifier for the widget, which will be used to access the widget.
Visual Message
: The message selector that returns the image, which is typically an image resource method.
Mappings Selector
: The message that returns the mapping between an area and the action for that location. You define mapping using the Hot Regions Editor.
Default Click Message
: (optional) Specifies the action to be invoked when the user clicks in an area for which no other action is specified. If left empty, no action is taken.
In the canvas, resize the click map widget so that it closely outlines the graphic image.
Install
the canvas, and provide a class name and method name in which to install the canvas when prompted.
Defining the Hot Region Mappings
Select the click map widget in the canvas.
In the GUI Painter Tool, choose Tools ﬁ Hot Regions Editor .
Choose Regions ﬁ Read to display the selected click widget's image as the background in the Hot Regions Editor. Resize the window to display the entire image.
Choose Edit ﬁ New Slice to begin defining a region.
A slice is comprised of one or more areas that invoke the same action when clicked. The areas in a slice do not need to be contiguous. Identify areas in the Hot Regions Editor using tools similar to other bitmap editors:
The four colors or patterns of ink allow you to select a color that shows well when selecting a region. The color does not show in the final click map.
Six brush sizes and shapes. The first four draw lines, the next draws an ellipse, and the sixth draws a rectangle.
A fill-mode bucket that fills the entire image.
In the Selector input field, enter a method selector for the action to be invoked when this slice is clicked and press <Return>.
The selector is added to the menu button in the middle of the Hot Regions Editor. You can use the menu to switch between different slices of the same hot region resource.
Repeat steps 4 and 5 to define all the slices for this hot region.
Choose Regions ﬁ Apply to:
Install the hot region resource. You are prompted for the class name and selector for the resource method that stores the hot region mappings.
Insert the resource's selector into the Mapping Selector property of the click map widget that is selected in the canvas.
Close the Hot Regions Editor.
Install the canvas.
Using Custom Views and Controllers
If you have an application that uses custom views and controllers, you can make it work as a VisualWave application:
Make the custom view a subclass of `ClickWidget` .
In the custom view, implement a `mouseReleaseAt:` method that specifies what to do when the user clicks in the custom view.
Disconnect the custom controller from your application. Move any custom behavior to the custom view's`mouseReleaseAt:` method.
Note that interaction with the user is more limited in web applications than in other applications. In particular, the only mouse events transmitted to the application from the web browser are mouse clicks. Events based on entry, mouse movement, and exit are ignored.
Notebooks
Add a notebook to a canvas
Use a notebook to provide a set of index tabs, and possibly subtabs.
Selecting a tab is similar to selecting an item in a list. A notebook typically contains a subcanvas, which displays a different interface for each index tab.
Online example: Notebook1Example
See also:
"Working with Notebooks" in the VisualWorks GUI Developer's Guide .
Get the selected notebook tab
To get the selected index tab's string or association, send a `selection` message to the notebook's major `SelectionInList` .
changedLetter | chosenLetter list | chosenLetter := self majorKeys selection last. list := Smalltalk classNames select: [ :name | name first == chosenLetter]. self classNames list: list.
Online example: Notebook1Example
Get a notebook tab's value
If you associate an object with each notebook tab, you need to retrieve that value. The associated object is typically a `Symbol` that identifies a method to be performed, a canvas to be installed, or an application-specific attribute.
Online example: Notebook2Example
In a method in the application model, get the selected tab's association by sending a `selection` message to the `SelectionInList` (in the example, `minorKeys` , to which the `initialize` method assigned a `SelectionInList` with a collection of associations.)
Send a `value` message to the resulting association. (In the example, the `value` is a `Symbol` --`#all` or `#examples` --which is used to filter the list of class names.)
changedPage | chosenLetter list filter filteredList | chosenLetter := self majorKeys selection last. filter := self minorKeys selection value.

list := Smalltalk classNames select: [ :name | name first == chosenLetter].

filter == #all ifTrue: [filteredList := list] ifFalse: [filteredList := list select: [ :name | '*Example' match: name]].

self classNames list: filteredList.
Get a notebook tab's index
To get the index number, send `selectionIndex` to the notebook's `SelectionInList` .
Change the notebook binding location
A solid color strip at the left or top edge of a notebook is used to simulate the appearance of a book binding. By default, the binding is along the left edge.
To set the binding location
Select the notebook in the canvas.
In the GUI Painter Tool, go to the notebook's Binding property ( Details page) and select `top` . This moves the binding to the top edge. (To move it back, select `left` .)
Apply
properties and Install the canvas.
Online example: Notebook3Example
Change the notebook binding width
By default, the binding strip is 18 pixels wide. A width of zero can be used to eliminate the binding strip altogether.
To set the binding width, in the Width field of the notebook's Binding property, enter the desired number of pixels of width.
Online example: Notebook3Example
Set the notebook tab axis
By default, the major index tabs are aligned along the right-hand edge of the notebook, and the minor tabs are along the bottom.
To change the tab orientataion:
Select the notebook in the canvas.
In the GUI Painter Tool, go to the notebook's Major Tabs property ( Details page) and select `bottom` . (The minor tabs, if any, will move to the right-hand edge.)
Apply
the property and Install the canvas.
Online example: Notebook3Example
Set the notebook tab size
By default, the right-hand tabs are 60 pixels wide and the bottom tabs are 24 pixels high. These values are called insets.
To change the inset values:
In Right field of the notebook's Insets property ( Details page), enter the number of pixels of width for the right-hand index tabs. (The height adjusts automatically to fit the label font).
In Bottom field of the notebook's Insets property, enter the number of pixels for the bottom tabs to be offset from the bottom edge of the page. (The width adjusts to fit each tab's label.)
Online example: Notebook3Example
Set the notebook starting page
To set the start page by index, send a `selectionIndex:` message to the `SelectionInList` that holds the major keys, with the index number as argument.
postOpenWith: aBuilder (aBuilder componentAt: #pageHolder) widget client: self spec: #listSpec. majorKeys selectionIndex: 1.
If the notebook has minor keys, also set the selection index in the `SelectionInList` that contains the minor keys.
To set the start page by specifying the list element itself, rather than by index, send a `selection:` message to the `SelectionInList` , with the desired element.
Online example: Notebook1Example
Add secondary tabs (minor keys)
Minor keys, or secondary tabs, can be used for subdivisions within the major keys, or to filter the content of the notebook along a separate dimension.
Select the notebook in the canvas.
In the GUI Painter Tool, fill in the notebook's Minor property with the name of the method that returns a `SelectionInList` containing the labels for the secondary tabs (in the example, `minorKeys` ).
Use the canvas's define command to create the instance variable (`minorKeys` ) and accessing method (`minorKeys` ) for the notebook's list of index labels.
Initialize the variable, either in the accessing method or in an `initialize` method (as in the example), with a `SelectionInList` containing either strings or associations (the example uses associations).
In the `initialize` method, use an `onChangeSend:to:` message to arrange for the notebook to send a message (`changedPage` ) to the application model when the user selects a secondary tab. (In the example, both the major and minor tabs trigger the same message: `changedPage` .)
initialize
| letters |
letters := #( ' A' ' B' ' C' ' D' ' E' ' F' ' G' ' H' ' I' ' J' ' K' ' L' ' M'
' N' ' O' ' P' ' Q' ' R' ' S' ' T' ' U' ' V' ' W' ' X' ' Y' ' Z' ).
majorKeys := SelectionInList with: letters.
majorKeys selectionIndexHolder
onChangeSend: #changedPage to: self.
minorKeys := SelectionInList with: (Array
with: 'All classes'-> #all
with: 'Examples only' -> #examples).
minorKeys selectionIndexHolder
onChangeSend: #changedPage to: self.
classNames := SelectionInList new.
Create the change message (`changedPage` ) in which the subcanvas is updated based on the selected index tab. (In the example, the `classNames` list is updated with all class names or only example classes, based on the minor key.)
changedPage
| chosenLetter list filter filteredList |
"Major key."
chosenLetter := self majorKeys selection last.
list := Smalltalk classNames
select: [ :name | name first == chosenLetter].
"Minor key."
filter := self minorKeys selection value.
filter == #all
ifTrue: [filteredList := list]
ifFalse: [filteredList := list
select: [ :name | '*Example' match: name]].
self classNames list: filteredList.
Online example: Notebook2Example
Connect minor tabs to major tabs
You can set the minor keys to depend on the major keys.
In the application model's `initialize` method, use `onChangeSend:to:` messages to arrange for the two `SelectionInList` s to notify the application model when their selections are changed.
initialize
self initializeDepartments.
self initializeEmployees.
majorKeys := SelectionInList with: departments keys asArray.
majorKeys selectionIndexHolder
onChangeSend: #changedDepartment
to: self.
minorKeys := SelectionInList new.
minorKeys selectionIndexHolder
onChangeSend: #changedSubdepartment
to: self.
employeeList := SelectionInList new.
Create the change message (`changedDepartment` ) for the major keys. In this method, get the selection from the major `SelectionInList` and verify that it is not `nil` . Then use that selection as the basis for choosing the new labels for the minor tabs. Typically, this method will also reset the minor key selection so the notebook displays the first subpage.
changedDepartment
| subdepts sel |
sel := self majorKeys selection.
sel isNil ifTrue: [^self].
"Display the appropriate subdepartments as minor keys."
subdepts := self departments at: sel.
self minorKeys list: subdepts.
self minorKeys selectionIndex: 1.
Create the change message (`changedSubdepartment` ) for the minor keys. In this method, get the minor selection and verify that it is not `nil` . Then use that selection as the basis for updating the canvas in the notebook or, as in the example, its model (`employeeList` ).
changedSubdepartment
"Display the appropriate employees in the list."
| emps sel |
sel := self minorKeys selection.
sel isNil ifTrue: [^self].
emps := self employees at: sel.
self employeeList list: emps.
Online example: Notebook3Example
Change the notebook layout (subcanvas)
You can use a notebook to present a different interface on each page.
In the application model's `initialize` method, arrange for the `SelectionInList` that holds the major keys to notify the application model when a tab is selected. (In the example, a `changedExample` message is triggered.)
initialize
| exampleClasses |
exampleClasses := OrderedCollection new.
exampleClasses := Smalltalk keys select: [ :c |
(('*Example' match: c)
and: [(Smalltalk at: c) isVisualStartable])
and: [('Notebook*' match: c) not]].
majorKeys := SelectionInList
with: exampleClasses asSortedCollection.
majorKeys selectionIndexHolder
onChangeSend: #changedExample to: self.
In the change method (`changedExample` ), get the notebook widget from the application model's builder by sending a `componentAt:` message.
Still in the change method, send a `client:spec:` message to the notebook. The first argument is an instance of the desired application model (in the example, `exampleClass` ). The second argument is the name of the desired canvas (in the example, each example class's `windowSpec` is used).
changedExample
| sel exampleClass |
sel := self majorKeys selection.
sel isNil ifTrue: [^self].
exampleClass := Smalltalk at: sel value.
(self builder componentAt: #pageHolder) widget
client: exampleClass new
spec: #windowSpec.
Online example: Notebook4Example
Connect a notebook to a text editor
In the application model's initialize method, arrange for the `SelectionInList` that holds the lowest level of selections to notify the application model when a text is selected. (In the example, a `changedClass` message is triggered by a selection in the `classNames` list.)
initialize
| letters |
letters := #( ' A' ' B' ' C' ' D' ' E' ' F' ' G' ' H' ' I' ' J' ' K' ' L' ' M'
' N' ' O' ' P' ' Q' ' R' ' S' ' T' ' U' ' V' ' W' ' X' ' Y' ' Z' ).
majorKeys := SelectionInList with: letters.
majorKeys selectionIndexHolder
onChangeSend: #changedLetter to: self.
classNames := SelectionInList new.
classNames selectionIndexHolder
onChangeSend: #changedClass to: self.
classComment := '' asValue.
In the change method (`changedClass` ), get the selection from the lowest-level `SelectionInList` (`classNames` ). Then use that selection as the basis for choosing the text and install the new text in the text holder (`classComment` ).
changedClass
| chosenClass newText |
chosenClass := self classNames selection.
newText := chosenClass isNil
ifTrue: ['']
ifFalse: [(Smalltalk at: chosenClass) comment].
self classComment value: newText.
Online example: Notebook5Example
Tab Control
About the Tab Control
Online example:
TabControlExample
The TabControl is an alternative to the Notebook widget, providing a tabbed folder look. To change the contents of the subpane below the row of tabs, click a tab, or select the tab using cursor keys and press Enter .
The Tab Control shows two scroller buttons, if there are more tabs than can be displayed in the available header space. Press the buttons to scroll the tabs.
Compatibility with Notebook
The Tab Control is largely, but not completely, protocol-compatible with the Notebook widget.
In many cases, you can chang from a Notebook to a Tab Control simply by editing the application's windowSpec, replacing the `#NoteBookSpec` symbol to `#TabControlSpec` . This conversion method works, for example, with `Notebook1Example` , `Notebook4Example` , and `Notebook5Example` .
One limitation is that Tab Control does not support secondary tabs, as does Notebook. For this reason, the above conversion does not work for `Notebook2Example` or `Notebook3Example` .
For an example specifically using the Tab Control, load the TabControlExample parcel.
Adding a Tab Control
Select the Tab Control on the Palette and drop it on the canvas.
In its Aspect property, enter an aspect name. Also enter an ID , or keep the default. You will need the ID to identify the selected tab.
Define the accessor method for the aspect.
The model should be a `SelectionInList` on either `Strings` , which make up textual tab labels, or `Associations` of a `VisualComponent` to a (optional) `String` . In `TabControlExample` , this is handled in two methods:
tabs
tabs isNil
ifTrue:
[(tabs := SelectionInList with: self labelArray)
selectionIndex: 1.
tabs selectionIndexHolder
onChangeSend: #tabsChanged
to: self].
^tabs
labelArray
"Private - The list of tab names (and/or icons). See also #specArray."
^Array
with: 'Appearance'
with: self class colorsImage -> 'Colors'
with: self class printerImage -> ''
Paint subcanvases for each page (See the `appearanceSpec` , `colorsSpec` , and `fontsSpec` class methods in TabControlExample for examples.)
So the canvas can be identified by an index, which will be retrieved from labelArray, TabControlExample defines a specAttay method:
specArray
"Private - The list of associated sub canvaes. See also #labelArray."
^#(#appearanceSpec #fontsSpec #colorsSpec)
Define a method that will respond by changing subcanvases when the tab selection has changed. This message is sent when the `SelectionInList` changes, as is defined in the `tabs` method.
tabsChanged
"Every time, a tab is changed, a new sub canvas gets installed."
| index |
index := self tabs selectionIndex.
(self builder componentAt: #tabbing) widget
client: self
spec: (self specArray at: index)
The change is made by the `client:spec:` message sent to the Tab Control widget, which is retrieved from the builder.
For additional options and approaches, see Notebooks .
Defining Initial Labels
The above example defines the set of tab labels in code. You have the option of defining labels directly in the Details property page. This can be convenient, especially when there is no need to add pages dynamically.
To add a label, enter it into the Entry Field below the List and click Add . To change a label, select it, edit it in the Entry Field, and click Change . To delete, click Delete. To change the order of lables, select a label and drag it to its new position in the list.
When you click Apply , your labels are shown in the canvas, unlike when you provide labels in code.
You access the label by index as before, and so can change subcanvases in the same way. So, within the Application Model, for a Tab Control with aspect name `tabs` , send:
self tabs selectionIndex
returns the index of the currently selected tab.
Placing an Icon on a Tab
Each tab label can be a string, an icon, or an icon and a string. To use an icon, the `SelectionInList` must be an `Association` . The tabs method invokes labelArray to provide the labels, as follows:
labelArray
"Private - The list of tab names (and/or icons). See also #specArray."
^Array
with: 'Appearance'
with: self class colorsImage -> 'Colors'
with: self class printerImage -> ''
If the label is only a string, then it is provided simply as a String, as in the case with the first tab, 'Appearance'.
If the label includes an icon, then the label is specified by an `Association` between the icon and a `String` . The second tab label is an `Association` between the graphic returned by `self colorsImage` and the `String` 'Colors'.
The third tab is an icon without a `String` , so the `String` is empty.
Text Editors
Add a text editor to a canvas
Use a Palette to add a text editor to the canvas, leaving it selected.
In the GUI Painter Tool, fill in the editor's Aspect property with the name of the method (`comment` ) that will return the value model for the text editor.
Use the define command to add an instance variable (`comment` ) and accessor method to the application model for storing the text editor's value model.
Create an `initialize` method that initializes the aspect variable (`comment` ) with a value holder containing the initial text to be displayed (an empty string).
initialize super initialize.

comment := '' asValue.

classes := SelectionInList with: Smalltalk classNames. classes selectionIndexHolder onChangeSend: #changedClass to: self.

textStyle := #plain asValue. textStyle onChangeSend: #changedStyle to: self.

readOnly := false asValue. readOnly onChangeSend: #changedReadOnly to: self
Online example: Editor1Example
Access and modify selected text
Your application can access and modify a selected (highlighted) portion of text in an editor.
In a method in the application model, get the widget's controller by sending a `controller` message to the widget.
Get the selected text from the controller by sending a `selection` message to the widget.
Make any changes to the selected text, such as format changes.
Replace the original text with the modified text, by sending a `replaceSelectedText:` message to the controller.
Send `resetSelections` and `invalidate` messages to the controller's view to update the display.
changedStyle "A text style was selected -- apply it to the current selection in the comment."

| c selectedText style |

"Get the selected text." c := (builder componentAt: #comment) widget controller. selectedText := c selection.

"If nothing is selected, take no action." selectedText isEmpty ifTrue: [^self].

"If 'Plain' was selected, remove all emphases; otherwise add the new emphasis." style := self textStyle value. style == #plain ifTrue: [selectedText emphasizeAllWith: nil] ifFalse: [ selectedText addEmphasis: (Array with: style) removeEmphasis: nil allowDuplicates: false].

"Ask the controller to insert the modified text, then update the view." c replaceSelectionWith: selectedText. c view resetSelections. c view invalidate.
Online example: Editor1Example
Highlight text programmatically
Your application can highlight text for the user (e.g., as part of spell checking.)
In a method in the application model, get the controller from the widget.
Ask the controller to select the text between two endpoints (and ask it to scroll the selection into view if necessary).
Ask the builder's component to take the keyboard focus, so the highlighting will be displayed.
changedClass "When the list selection changes, update the comment view."

| selectedClass txt start wrapper | selectedClass := self classes selection.

selectedClass isNil ifTrue: [self comment value: '' asText] ifFalse: [ txt := (Smalltalk at: selectedClass) comment.

self comment value: txt.

"Find and highlight the class name in the text." start := txt indexOfSubCollection: selectedClass asString startingAt: 1. start > 0 ifTrue: [ wrapper := (self builder componentAt: #comment). wrapper widget controller selectAndScrollFrom: start to: start + selectedClass asString size - 1. wrapper takeKeyboardFocus]].
Online example: Editor1Example
Set text alignment
To change the default alignment in an editor, set the Align property in the GUI Painter Tool.
Change text alignment programmatically
In a method in the application model, get the widget from the builder.
Get a copy of the widget's text style. (Do not modify the widget's text style directly, because that object is shared by many text editors in the system.)
Set the alignment of the text style to `0` , `1` , or `2` (`0` is flush left, `1` is flush right, and `2` is centered).
Install
the new text style in the widget.
Ask the widget to redisplay itself.
alignRight | widget style | widget := (self builder componentAt: #comment) widget. style := widget textStyle copy. style alignment: 1. widget textStyle: style. widget invalidate.
Set a text editor to be read-only
To make the editor read-only, set the editor's Read Only property in the GUI Painter Tool.
Change the read-only setting programmatically
In a method in the application model, get the widget's controller.
Set the controller's read-only setting to `true` or `false` .
makeReadOnly | c | c := (self builder componentAt: #comment) widget controller. c readOnly: true.
Lists
Add a list to a canvas
Use a list widget to display a collection of objects and to allow the user to select one or more elements in the list.
A list widget depends on two value holders: a collection of objects and the index of the current selection. A `SelectionInList` object contains both of these value holders.
Add a list widget to the canvas, leaving it selected.
On the Basics page of the GUI Paitner Tool, fill in the list's Aspect property with the name of the method that will return an instance of `SelectionInList` .
Use the canvas's define command to add an instance variable (`classes` ) to the application model, and to create a corresponding aspect method. This instance variable will hold the `SelectionInList` .
Create an `initialize` method to assign a default value to the instance variable you created in step 2 (`classes` ). Initialize the variable with an instance of `SelectionInList` that is itself initialized with a list of Smalltalk class names.
initialize super initialize. classes := SelectionInList with: Smalltalk classNames. classes selectionIndexHolder onChangeSend: #changedClass to: self.

methodNames := MultiSelectionInList new.

instances := SelectionInList new.
Online example: List1Example
Display a list of non-textual elements
Create a new `displayString` method in the class of the element to be listed. The list will send this message to each element at display time. (The following method is implemented in the `Association` class.)
displayString "Allows a value to be quietly associated with the key that is displayed in a SequenceView."

^key displayString
Edit a list of elements
To change the list of elements, give the `SelectionInList` a new collection of elements.
In the method that is responsible for updating the list, get the `SelectionInList` from the application model
Send a `list:` message to the `SelectionInList` with the new collection as the argument.
changedClass | cls | self classes selection isNil

"No class is selected -- empty the selector list." ifTrue: [ self methodNames list: List new. self instances list: List new]

"A class is selected" ifFalse: [ cls := Smalltalk at: self classes selection.

"Update the selectors list." self methodNames list: cls selectors asSortedCollection.

"Update the instances list." self instances list: cls allInstances].
Online example: List1Example
Allow multiple list selections
You can select more than one item in a list as targets for an action. A list allows multiple selections when its Multi Select property is turned on.
The Use Modifier Keys For Multi Select property determines how modifier keys behave in selecting. When set (the default):
The <Select> mouse button to selects a single item on the list
<Shift>-click selects additional contiguous items
<Control>-click selects an additional noncontiguous item
Select the list widget in the canvas.
In the GUI Painter Tool, turn on the list widget's Multi Select property.
Apply properties and Install the canvas.
In the application model's `initialize` method, initialize the list widget's aspect variable to hold a `MultiSelectionInList` .
initialize super initialize. classes := SelectionInList with: Smalltalk classNames. classes selectionIndexHolder onChangeSend: #changedClass to: self.

methodNames := MultiSelectionInList new. instances := SelectionInList new.
Online example: List1Example
Get a list selection
You can ask a `SelectionInList` for the selected object or for the index of the selected object in the list. For a multiselect list, the application model must be able to handle a collection of objects.
When nothing is selected, a `SelectionInList` returns `nil` as the selection and zero as the index; a `MultiSelectionInList` returns an empty collection for both the selections and the indexes.
Get the `SelectionInList` (or `MultiSelectionInList` ) from the application model.
To get the selected item(s) and send a `selection` (or `selections` ) message.
To get the index(es), send a `selectionIndex` (`selectionIndexes` ) message.
changedClass | cls | self classes selection isNil

"No class is selected -- empty the selector list." ifTrue: [ self methodNames list: List new. self instances list: List new]

"A class is selected" ifFalse: [ cls := Smalltalk at: self classes selection.

"Update the selectors list." self methodNames list: cls selectors asSortedCollection.

"Update the instances list." self instances list: cls allInstances].
Online example: List1Example
Set list selections programmatically
Get the `SelectionInList` from the application model
To select by index, send it a `selectionIndex:` message with the index number as argument.
To select by item, send it a `selection:` message with the object as argument.
postOpenWith: aBuilder

super postOpenWith: aBuilder.

"Uncomment the line below to auto-select the first class." self classes selectionIndex: 1.

"Uncomment the lines below to auto-select the last class." "self classes selection: self classes list last. (aBuilder componentAt: #classes) widget controller cursorPointWithScrolling."

"In the classes list, use boxed highlighting instead of reverse- video." (aBuilder componentAt: #classes) widget strokedSelection.
For a `MultiSelectionInList` , send `selectionIndexes:` or `selections:` , with the appropriate collection as argument.
Online example: List1Example
Select all objects in a multiple-selection list
Get the `MultiSelectionInList` from the application model.
Send a `selectAll` message to it.
selectAll self methodNames selectAll.
Clear all selections in a multiple-selection list
Get the `MultiSelectionInList` from the application model
Send a `clearAll` message to it.
clearAll self methodNames clearAll.
Add a menu to a list
By default, a list does not have a pop-up menu, but it is sometimes useful to have a menu that includes:
Commands that act on the selection(s).
Commands that act on the list itself, usually by updating or filtering its contents.
To add a menu:
Select the list widget in the canvas.
In the GUI Painter Tool, fill in the list widget's Menu property with the name of the method that will supply the menu.
Apply
the property and Install the canvas.
Use a Menu Editor to create the menu method.
Create the methods that are invoked by the menu.
Online example: List1Example (Instances view)
Change the highlighting style
By default, selected items are highlighted with reverse video. To change this to a check mark, set the list's Selection Type property in the GUI Painter Tool.
You can programmatically arrange for selected items to be surrounded by a rectangular border.
In a method in the application model (typically `postBuildWith:` or `postOpenWith:` ), get the list widget from the application model's builder.
Send a `strokedSelection` message to the widget. (Send a `normalSelection` message to revert to normal highlighting.)
postOpenWith: aBuilder

super postOpenWith: aBuilder.

"Uncomment the line below to auto-select the first class." self classes selectionIndex: 1.

"Uncomment the lines below to auto-select the last class." "self classes selection: self classes list last. (aBuilder componentAt: #classes) widget controller cursorPointWithScrolling."

"In the classes list, use boxed highlighting instead of reverse-video." (aBuilder componentAt: #classes) widget strokedSelection.
Online example: List1Example
Connect two lists
As for any interaction between widgets, lists may be connected by declaring dependencies between them.
In the application model's `initialize` method, arrange for a change message (`changedClass` ) to be sent when the selection is changed in the first list.
initialize super initialize. classes := SelectionInList with: Smalltalk classNames. classes selectionIndexHolder onChangeSend: #changedClass to: self.

methodNames := MultiSelectionInList new. instances := SelectionInList new.
Create the change method (`changedClass` ) in the application model to update the second list (`methodNames` ) when appropriate.
changedClass | cls | self classes selection isNil

"No class is selected -- empty the selector list." ifTrue: [ self methodNames list: List new. self instances list: List new]

"A class is selected" ifFalse: [ cls := Smalltalk at: self classes selection.

"Update the selectors list." self methodNames list: cls selectors asSortedCollection.

"Update the instances list." self instances list: cls allInstances].
Online example: List1Example
Connect a list to a text editor
In the application model's `initialize` method, arrange for a change message (`changedClass` ) to be sent to the application model whenever the list selection is changed.
initialize super initialize.

comment := '' asValue.

classes := SelectionInList with: Smalltalk classNames. classes selectionIndexHolder onChangeSend: #changedClass to: self.

textStyle := #plain asValue. textStyle onChangeSend: #changedStyle to: self.

readOnly := false asValue. readOnly onChangeSend: #changedReadOnly to: self.
Create the change method (`changedClass` ) in the application model to update the text editor's value holder (`comment` ) appropriately.
changedClass "When the list selection changes, update the comment view."

| selectedClass txt start wrapper | selectedClass := self classes selection.

selectedClass isNil ifTrue: [self comment value: '' asText] ifFalse: [ txt := (Smalltalk at: selectedClass) comment.

self comment value: txt.

"Find and highlight the class name in the text." start := txt indexOfSubCollection: selectedClass asString startingAt: 1. start > 0 ifTrue: [ wrapper := (self builder componentAt: #comment). wrapper widget controller selectAndScrollFrom: start to: start + selectedClass asString size - 1. wrapper takeKeyboardFocus]].
Online example: Editor1Example
Hierarchical Lists
About Hierarchical Lists
The Hierarchical List widget will be deprecated in a future release, in favor of the Tree View widget. Unless you need functionality in the Hierarchical List that is not currently provided by Tree View, please use Tree View. The missing functionality will be added to Tree View before Hierarchical List is deprecated.
A Hierarchical List widget is similar to a List widget, but its primary component is an `IndentedTreeSelectionInList` . This object holds a tree object representing the hierarchy.
For cases where the hierarchical list is displaying a natural hierarchy, such as the class hierarchy, `IndentedTreeSelectionInList` provides instance creation methods to create the tree. Otherwise, create the tree using instances of `AssociationTreeWithParent` .
Retrieving the index and contents of a list item uses the same methods as for a List widget's `SelectionInList` .
Adding a List
Use a Palette to add a Hierarchical List widget to the canvas. Leave the list selected.
On the Basics property page, fill in the list's Aspect property with the name of the method that will return an instance of `IndentedTreeSelectionInList` .
Apply
the change, install the canvas, and use define to add an instance variable and aspect accessor method to the application model. This instance variable will hold the `SelectionInList` .
Write an `initialize` method to initialize the `IndentedTreeSelectionInList` .
Create a List for a Natural Tree
The `IndentedTreeSelectionInList` class has a few instance creation methods that make it easy to create a tree out of an object with a natural hierarchy. For instance, the class method `#listObjectHierarchy:childAccessor:childNameAccessor:` can create an appropriate list object as follows:
initialize
theList := IndentedTreeSelectionInList
listObjectHierarchy: Object
childAccessor: #allSubclasses
childNameAccessor: #name.
^self
For other instance creation convenience methods, browse the instance creation protocol.
Building a Tree
You can build a hierarchy of arbitrary objects using instances of `AssociationTree` or `AssociationTreeWithParent` . Then use the tree as the source of the hierarchy for the Hierarchical List widget.
This version of the initialize method creates a hierarchy:
initialize
tree := (AssociationTreeWithParent key: 'first' value: #first).
tree addChild:
((AssociationTreeWithParent
key: 'second'
value: #second)
addChild: (AssociationTreeWithParent
key: 'third'
value: #third);
addChild: (AssociationTreeWithParent
key: 'fourth'
value: #fourth));
addChild: ((AssociationTreeWithParent
key: 'fifth'
value: #fifth)
addChild: (AssociationTreeWithParent
key: 'sixth'
value: #sixth);
addChild: (AssociationTreeWithParent
key: 'seventh'
value: #seventh)).
myList := IndentedTreeSelectionInList
listObjectHierarchy: tree
childAccessor: #children
childNameAccessor: #displayString.
^self
Charts
About Charts
The Chart widget supports a large variety of graphical representations that are commonly used in business applications to represent numerical data. The style and appearance of the chart are set by widget properties.
The application model provides the chart data in the form of a collection of numerical data. A simple chart presents a single set or series of values. A more complex chart, comparing two or more sets of data, requires two or more collections of values, called a data series .
To use this widget, load the BGOK parcel.
Adding a Chart
To add a bar chart to an application:
Select the Chart widget in the Palette, and drop it on a canvas.
On the Basics property page, specify the chart's Aspect , which is the the name of the instance variable and accessor method that supplies the data to the chart.
In the case of a simple chart with a single series of values, the aspect returns a collection. For charts that compare multiple series of values, you set up one or more data series, as described in "Charting Multiple Data Series" .
Select the chart type and click Apply
Install the canvas.
Create a definition for the chart widget's aspect and edit it to return a value holder on the collection containing the chart data.
Charting Multiple Data Series
A more complex chart, such as one that compares performance of two or more entities over time, needs to plot more than a single series of data points. To do this, you declare the multiple data series.
The Aspect for your chart declared on the Basics property page is still the instance variable for your chart data, but it now is a collection of items with some additional structure.
The simplest case is that it is a collection of simple collections of numbers, such as the following array of arrays:
#(#(60 40) #(20 55) #(35 10) #(75 50))
The data series can be identified by index within the subcollections. (See `BG_EzChart` for an example of this approach.)
More typically, the collection will consist of structured objects, such as an Employee object, defined in a class other than the application model. In this case the data series will require accessor methods defined in the relevant class to return the collections required for charting. (See `BG_Company` for an example of this approach.)
In either case, you must specify the data series for the chart.
Add a Chart widget to a canvas.
On the Basics property page for the chart, specify the Aspect for the Chart.
On the Data Series page, click New to add a new data series, and
In the Name field, enter a descriptive name for the first set.
In the Aspect field, enter either the accessor method for the items making up this data series, or the index of this item in the subcollection.
Repeat step 3 for each additional data series.
Apply
your changes.
Using a source browser, ensure that the chart widget's aspect instance variable is initialized to the collection, and that its accessor method returns the collection as a value holder.
Adding Labels
You add labels to your chart by including the label strings as another data series.
To add labels:
On the Data Series property page, add a new data series with an appropriate name. For the Aspect , enter the index for the labels in the collection, or the name of the accessor method that returns the label from the collection members.
Apply
your changes.
Edit the application code as necessary to include the label in the collection stored in the chart widget's aspect instance variable.
Special Requirements for Charts
Most of the charts are configured as described in the preceding sections. A few charts have special requirements for formatting their data, and are described here.
Pareto
A Pareto chart consists of a bar chart in descending order of item magnitude, with the overlay graph of cumulative percentages. Used with multiple data series, it is useful for pinpointing the data series that accounts for the greatest number.
The Pareto chart can take either a single or multiple data series, as described previously.
A Pareto chart does not accept labels.
Picture
A picture chart represents the unit of a numeric item with a meaningful picture or symbol. A picture chart can represent only a single data series, but may include the labels:
#(#(60 'Jan') #(20 'Feb') #(35 'Mar') #(75 'Apr'))
For the labels to display, define two data series, and check the Label checkbox for the series holding the labels.
To replace the default picture, enter the name of the graphic into the Aspect field on the Picture section on the Options property page.
By default, the picture represents a unit of 20. To change the unit amount, enter the desired amount in the Unit field under the Picture section of the Options Properties.
XY
An XY chart indicates any correlations between two characteristics derived from the scattering status of points plotted in the chart.
The XY chart expects points as its data. For example, an XY chart with two data series could be set up as follows:
dataPointXY
| array1 array2 array3|
array1 := Array with: 20@60 with: 20@40.
array2 := Array with: 40@40 with: 40@10.
array3 := Array with: 60@75 with: 60@35.
^(Array with: array1 with: array2 with: array3) asValue
Pie Charts
A pie chart can only represent one data series, but may include labels:
#(#(25 'Jan') #(15 'Feb') #(40 'Mar') #(20 'Apr'))
Exploding Labels
In a pie chart, a slice can be designated to be exploded. For example, enter Jan in the Labels to Explode field on the Options page. Only one label can be exploded at a time.
Doughnut Labels
A doughnut label appears inside a circle in the center of a pie chart. To create a doughnut label, enter the label name in the Doughnut Label field on the Options page.
Tables
Add a table
Use a table to display data in a rows-and-columns format.
A table uses a `TableInterface` to store its collection of cells, which is stored in an instance variable of the application model.
Use a Palette to add a table widget to the canvas, leaving it selected.
On the Basics page of the GUI Painter Tool, enter `tableInterface` as the Aspect . Turn on both horizontal and vertical grid lines.
Apply
the properties and Install the canvas.
Use the canvas' define command to add the instance variables and aspect methods `sightingsTable` and `tableInterface` .
Create an `initialize` method in the application model to give a default value to the `SelectionInTable` .
initialize
| list |
super initialize.
"Create a collection of sightings data."
list := TwoDList
on: #('Vulcans' 188 173 192 'Romulans' 26 26 452) copy
columns: 4
rows: 2.
sightingsTable := SelectionInTable with: list.
"Create a table interface and load it with the sightings."
tableInterface := TableInterface new
selectionInTable: sightingsTable.
Online example: Table1Example
Control table column widths
By default, all columns have an equal width that expand and shrink according to the window size.
To set specific widths for the columns, send a `columnWidths:` message to the table interface, with an array of column widths specified in pixels.
tableInterface columnWidths: #(100 40).
In this example, the first two columns are assigned fixed sizes. These widths remain fixed even if the window is expanded.
Select by cell, row, or column
By default, a single cell in the table is highlighted when the user clicks in it. In some applications, it is more appropriate to highlight the entire row or column in which the cell is located.
To set the extent of a user selection, select either Cell , Row , or Column in the Selection set on the Details page of the GUI Painter Tool.
Connect a table to an input field
To allow the user to change values in a table cell, the entry needs to be input some other way, such as in an input field. The input can then be added to the table.
Add an input field below the table, leaving the field selected.
On Basics page of the GUI Painter Tool, enter `cellContents` as the field's Aspect property.
Use the canvas' define command to add an instance variable named `cellContents` to the `UFOtable` class, and to create a corresponding aspect method (`cellContents` ).
cellContents
^cellContents
Add the instance method `changedCell` in a `change` messages protocol.
changedCell
| cellLocation |
"Get the coordinates of the highlighted cell."
cellLocation := self sightingsTable selectionIndex.
"If a cell is selected, update its contents from
the input field."
cellLocation = Point zero
ifFalse: [self sightingsTable table
at: cellLocation
put: self cellContents value]
In the application model's `initialize` method, initialize the input field (`cellContents` ).
initialize
| list |
super initialize.
"Create a collection of sightings data."
list := TwoDList
on: #('Vulcans' 188 173 192 'Romulans' 26 26 452) copy
columns: 4
rows: 2.
sightingsTable := SelectionInTable with: list.
"Create a table interface and load it with the sightings."
tableInterface := TableInterface new
selectionInTable: sightingsTable.
cellContents := String new asValue.
self cellContents onChangeSend: #changedCell to: self.
To update the table cell, register interest in the table selection with `onChangeSend:` and trigger an update in the input field.
For code sample, see Table2Example.
Online example: Table2Example
Label columns and rows
To assign labels to columns, send an array of labels to the table interface. For row labels, you send an array of labels and specify the width of those labels.
To assign column labels, send a `columnLabelsArray:` message to the table interface with an array of label strings.
To assign row labels, send a `rowLabelsArray:` message to the table interface with an array of label strings, and a `rowLabelsWidth:` message with a width in pixels.
tableInterface
columnLabelsArray: #('Visiting Race' '1992' '1993' '1994');
rowLabelsArray: #(1 2);
rowLabelsWidth: 20.
Online example: Table3Example
Align table data and labels
By default, all cells contents are left aligned, and all labels are centered. You can align data and labels using one of the following symbols: `#left` , `#right` , `#centered` , or `#leftWrapped` .
To set the alignment for column labels, edit the `initialize` method to send a `columnLabelsFormats:` message to the table interface with the format for each column label.
To set the alignment for column data, send a `columnFormats:` message to the table interface with the format for each column.
To set the alignment for row labels, send a `rowLabelsFormat:` message to the table interface with the format.
tableInterface
columnFormats: #(#left #right #right #right);
columnLabelsFormats: #(#left #right #right #right);
rowLabelsFormat: #right.
Datasets
Add a dataset
Use a dataset to present a table of similar objects for a user to edit. Datasets allow data to be edited directly.
A dataset uses a `SelectionInList` to hold the list of objects to be displayed, along with information about the current selection.
Online example: Dataset1Example
See also:
"Datasets" in the VisualWorks GUI Developer's Guide
Change column widths
By default, all columns have a width of 80 pixels. You can set specific widths in the dataset's Column properties.
You can also change the column widths by editing the dataset in the canvas:
In the canvas, <Control>-click a column to select it.
Place the cursor near the right margin of the column.
<Control>-click and hold down the mouse button. If necessary, move the pointer toward the right margin of the selected column until the cursor changes appearance.
Drag the cursor to the right to widen the column; drag the cursor to the left to make the column narrower.
Install
the canvas.
Change column order
You can switch the order of a dataset's columns by editing it in the canvas.
In the canvas, <Control>-click a column to select it.
Place the cursor on the drag handle within the selected column.
Click on the handle and drag it to its new position.
Install
the canvas.
Disable column scrolling
You can set a dataset's columns so that they cannot be scrolled horizontally.
This is useful if you want to keep one or more columns displayed on the dataset at all times, while the others continue to scroll.
Select that column.
In the GUI Painter Tool, click the Fixed check box on the Column page.
Apply
the change and Install the canvas.
Select a column while painting
Select the dataset on the canvas.
Place the cursor inside one of the columns of the dataset.
Hold down the <Control> key while clicking the <Select> mouse button.
Move the selection to another column
Select a column in the dataset.
Click the <Select> mouse button for subsequent column selections.
Scroll dataset columns
Select a column in the dataset.
Press <Control> while using the mouse to move the scroll bars on the dataset.
Add rows programmatically
You can add rows to the dataset programmatically. This is necessary when the size of a dataset cannot be determined prior to runtime.
Use the Palette to add an action button to a canvas containing a dataset. Leave the button selected.
In the GUI Painter Tool, enter `Add Row` as the button's Label property and `addRow` as the button's Action property. Apply the properties and Install the canvas.
Using the define command or a System Browser, add the instance method `addRow` in the `actions` protocol. This method adds a new object to the list displayed by the dataset. This, in turn, adds a new row to the dataset.
addRow
(dsvList list) add: Employee new
Online example: Dataset2Example
Add a row marker
A row marker indicates which row is selected within a dataset. It is used in place of row highlighting.
To add a row marker, select Row Selector on the dataset's Details properties. The marker appears as the first column within the dataset.
Connect data to a dataset
Your application can arrange for a dataset to display data initially.
In the application model, create an `initialize` method that provides the data for your dataset.
initialize
| aList anEmp |
aList := List new.
"The aspect for the dataset should be a list of Employee instances.
Create an employee to put in the list."
anEmp := Employee new initialize.
anEmp name: 'Tami Hayes'; empNo: '341-2'; citizen: true.
aList add: anEmp.
"Create an employee to put in the list."
anEmp := Employee new initialize.
anEmp name: 'Leo Mazon'; empNo: '786-9'; citizen: false.
aList add: anEmp.
"Set the list for the dataset aspect. This list appears when you start."
self dsvList list: aList.
super initialize.
Online example: Dataset3Example
Enhance column labels
The standard Label property supports a one line string. For multiple-line or formatted labels, you can use composed text.
Create a class method in a `resources` protocol of the application model that returns a `ComposedText` object. Apply appropriate formatting to the object.
number
^('Employee
Number' asText allBold) asComposedText
In the canvas, select the column to be labelled.
In the GUI Painter Tool, enter the `label` method in the Label field on the Column page.
Select the Image check box next to Label .
Apply
the properties and Install the canvas.
Online example: Dataset4Example
Dialogs
Display a warning
Send a `warn:` message to the `Dialog` class, with a string specifying the message.
When the user clicks OK , the message returns the value `nil` . Backslash characters in the string are converted to carriage returns by the `withCRs` message.
warn | returnVal | returnVal := Dialog

warn: 'The memory named\''FirstKiss''\was not found.\' withCRs.

"Update the text field in the main window." self returnedValue value: returnVal printString.
Online example: DialogExample
Prompt for a Yes/No answer
Send a `confirm:` message to the `Dialog` class. The argument is the question to be asked.
Dialog confirm: 'Really erase all memories\of adolescent period?\' withCRs.
To specify a different default answer, send a `confirm:initialAnswer:` message to `Dialog` instead, with the as either true or false.
Dialog confirm: 'Really erase all memories\of adolescent period?' withCRs initialAnswer: false
To adopt the look of a master window, send a `confirm:initialAnswer:for:` message, with the master window as the third argument:
returnVal := Dialog confirm: 'Really erase all memories\of adolescent period?' withCRs initialAnswer: false for: ScheduledControllers activeController view .
Ask a multiple-choice question
Send a `choose:labels:values:default:` message to the `Dialog` class. The `choose:` argument is the question, the `labels:` argument is an array of `String` s for the button labels, the `values:` argument is an array of `Symbol` s to return values for each buttons, and the `default:` argument is the `Symbol` for the default answer.
Dialog choose: 'Which memory would you like to review first?' labels: #( 'Swimming the Channel' 'Triumph at the Coliseum' 'Love & War #47') values: #(#swim #triumph #love47) default: #triumph
To adopt the look of a master window, send a `choose:labels:values:default:for:` message instead, with the master window as the `for:` argument.
returnVal := Dialog choose: 'Which memory would you like to review first?' labels: #( 'Swimming the Channel' 'Triumph at the Coliseum' 'Love & War #47') values: #(#swim #triumph #love47) default: #triumph for: ScheduledControllers activeController view .
Prompt for a textual response
Send a `request:initialAnswer:` message to `Dialog` , with a prompt string and a default answer string.
Dialog request: 'Find all memories associated with...' initialAnswer: 'friend'
If there is no default answer, you can use the shorter `request:` , and provide the question string only.
Supply a cancel action
A text prompter, by default, returns an empty string when the user clicks Cancel . To provide a specific action on cancel, use the `request:initialAnswer:cancel:` message. The argument for `cancel:` is a block that returns a value or initiates some further action.
getText | returnVal | returnVal := Dialog request: 'Find all memories associated with...' initialAnswer: 'friend' onCancel: [self defaultRuminationTopic].
Online example: DialogExample
Prompt for a file name
Send a `requestFileName:default:` message to `Dialog` , with a prompter string and the name of the default file as a string.
Dialog requestFileName: 'Open memory file named...' default: 'hero01.mem'
If a default file name isn't needed, use the shorter `requestFileName:` message.
Check whether a file exists
The a `requestFileName:default:version:` message to `Dialog` supports various test symbols for the third argument to check the file existence status:
#new
indicates that you expect the file to be new
#old
indicates that you expect the file to exist
#mustBeNew
cancels if the file exists
#mustBeOld
cancels if the file does not exist
Dialog requestFileName: 'Open memory file named...' default: 'hero01.mem' version: #new
To supply a specific cancellation action, use the longer `requestFileName:default:version:ifFail:` message, with a block containing the action.
getFilename | returnVal | returnVal := Dialog requestFileName: 'Open memory file named...' default: 'hero01.mem' version: #mustBeOld ifFail: [Transcript show: 'Memory file access canceled'. '']. "Update the text field in the main window." self returnedValue value: returnVal printString.
Prompt with a list of items
To display a dialog with a list of commands or data values, send a `choose:fromList:values:lines:cancel:` message to `Dialog` . The `choose:` argument contains the prompt string; the `fromList:` argument contains a collection of strings (command names or value descriptions); the `values:` argument, a collection of values associated with the `fromList:` items; `lines:` contains the maximum number of list items to display; and `cancel:` passes a block containing the cancel action.
Each item in the list is associated with a value which can insert the selected value in a value holder or trigger an action.
| files response | files := Filename defaultDirectory directoryContents reject: [ :name | name asFilename isDirectory].

response := Dialog choose: 'Edit which file?' fromList: files values: files lines: 8 cancel: [^nil].

response asFilename edit.
To Supply extra buttons to the list prompter, send a `choose:fromList:values:buttons:values:lines:cancel:` message instead, where the `buttons:` argument is a collection of strings to be used as button labels and the `values:` argument is a collection of values to associate with the button labels.
response := Dialog choose: 'Edit which file?' fromList: files values: files buttons: #('Count Files') values: #(#count) lines: 12 cancel: [^nil].
Link a dialog to a master window
To open dialogs that mimic the colors and UI Look of a master window, use the instance opening protocol including a for: argument ( e.g., `warn:for:` ) to specify the UI Look.
To change the colors used, send a `useColorOveridesFromParent: true` message to `SimpleDialog` .
Send a `useColorOveridesFromParent: true` message to the `SimpleDialog` class. Subsequently opened dialogs will adopt the colors of their master window.
Send a `warn:for:` message to the `Dialog` class. The first argument is the message string, and the second argument is the master window.
| masterWindow | SimpleDialog useColorOveridesFromParent: true. masterWindow := ScheduledWindow new. masterWindow background: ColorValue yellow. masterWindow open.

Dialog warn: 'This dialog has a yellow background, too.' for: masterWindow.

masterWindow sensor eventQuit: nil.
To reset the `SimpleDialog` class to its default behavior, send it the `useColorOveridesFromParent: false` message with the argument .
Create a custom dialog
Create a subclass of `SimpleDialog` , reusing its protocol for `#accept` or `#cancel` . In the method that is to open the dialog, send an `openDialogInterface:` message to the application model. The argument is the dialog's interface specification.
openDialogCanvas | returnVal | returnVal := self openDialogInterface: #memoryZonesDialog. "Update the text field in the main window." self returnedValue value: returnVal printString.
Online example: DialogExample
Request actions for OK and Cancel buttons
When a custom dialog has OK and Cancel buttons, you can arrange for them to invoke predefined methods that close the dialog and return the appropriate value (`true` or `false` ).
In the canvas for the dialog, select the action button that is to accept the dialog (typically labeled OK ).
In the GUI Painter Tool, enter `accept` in the button's Action property.
In the canvas, select the button that is to cancel the dialog (typically labeled Cancel ).
In the GUI Painter Tool, enter `cancel` in the button's Action property.
Apply
the properties and Install the canvas.
Online example: DialogExample
Store a dialog builder for reference
In the method that is to open the dialog, create an instance of `SimpleDialog` .
Get the builder from the `SimpleDialog` and store it, typically in an instance variable of the application model (`dialogBuilder` ).
Send an `openFor:interface:` message to the `SimpleDialog` . The first argument is the application model so that the dialog's widgets can obtain their value models, actions, and resources from it. The second argument is the name of the dialog's interface specification.
openDialogStoreBuilder | returnVal dialogModel | dialogModel := SimpleDialog new. self dialogBuilder: dialogModel builder.

returnVal := dialogModel openFor: self interface: #memoryZonesDialog.

"Update the text field in the main window." self returnedValue value: returnVal printString.
Online example: DialogExample
Provide a temporary model for a dialog
In the method that is to open the dialog, create an instance of `SimpleDialog` .
Get the builder from the `SimpleModel` and preload it with one binding for each active widget. The `aspectAt:` argument is the symbol you specified in the widget's Aspect property. The `put:` argument is an appropriate value model.
Ask the `SimpleDialog` to open the interface.
openTempModelDialog | returnVal dialogModel list | dialogModel := SimpleDialog new. dialogBuilder := dialogModel builder.

"Since the simple model does not respond to a #memoryZones message, its builder must be preloaded with a multilist." list := MultiSelectionInList new list: self memoryZones list copy. dialogBuilder aspectAt: #memoryZones put: list.

"Open the interface." returnVal := dialogModel openFor: self interface: #memoryZonesDialog. "Update the text field in the main window." self returnedValue value: returnVal printString.
Online example: DialogExample
Subcanvases
Inherit an application's interface
You can include an entire application within a new application by making the new application a subclass of the original.
To include its user interface, the new application inserts the old user interface into a subcanvas. The new application can override an inherited action, though it need not.
Create a new application model (`Subcanvas1Example` ) as a subclass of the application model from which it is to inherit (`List2Example` ).
Use a Palette to place a subcanvas widget on the inheriting canvas (the canvas for `Subcanvas1Example` ). Leave the subcanvas widget selected.
In the subcanvas's `Canvas` property, enter the name of the inherited interface specification to be used by the subcanvas (`listSpec` ). This name must be unique within the inheritance chain--for example, you could not embed an inherited canvas named `windowSpec` in a local canvas named `windowSpec` .
Apply the property and Install the inheriting canvas in its application model (`Subcanvas1Example` ).
Online example: List2Example (parent) and Subcanvas1Example
See also:
"Subcanvas" in the VisualWorks GUI Developer's Guide
Installing a different value in an inherited widget
You can reuse widgets by providing a local value in a subclass.
Create an `initialize` method in the application model, and first invoke the inherited `initialize` method with a message to super.
In the `initialize` method, use the inherited aspect message to access the desired value model. Then send an accessing message to the value model to install the desired value.
initialize
"Install a different list (cursor names) than
the inherited default (color names)."
| cursorNames |
super initialize.
cursorNames := Cursor class organization
listAtCategoryNamed: #constants.
self selectionInList list: cursorNames.
Nest one application in another
Using a subcanvas, you can embed one application in another even though they are not related by inheritance. This is a convenient way to create a set of application modules to plug into a larger.
The embedded application supplies its own value models and action methods. Since there is no inheritance, you cannot override these actions and values.
Use a Palette to place a subcanvas in the reusing canvas, leaving it selected.
In the subcanvas's Name property, enter the name of the method that will supply an instance of the embedded application.
In the subcanvas's Class property, enter the name of the application that you are embedding.
In the subcanvas's Canvas property, enter the name of the interface specification that you are using from the embedded application.
Apply
the properties and Install the reusing canvas in its application model.
Create an instance variable in the reusing application model, for holding onto the embedded application.
Create an `initialize` method in the reusing application model, in which the embedded application is created and assigned to the variable that you created in step 6.
initialize
"Reusing List2Example's interface only -- initialize the list holder."
selectionInList := SelectionInList with: Smalltalk classNames.
"Reusing List2Example application -- initialize the application instance."
classNames := List2Example new.
classNames list: Smalltalk classNames.
Online example: `List2Example` embedded in Subcanvas2Example
Install a value in an embedded widget
An embedded widget uses the value with which its host application initializes it.
In the `initialize` method, send a message to the embedded application installing the desired value.
initialize
"Reusing List2Example's interface only -- initialize the
list holder."
selectionInList := SelectionInList with: Smalltalk classNames.
"Reusing List2Example application -- initialize the application
instance."
classNames := List2Example new.
classNames list: Smalltalk classNames.
In some situations, as in the example, you will have to create a method (`list:` ) in the embedded application model that enables an outside application to supply a new value.
list: aCollection
"Install aCollection in the list. This message is
provided so reusers can install a list that is different
than the default list (color names)."
self selectionInList list: aCollection.
Reuse an application's interface only
You can use another application's interface in your application without inheriting from that application. Your application must supply all value models and methods supporting the interface.
Because your application is supplying value models, you can reuse an interface only once on a canvas.
Use a Palette to place a subcanvas in the reusing canvas (the canvas for `Subcanvas2Example` ).
In the subcanvas's Class property, enter the name of the application (`List2Example` ) that defines the interface to be embedded.
In the subcanvas's Canvas property, enter the name of the interface specification (`listSpec` ) to be embedded.
Apply the properties and install the reusing canvas in its application model (`Subcanvas2Example` ).
Use a System Browser to edit the reusing application model (`Subcanvas2Example` ), creating instance variables (`selectionInList` ) and methods (`selectionInList` , `initialize` , `add` , and `delete` ) to support the embedded interface. These instance variables and methods must have the same names as the corresponding ones in the reused class (`List2Example` ). Modify values and action methods as desired.
Online example: Subcanvas2Example (which reuses List2Example 's `listSpec` )
Change interfaces during run-time
You can use a subcanvas to change the widgets or collections of widgets that appear in a larger canvas.
Use a Palette to place a subcanvas in the reusing canvas (the canvas for `Subcanvas3Example` ).
In the subcanvas's Name property, enter the name of the method (`embeddedApplication` ) that supplies the embedded application at startup time.
Apply the properties and install the reusing canvas in its application model (`Subcanvas3Example` ).
Use a System Browser to create the method (`embeddedApplication` ) that you named in step 2. You create this method in the reusing application model (`Subcanvas3Example` ). This method can supply either a `nil` value (for a blank subcanvas) or one of the subapplications.
embeddedApplication
^nil asValue
In a change message (presumably triggered by a change in some other widget such as a button), create an instance of the desired application model (`Editor2Example` ) and initialize it. (Or you can create and initialize the application model once at startup and store it in an instance variable.)
Continuing in the change message, get the spec object for the interface you want to use by sending an `interfaceSpecFor:` message to the embedded application model's class (`Editor2Example` ). The argument is the name of the interface specification (`#windowSpec` ).
Continuing in the change message, get the subcanvas from the builder and send a `client:spec:` message to it. The first argument is the application you created in step 5. The second argument is the spec object you obtained in step 6.
showComment
| selectedClass subcanvas spec application |
selectedClass := Smalltalk at: self classNames selection.
"Create the subapplication and initialize it."
application := Editor2Example new.
application text value: selectedClass comment.
"Get the spec object for the embedded canvas."
spec := Editor2Example interfaceSpecFor: #windowSpec.
"Get the subcanvas and install the editing application."
subcanvas := (self builder componentAt: #subcanvas) widget.
subcanvas client: application spec: spec.
Online example: Subcanvas3Example (which swaps Editor2Example and List2Example )
Make a subcanvas blank
You can empty a subcanvas at run time.
Get the subcanvas from the builder.
Send a `client:` message to the subcanvas with a `nil` argument.
showNothing
| subcanvas |
subcanvas := (self builder componentAt: #subcanvas) widget.
subcanvas client: nil.
In `Subcanvas3Example` , the subcanvas goes blank when no class is selected.
Online example: Subcanvas3Example
Accessing an embedded widget
Frequently an embedded or inherited interface contains more than you need. For example, when an embedded action button is not appropriate in the local application, you could make it invisible or disable it. Before you can manipulate embedded widgets, however, you need to access them.
Before installing the new subapplication using `client:spec:` , initialize the subapplication's builder to `nil` . (Otherwise, the subapplication will continue to hold the old builder even after a new builder is created to assemble the new interface.)
Ask the subapplication for its builder and then send `componentAt:` to that builder. The argument is the ID of the desired widget.
showMethods
| selectedClass subcanvas spec |
selectedClass := Smalltalk at: self classNames selection.
spec := List2Example interfaceSpecFor: #listSpec.
"Install the method names as the collection in the list application."
self listApplication list: selectedClass selectors asSortedCollection.
"Set the subbuilder to nil to discard the old builder. This is only
necessary when the application uses the builder later to access widgets."
listApplication builder: nil.
"Get the subcanvas and install the list application."
subcanvas := (self builder componentAt: #subcanvas) widget.
subcanvas client: listApplication spec: spec.
"Disable the embedded buttons (just to show that we can)."
(listApplication builder componentAt: #addButton) disable.
(listApplication builder componentAt: #deleteButton) disable.
Online example: Subcanvas3Example