Wizard builder Merlin

Merlin Wizard

Moose has this nice library for building wizards called Merlin. Unfortunately there’s really no documentation apart from couple of examples. Because I need to use this library (I’m sick of creating forms by hand like some kind of animal…), I might as well publish my notes.

Installation

Merlin is available by default in the Moose distribution; for regular Pharo install it via Metacello.

Merlin installation script
1
2
3
4
5
Metacello new
configuration: 'Merlin';
version: #development;
repository: 'http://smalltalkhub.com/mc/Moose/Merlin/main';
load

Composition

As you can see in the introductory image, the UI consists of three parts:

  • WizardControl — the main container
  • WizardPane — a single step of the wizard
  • WizardPart — a single item in a pane
dead-simple example
1
2
3
4
5
6
7
8
9
control := WizardControl new.
pane := WizardSinglePane new.
pane name: 'My Wizard'.
pane
addPart: (CheckboxPart new label: 'label')
associatedTo: #checkBox.
control addPane: pane.
control atEndDo: [ :dictionary | dictionary inspect ].
control open
my-wizard.png

So you basically create an instance of WizardControl, add to it a bunch of WizardPanes, and each pane populate with WizardParts.

Creating a wizard

1. WizardControl

To start creating a new wizard, you need an instance of WizardControl.

1
control := WizardControl new.

With that, you are interested mainly in these three methods:

  • addPane: to add a new pane
  • open to open the wizard
  • atEndDo: aOneArgBlock that will be evaluated when the wizard finished

Alternatively to atEndDo: you can also use wizardInformations. Both will provide you with a Dictionary containing the value of each individual Part.

2. WizardPane

Now you need to populate the wizard with panes.

1
2
3
4
control
addPane: (WizardFirstPane new);
addPane: (WizardMiddlePane new);
addPane: (WizardLastPane new).

There are several choices for a WizardPane:

WizardPane withAllSubclasses
  • WizardSinglePane — used only if the wizard has a single pane; adds the Finish button
  • WizardFirstPane — first pane of a multipane wizard; adds the Next button
  • WizardMiddlePane — middle pane; has the Back and Next buttons
  • WizardLastPane — last pane; has the Back and Finish buttons
  • WizardMessagePane — ¯\_(ツ)_/¯

Just make sure you are adding the panes in the correct order (first, middle*, last) XOR (single). You can also name the pane with pane name: 'My Pane' (it will appear as the title of the wizard for the active pane).

3. Adding Parts

To add parts to a pane, use one of the following methods

  • addPart:associatedTo:
  • addPart:associatedTo:requiring:
  • column:associatedTo:
  • column:associatedTo:requiring:
  • row:associatedTo:
  • row:associatedTo:requiring:

associatedTo: takes a symbol under which you can retrieve the value (output) of the part in atEndDo: (see WizardControl).
requiring: takes a list of symbols this Part needs to operate
addPart: takes an instance of WizardPart, and
column: or row: takes either a part, or a block, e.g.

1
2
3
4
5
lastPane
column: [ :c |
c row: part3 associatedTo: #targetVersionNumber.
c row: part4 associatedTo: #versionSelector
]

With a combination of row: and column: you can customize the layout of the wizard to your needs.

4. WizardPart

WizardPart withAllSubclasses (click to zoom in)

Finally you have a wealth of WizardParts: checkboxes, file inputs, text inputs, etc. Not much point going through them one by one, look at the API to see how each should be used.

examples of WizardParts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|control pane textField checkBox list|
control := WizardControl new.
pane := WizardSinglePane new.
pane name: 'GUI is a pane'.
textField := TextFieldPart new
inGroupboxNamed: 'Group Name';
textEntryContents: 'default text';
prefix: 'Enter number 1-3 here:';
defaultValue: '2';
textFieldHorizontalSize: 100.
checkBox := CheckboxPart new
label: 'Check me out!';
defaultValue: true.
list := ListPart new
list: #('first' 'second' 'third');
defaultValue: [ :inputs | (inputs at: #text) asNumber ].
pane
row: textField associatedTo: #text;
row: checkBox associatedTo: #checkBox;
row: list associatedTo: #list requiring: #(text).
control addPane: pane.
control open.
pain.png

Two methods common to all parts that might be interesting:

  • inGroupboxNamed: aString — a label container around the input
  • defaultValue: aValueOrBlock — a default value or a one arg block of required inputs
list depending on a number
1
2
3
4
5
6
7
textPart := TextFieldPart new.
listPart := ListPart new
defaultValue: [:inputs | (inputs at: #text) asNumber ];
list: #('yi' 'er' 'san').
pane1 row: textPart associatedTo: #text.
pane2 row: listPart associatedTo: #list requiring: #(text).

More examples

To see the wizard in action, it’s best to download the latest Moose image from Moose site and take a look at the subclasses of WizardControl and at the class-side of MerlinExamples.