JBuilder wizard concepts

General description

A wizard is a standardized modal dialog box that has one or more panels that walk a user through an otherwise complicated or tedious task.

Wizards usually appear in the IDE either in the Object Gallery (for those wizards that construct source code for objects) or under the Wizards menu on the menu bar. Other possible locations (such as on the toolbar and on pop-up context menus) require different registration methods. To display the Object Gallery, choose File|New.

To write a new wizard, you must perform at least these steps:

Like other OpenTools, wizards are introduced into JBuilder at application startup during the OpenTools discovery process. Each wizard provides a static initOpenTools() method that registers its static WizardAction method with the WizardManager. A WizardAction provides information that helps integrate that wizard with JBuilder and acts as a factory when an instance of that wizard is needed.

The entire JBuilder IDE is based on the Swing component architecture. All wizard UI components must be Swing-based.


Detailed description of feature/subsystem

OpenTools registration

The new wizard must provide the standard interface looked for by the OpenTools discovery process and use it to register its WizardAction. The most common way to integrate a wizard is to register it with the WizardManager in the initOpenTool() method. Here is an example:
  public static void initOpenTool(byte majorVersion, byte minorVersion) {
    if (majorVersion == PrimeTime.CURRENT_MAJOR_VERSION) {
      WizardManager.registerWizardAction(myWizard);
    }
  }

A WizardAction is essentially a cookie describing the wizard. Use one of its several constructors, which provide ever increasing overrides of default behavior. This example uses the constructor that takes six parameters: shortText, mnemonic, longText, smallIcon, largeIcon, and galleryWizard:

  public static final WizardAction myWizard = new WizardAction (
    "My Wizard...", 'm', "My wizard description",
    BrowserIcons.ICON_BLANK,
    BrowserIcons.ICON_BLANK,
      false) {
    protected Wizard createWizard() {
      return new MyWizard();
    }
  };

Because the final galleryWizard parameter is false, this wizard doesn't appear in the Object Gallery, but on the Wizards menu. If the wizard appears in the Object Gallery, the specified large icon is used and the longText parameter value is the description that appears as the wizards tool tip. If the wizard appears on the Wizards menu, the small icon is used and the description appears on the status bar when the mouse is over the wizard menu item.

Wizard flow control

The BasicWizard class controls the flow of the wizard by defining which pages will appear and in what order. When you extend this class, you must override the invokeWizard() method to call setWizardTitle(), and then call addWizardPage() for each BasicWizardPage you defined. Here's an example:
  MyWizardPage1 page1 = new MyWizardPage1();
  public WizardPage invokeWizard(WizardHost host) {
    setWizardTitle("My Wizard");
    addWizardPage(page1);
    return super.invokeWizard(host);
  }

The BasicWizard class also supplies the finish() method that is called when the user clicks the OK or Finish button in the wizard, indicating the wizard should perform its assigned tasks. In this example, you would put the primary logic of the wizard within the doIt() method:

  protected void finish() throws VetoException {
    doIt();
  }

Wizard steps

Each BasicWizardPage class provides the UI for a single panel (sometimes referred to as a step) of the wizard.

There aren't any methods that you must override in the class. Usually the content consists of Swing components that form the panel user interface. You can design the page with JBuilder's UI designer.

If the default large icon on the left of the page is in the way, you can switch to a smaller icon by calling the setPageStyle(STYLE_COMPEX) method. Depending on which page style you choose and if you want to supply a different icon than the default, you can also call setSmallIcon() or setLargeIcon().

You might find the activated() method of the BasicWizardPage useful. It provides a way of initializing fields based on the input from prior pages.

If you want the wizard to validate the user input before the user advances to the next page or clicks the OK or Finish button, you can override the checkPage() method, supply the validation logic, and then throw a VetoException if any validation errors exist. Here's an example:

  public void checkPage() throws VetoException {
    if (checkForError()) {
      JOptionPane.showMessageDialog(wizardHost.getDialogParent(),
                                   "We have a problem.",
                                   "Error",
                                   JOptionPane.ERROR_MESSAGE,
                                   null);
      throw new VetoException();
    }
  }

