Copyright © 2004 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:
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
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.
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
List of Examples
Table of Contents
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.
Further information and the latest version of jwizz can be obtained from http://javaprog.net/jwizz/
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:
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.
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.
Table of Contents
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.
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.
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.
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
.
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.