jwizz Developer Manual

Michael Rudolf

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  3. Neither the name Michael Rudolf nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


Table of Contents

1. Introduction
Motivation
Additional Information
2. Using jwizz
Creating a simple wizard
Using dynamic step sequences
Adding JavaHelp™ support
3. Customizing jwizz
Creating a custom step list renderer
Installing your own step description renderer
Achieving Look & Feel specific changes
Internationalizing jwizz

List of Examples

2.1. A basic Step
2.2. A simple WizardModel
2.3. A step sequence using DataModel
2.4. Adding a listener to the model
2.5. Showing the wizard
2.6. Altering the step sequence
2.7. Provide global help for wizard
2.8. Provide local help for steps
3.1. Implementing the StepListRenderer interface
3.2. Implementing the StepDescriptionRenderer interface
3.3. Creating a new UI handler
3.4. Registering a custom UI handler
3.5. Providing a custom resource bundle

Chapter 1. Introduction

Motivation

Every end-user application aiming at a broad audience has to offer a certain degree of usability and thus wizards guiding users through long or complex data input processes form a central part of them. With the rise of feature-rich Java programs, such as text processors or personal information management applications, the need for a simple-to-use, extensible wizard library came up. When I was looking for something suitable I couldn't find anything appropriate and therefore developed the jwizz framework.

Additional Information

Further information and the latest version of jwizz can be obtained from http://javaprog.net/jwizz/

Chapter 2. Using jwizz

Creating a simple wizard

The jwizz framework is modeled after the MVC design pattern used throughout Swing and therefore separates the data model from its representation. The latter provides several buttons for the user to navigate the step sequence assembled in an instance of WizardModel. This model also offers the ability to register listeners in order to get notified on certain events (i.e. when the user dismisses the wizard). The data collected and used by the steps in the WizardModel can be made accessible via the class DataModel. Consequently you need to perform at least three steps to set up a usable wizard: Create an instance of WizardModel, register a WizardModelListener with it, initialize and show the wizard passing it the model.

The easiest way of creating a WizardModel is to use the DefaultWizardModel implementation and pass it an array of Step instances. The latter can be obtained by subclassing AbstractStep and providing a component to be shown to the user.

Example 2.1. A basic Step

class WelcomeStep extends AbstractStep {
    public WelcomeStep() {
        //pass step title and description
        super("Welcome", "Welcome to this wizard");
    }

    protected JComponent createComponent() {
        //return component shown to the user
        JPanel stepComponent = new JPanel();
        stepComponent.add(new JLabel("Welcome!"));
        return stepComponent;
    }
}

Example 2.2. A simple WizardModel

WizardModel model = new DefaultWizardModel(new Step[]{
    //populate wizard model with custom steps
    new WelcomeStep(),
    new DataInputStep(),
    new FinishStep()
});

However, this WizardModel does not provide any data link between the single steps, therefore you should make use of an additional class called DataModel. It acts as a kind of registry, enabling steps to export and share the data they collected.

Example 2.3. A step sequence using DataModel

//create data model - use this in listener later on
DataModel data = new DataModel();
WizardModel model = new DefaultWizardModel(new Step[]{
    //populate wizard model with custom steps
    new WelcomeStep(),
    new DataInputStep1(data),
    new DataInputStep2(data),
    new FinishStep()
});

In order to be able to react to a user cancelling or finishing the wizard we need to register a listener with the wizard's step model:

Example 2.4. Adding a listener to the model

model.addWizardModelListener(new MyListener());
...
class MyListener implements WizardModelListener {
    public void wizardFinished(WizardModelEvent e) {
        //do something with the collected data.
    }

    //methods not implemented
    public void wizardCanceled(WizardModelEvent e) {}
    public void stepShown(WizardModelEvent e) {}
    public void wizardModelChanged(WizardModelEvent e) {}
}

Now you can create the actual wizard instance, passing the model to the constructor, and show it on the screen:

Example 2.5. Showing the wizard

Wizard wizard = new Wizard(model, "My Wizard");
//show wizard
wizard.pack();
wizard.setLocationRelativeTo(null);
wizard.setVisible(true);

Using dynamic step sequences

Due to the complexity of processes guided through by wizards, adaptivity to previously collected data is a common feature. Thus, jwizz provides dynamic step sequences by the means of the StepModelCustomizer interface. By implementing it, a decisive step can control the pending steps and consequently offer an appropriate reaction to the user's choice. A common situation is, for example, the prompt for a data location: In case the user inputs a URL pointing to a remote host the next step could ask for authentication information while a local file wouldn't need this.

Example 2.6. Altering the step sequence

class MyDecisiveStep extends AbstractStep implements StepModelCustomizer {
    public MyDecisiveStep() {
        super("Step title", "Step description");
    }

    protected JComponent createComponent() {
        //return a component providing a choice
    }

    public Step[] getPendingSteps() {
        //return an array of pending steps depending on the choice made
    }
}

Whenever the default WizardModel implementation encounters such a step it displays three dots in the step list indicating that the decision about the pending steps has to be made in that step. The array returned by getPendingSteps can of course contain other decisive steps.

Adding JavaHelp™ support

