Several times I needed to implement wizards that have variable flow (i.e. user input on one page defines if next page is shown and what next page(s) will be). At first I felt it is complex and pretty counter-intuitive to implement the behavior – but the problem was that I didn’t quite understand the Wizard logic.
Usually JFace wizard is a one instance of org.eclipse.jface.wizard.IWizard with one or more org.eclipse.jface.wizard.IWizardPage pages. My mistake was that I assumed that both Wizard and Pages are “Views” in MCV terms – so I was confused on how to create the logic. But actually Wizard is a controller and Pages are views. What this means is that:
- Wizard instance is responsible for “directing” wizard flow.
- Wizard instance bridges between “model” and “pages” – i.e. it initializes pages from model, interprets information from pages.
- Pages are responsible for information presentation, user entry interpretation, validation, etc.
- Wizard depends both on model and views (pages), views are self-contained.
To create wizard with non-lineal flow I do the following:
In addPages method I add all the pages – independent on wether they are always shown or not:
public void addPages() {
firstWizardPage = new FirstWizardPage();
addPage(firstWizardPage);
optionalWizardPage = new OptionalWizardPage();
addPage(optionalWizardPage);
lastWizardPage = new LastWizardPage();
addPage(lastWizardPage);
}
Thus all pages will be properly initialized by Wizard, all controls will be created and the wizard dialog size will be set to the biggest of the pages so no clipping will occur.
Then I override getNextPage and getPreviousPage in the Wizard. Depending on user input (or model state) wizard decides what pages should be shown next. In my example first wizard page is simply a page with single checkbox – so I added “isChecked” method that returns the checkbox state:
@Override
public IWizardPage getNextPage(IWizardPage page) {
if (page == firstWizardPage) {
if (firstWizardPage.isChecked()) {
return optionalWizardPage;
} else {
return lastWizardPage;
}
}
return super.getNextPage(page);
}
@Override
public IWizardPage getPreviousPage(IWizardPage page) {
if (page == lastWizardPage) {
if (firstWizardPage.isChecked()) {
return optionalWizardPage;
} else {
return lastWizardPage;
}
}
return super.getPreviousPage(page);
}
In more complex scenarios I use these methods to refresh the model with user entry and propagate the changes to other pages (i.e., if user entry on one page changes the contents of the list on another.
When user input needs the wizard to refresh the workflow I add a call to getContainer().updateButtons() – i.e. in my example I added this to checkbox selection listener. This will call getNextPage, getPreviousPage and some other wizard methods in wizard-independent mode.