Advanced features

A WizardAction can optionally enable itself depending on the current state of the environment. You can do this by overriding the update() method and using the supplied Browser reference to determine if conditions are suited for the wizard. For instance, you could have update() check if there is a project open, test the file type of the active node if there is one, and/or check whether a needed class can be found on the current classpath.

  public static final WizardAction myWizard = new WizardAction (
    "My Wizard...", 'm', "My wizard description",
    BrowserIcons.ICON_BLANK,
    BrowserIcons.ICON_BLANK,
      false) {
    public void update(Object source) {
      Browser browser = Browser.findBrowser(source);
      Node node = browser.getActiveNode();
      setEnabled((node != null) && (node instanceof JavaFileNode));
    }
    protected Wizard createWizard() {
      return new MyWizard();
    }
  };

You can have a BasicWizard dynamically change the flow of the wizard pages by overriding the checkPage() method. Usually checkPage() is called on the currently visible BasicWizardPage to validate its user input. Instead, for example, you can include code that, based on the input on the first page, dynamically alters the flow for the rest of the wizard. Here's an example:

  protected void checkPage(WizardPage page) throws VetoException {
    super.checkPage(page);

    if (page == step1) {
      removeWizardPage(step2);
      removeWizardPage(step3);
      removeWizardPage(step4);
      if (step1.getChoice()) {
        addWizardPage(step2);
        addWizardPage(step4);
      }
      else {
        addWizardPage(step3);
      }
    }
  }

Occasionally you might want to have the wizard validate input entered into a text field and dynamically alter the state of the OK/Finish or Next button accordingly. An easy way to do that is have your implemented BasicWizardPage class extend javax.swing.event.DocumentListener and add itself as a listener for that field:

  jTextField1.getDocument().addDocumentListener(this);

Then the implementation of the interface would be similiar to this:

  public void insertUpdate(DocumentEvent e) {update();}
  public void removeUpdate(DocumentEvent e) {update();}
  public void changedUpdate(DocumentEvent e) {update();}

  private void update() {
    String text = jTextField1.getText().trim();
    if (wizardHost ! = null){
      boolean valid = (text.length() > 0);
      wizardHost.setNextEnabled(valid);
      wizardHost.setFinishEnabled(valid);
    }
  }

In a multiple step wizard, the user might use the Back button to revisit a prior step. It's also possible that you might want to enable the Finish button before the last step, thereby allowing the user to accept all the defaults for steps not visited. It then makes sense to architect such a wizard with a class that initializes all the default settings. A reference to that class would be passed when creating each wizard page, and each would initialize by accessing that class when activated and update it when deactivated. The actual logic invoked by Finish would probably reside there as well.

Testing

The heart of each BasicWizardPage implementation is the user interface it presents. To ease future localization, you should make the the layout as flexible as possible to accomodate labels and text that might greatly decrease or increase in width once translated. When the wizard frame is stretched larger, the layout for each page should be designed to stretch with it so the user can adjust for any components that might have been clipped despite your best efforts. When there is extra vertical space, components should move upwards rather than centering.

You should assign keyboard accelerators to all input fields. Avoid using the same accelerator keys as are used by the Back, Next, Finish, and Help buttons if you are implementing a multi-page wizard.

Check the order in which focus moves between components when the user uses the Tab key to ensure movement is in the order the user of that page would expect.

Using the Tools|IDE Options dialog box, switch between alternative Look & Feel settings and verify that the new wizard still has an acceptable layout and colorization in each.

Compare your wizard to the other JBuilder wizards and attempt to present a similar user interface. For instance, JBuilder wizards capitialize only the first word in a label and end the text with a colon. When you put a group box around controls, use the following to make the box appear similar to the native wizards:

    JPanel myGroup = new JPanel();
    myGroup.setBorder(BorderFactory.createTitledBorder("My group:"));