Although the concept of managing long and difficult input tasks with wizards already is a great improvement in terms of usability, providing further help is never wrong. The jwizz framework therefore offers a simple way of integrating JavaHelp technology - first register a help ID with the wizard instance and then use the JavaHelpSupport class to tie together the wizard and the help broker object:

Example 2.7. Provide global help for wizard

//initialize Wizard instance
Wizard wizard = ...;
//initialize HelpBroker instance
HelpBroker hb = ...;
//set help ID using JavaHelp helper class
CSH.setHelpIDString(wizard, "myHelpID");
//activate JavaHelp
JavaHelpSupport.enableHelp(wizard, hb);

Note: If you want more precise help for single wizard steps, you can also attach a help ID to each step component:

Example 2.8. Provide local help for steps

public class MyStep extends AbstractStep {
    ...
    protected JComponent createComponent() {
        //initialize step component
        JComponent stepComponent = ...;
        //set help ID using JavaHelp helper class
        CSH.setHelpIDString(stepComponent, "myHelpID");
        return stepComponent;
    }
}
...
//activate JavaHelp
JavaHelpSupport.enableHelp(wizard, hb);

The wizard's graphical representation will then contain a help button next to the navigational buttons and it will react to a user pressing the F1 key.

Chapter 3. Customizing jwizz

The jwizz dialog window consists of four parts: the step component, the navigator panel, the step description renderer (optional), and the step list renderer (optional). Which of these components are shown and where they are displayed is determined at runtime. The default implementation uses the underlying Look & Feel which in turn is based on the operating system in order to compose the dialog. However, you can easily alter this behavior.

Creating a custom step list renderer

In order to make jwizz use your own component for rendering the step list you need to call setStepListRenderer and provide an implementation of the StepListRenderer interface before showing the wizard on screen. The recommended technique is to create a suitable component subclass implementing that interface and returning a reference to itself in getStepListRendererComponent. This way the component is only instantiated once. Everytime the user navigates to the next or previous step your implementation will be notified through the updateStepList method.

Example 3.1. Implementing the StepListRenderer interface

wizard.setStepListRenderer(new MyStepListRenderer());
...
public class MyStepListRenderer
    extends JPanel implements StepListRenderer {

    public Component getStepListRendererComponent(Wizard w) {
        //avoid creating a new object each time this method is called
        return this;
    }

    public void updateStepList(WizardModel m) {
        //update displayed information
    }
}

However, if you don't want the step list to be displayed at all you can simply call setStepListRenderer with null as parameter.

Installing your own step description renderer

The procedure with setting up a custom step description renderer component is similar to the one described in the first section: call setStepDescriptionRenderer and pass a component subclass implementing the StepDescriptionRenderer interface. The updateStepDescription method will be called whenever the current step changes and your component gets a chance to change the displayed information.

Example 3.2. Implementing the StepDescriptionRenderer interface

wizard.setStepDescriptionRenderer(new MyStepDescriptionRenderer());
...
public class MyStepDescriptionRenderer
    extends JPanel implements StepDescriptionRenderer {

    public Component getStepDescriptionRendererComponent(Wizard w) {
        //avoid creating a new object each time this method is called
        return this;
    }

    public void updateStepDescription(Step s) {
        //update displayed information
    }
}

Passing null to the setStepDescriptionRenderer method will prevent any step description renderer component from being shown.

Achieving Look & Feel specific changes

In case you want to change the appearance of jwizz only for a specific Look & Feel or platform you can either create a new UI handler by subclassing BasicWizardContentPaneUI or modify the UIManager properties at runtime. A new UI handler class should register the necessary property values with the UIManager in the constructor and overwrite additional methods for modifying the wizard's behavior.

Example 3.3. Creating a new UI handler

public class MyUIHandler extends BasicWizardContentPaneUI {
    public MyUIHandler() {
        String prefix = getPropertyPrefix();
        UIManager.put(prefix + "stepListRenderer", null);
    }

    public static ComponentUI createUI(JComponent c) {
        return new MyUIHandler();
    }

    protected BasicWizardNavigator createWizardNavigator() {
        return ...;
    }
}

The custom UI handler eliminates the step list renderer installed in BasicWizardContentPaneUI and sets up a new navigator component. To be able to use the new UI handler, you have to register it with the UIManager.

Example 3.4. Registering a custom UI handler

UIManager.getLookAndFeelDefaults().put("WizardContentPaneUI",
  MyUIHandler.class.getName());
wizard.updateUI(); //only necessary if there already is a wizard object

Internationalizing jwizz

Currently jwizz supports the following languages: English, German, Spanish, French and Italian. Making jwizz appear in other languages than the default ones is easy to achieve: Register a resource bundle with the UIManager using the put method and the key Wizard.resources before constructing a Wizard object. If none is registered, the default resources will be used.

Example 3.5. Providing a custom resource bundle

ResourceBundle myResourceBundle = ...;
UIManager.put("Wizard.resources", myResourceBundle);
Wizard wizard = new Wizard(model, "My Wizard");
...

The custom resource bundle will be used with every following wizard instance unless you remove it from the UIManager by calling the put method with a null value before creating another instance.