SIDEBAR
»
S
I
D
E
B
A
R
«
[Tip] JFace Wizards With Non-Lineal Flow
August 22nd, 2009 by Eugene Ostroukhov

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:

  1. Wizard instance is responsible for “directing” wizard flow.
  2. Wizard instance bridges between “model” and “pages” – i.e. it initializes pages from model, interprets information from pages.
  3. Pages are responsible for information presentation, user entry interpretation, validation, etc.
  4. 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);
}</p>

<p>@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.


4 Responses  
  • Eric Rizzo writes:
    August 22nd, 200911:45 pmat

    I bet that databinding could be used to make the logic in getNextPage() and getPreviousPage() trivial. What I’m thinking is to include nextPage and previousPage attributes in the model and then bind the page UI components to those attributes (using ComputedValue?) so that as the UI is changed the next/previous attributes are automatically updated. That way, getNextPage()/getPreviousPage() become trivial – they just return the current value from the model.

    • zzhou writes:
      August 23rd, 20099:27 amat

      I don’t think that model needs to know about wizard pages – this will make it less reusable. So computing the wizard pages to show should still be wizard’s responsibility – and I don’t know if the wizard supports databindings – I really have no experience in that field.

  • Aurelien Pupier writes:
    August 23rd, 200912:30 amat

    Isn’t IWizardNode here to do such things?

    http://help.eclipse.org/stable/index.jsp?topic=/org.eclipse.platform.doc.isv/reference/api/org/eclipse/jface/wizard/class-use/IWizardNode.html

    In your case, all IWizardPages are created when the IWizard is created. For complex IWizard, perhaps it can take a while. So IWizardNode seems to allow a lazy start.

    • zzhou writes:
      August 23rd, 20099:25 amat

      My understanding is that wizard node is for the cases like File/New/Other or File/Import – when first wizard page allows the user to select one of the other wizards to run. I don’t think it can always be used when you have “optional” pages like in the cases I’m trying to describe.

      Re: complex wizard – in our product (MyEclipse IDE) we have quite a few wizards and I never noticed init to take noticeable time. But I see several cases that cause it: 1. Complex init (i.e. depending on a model pages need to do some complex computations) – I would make that code invoked only when wizard getNextPage/getPreviousPage is called – thus only 3 pages will be kept in actual state – but that’s ok, wizard will know when the user moved to another page. 2. Complex pages with lots of widgets/really a lot of pages – in this case I would consider other options, notably http://code.google.com/p/screen-flows-in-eclipse/ (I don’t know if sources are available). I don’t think creating complex wizard is a good thing – I’m pretty sure there will be some reasons the user will want to interrupt flow on page 27 of 35 and exit the wizard. Using screen flow in editor will let the user to switch to other editor, I think there should be also a way to store the flow state for later. 3. In some rarer cases there is no way to go without creating pages later – i.e. when users does selection on the first page and you need to have a wizard page for each item. You can create pages later – but it makes the logic a bit more complex so I don’t like doing that without real need. More complex logic => less reliable => more bugs…


»  Substance:WordPress   »  Style:Ahren Ahimsa