This article discusses complex user-interface components called wizards, which you probably use almost every day. But did you know that you can use them in your own Java applications? Wizards help solve common tasks--for example, setting up a network connection or copying pictures from your digital camera onto your computer--by guiding you through such processes step by step.
In the first section we will take a closer look at what wizards are, how they work, and, of course, why you should consider using them in your programs. I will then introduce you to a project hosted here at java.net which provides a very easy-to-use implementation of a wizard component. In the remaining sections you will learn how to obtain, install, and use this library.
What Are Wizards?
To get an understanding of how wizards work, let us examine one of my introductory examples in greater detail. The process of copying pictures from your digital camera onto your computer involves several steps. These are:
Connecting the device
Specifying the source
Choosing which images should be transferred and deciding whether they should be copied or moved
specifying the destination
Transferring the pictures
Optionally removing the pictures from the camera
The scanner and camera wizard of Windows XP, which you can see in Figure 1, implements these steps.
Figure 1. Microsoft Windows scanner and camera wizard
You can cycle through these steps using the Next and Previous buttons or cancel the whole process. The camera wizard will start transferring pictures as soon as the user has finished step 4, which means that all information has been gathered. Please keep that in mind; I will turn back to this implementation detail later. Let's now have a look at another wizard, shown in Figure 2.
Figure 2. The NetBeans IDE New Project wizard
The NetBeans IDE makes use of wizards on several occasions. For example, it guides you through the process of creating a new project using a wizard, which you can see in Figure 2. The steps involved here are:
Choosing the type of project
Specifying its name and location
Setting aside some minor differences, the overall visual appearance of both wizards seems similar. Still, there is one fundamental difference. The NetBeans wizard will not create projects, files, or directories until you explicitly click the Finish button. But Microsoft's camera wizard starts copying or moving pictures automatically; the odd consequence is that after it has finished its job, you can still click Back, even though there are no more pictures if you decided to delete them from the camera. The underlying question is whether actually carrying out the task should part of the wizard. This, as we have seen, is implementation-dependent.
So, what is a wizard? From a user's point of view, wizards help with complex tasks by breaking them into logical pieces. These pieces are represented by panels or dialogs that display information or require input. The user can navigate through these steps and even alter previously entered data until she has completed all required steps. From the programmer's perspective, a wizard collects information: it comprises one transaction. This means that no changes should be made until the user has completed all the steps and finally clicked on a Finish button.
Wizards have gained widespread acceptance in recent years because they divide complex tasks into small, manageable pieces. They are understood easily, even by inexperienced users. Additionally, previously made decisions can be altered by simply revisiting an earlier panel.
The Wizard Project at java.net
The Wizard project is a subproject of SwingLabs, which allows experimentation with extensions to existing Swing components as well as completely new ones. These new or enhanced components might be included in future versions of the JDK. Wizard is a class library that aims to provide a simple API for writing wizards. Its goal is to offer an easy-to-use solution that enables any Swing application to provide wizards with minimal code and effort.
To use the Wizard API, you can check out its sources from the CVS repository and build it on your own, or you can download a ready-to-use JAR file and documentation. Visit the Resources section for details. To use the JAR, remember to expand the class path of your application or to copy the Wizard class library to the extension directory of your Java runtime (my article The Java Extension Mechanism contains an in-depth-discussion of this Java feature).
A Simple Example
After quite a lot of theory, you probably want to see a wizard do its magic. Please have a look at WizardPanelProviderDemo.java.
package wizarddemo;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JPanel;
import org.netbeans.api.wizard.WizardDisplayer;
import org.netbeans.spi.wizard.Wizard;
import org.netbeans.spi.wizard.WizardController;
import org.netbeans.spi.wizard.WizardException;
import org.netbeans.spi.wizard.WizardPage;
import org.netbeans.spi.wizard.WizardPanelProvider;
public class WizardPanelProviderDemo
extends WizardPanelProvider {
private static final String ID = "page1-id";
public WizardPanelProviderDemo() {
super("A Demo Wizard",
new String [] {ID},
new String [] {"Page #1"});
}
protected JComponent createPanel(
final WizardController wizardController,
String str, final Map map) {
if (str.equals(ID) == false) {
System.err.println("I should
never need to print that!!");
return null;
} else {
JPanel p = new JPanel(
new FlowLayout(FlowLayout.LEADING));
final JCheckBox cb =
new JCheckBox("You must select me to finish", false);
ActionListener al = new ActionListener() {
public void actionPerformed(ActionEvent e) {
boolean sel = cb.isSelected();
wizardController.setProblem(
sel ? null : "please select the checkbox");
map.put("boxSelected",
sel ? Boolean.TRUE : Boolean.FALSE);
}
};
cb.addActionListener(al);
al.actionPerformed(new ActionEvent(cb,
0, cb.getActionCommand()));
p.add(cb);
return p;
}
}
protected Object finish(Map settings) throws WizardException {
Set keys = settings.keySet();
Iterator it = keys.iterator();
while (it.hasNext()) {
Object key = it.next();
System.out.println(key + "=" + settings.get(key));
}
return settings;
}
public static void main(String [] args) {
WizardPanelProvider provider = new WizardPanelProviderDemo();
Wizard wizard = provider.createWizard();
Object result = WizardDisplayer.showWizard(wizard);
}
}
This creates the Wizard GUI shown in Figure 3.
Figure 3. WizardPanelProviderDemo
Creating and displaying the wizard shown in Figure 3 consists of three steps:
Preparing the wizard by extending org.netbeans.spi.wizard.WizardPanelProvider
Creating an instance of org.netbeans.spi.wizard.Wizard
Displaying the wizard using org.netbeans.api.wizard.WizardDisplayer
WizardPanelProvider is the ideal base class for creating a wizard based on a fixed set of panels. These panels consist of a human-readable, localized description and a unique identification string. Descriptions and IDs are passed to the constructor of WizardPanelProvider. You simply need to extend this class and implement the method createPanel() which should create and return an instance of a panel based on the supplied ID. WizardPanelProviderDemo contains just one page, so I don't need to check this parameter; more complex wizards must of course do so. Please note that there is no need to explicitly call createPanel(). This is done automatically for each ID passed to the constructor.
The wizard is created in the second step. You just have to invoke createWizard() of your WizardPanelProvider subclass. This wizard can then be displayed by calling WizardDisplayer.showWizard().
Now that you have a basic understanding of how to create a wizard I would like to discuss some important concepts of the Wizard API.
Underlying Concepts
The main idea of a wizard is to collect user input and have it available when its Finish button is pressed. The Wizard API maintains a java.util.Map to store this data. The map is passed to a method called finish() when the wizard completes successfully. finish() is an instance method of WizardPanelProvider, which you need to extend anyway. The method should return whatever object the wizard creates from its gathered data. This may be the map that was passed to finish(). The default implementation returns null.
You may be wondering who populates the settings map. Obviously, it should be updated whenever the user enters data or modifies previously typed text. To get informed of these changes, you can add appropriate listeners. For example, I have added an ActionListener to my JCheckBox. Inside actionPerformed(), the put() method is invoked on the map that was originally passed to createPanel(). As you will see shortly, the Wizard API can help you automate this task.
But I would like to introduce an important method to control navigation first. If you need to prevent the user from moving to the next page you can call setProblem() on the WizardController instance which was passed to createPanel(). Passing null means that there is no problem, and therefore navigation is enabled and Next and Finish can be pressed. In any other case, a message is displayed at the bottom of the wizard. You should, however, consider carefully whether you really need to block navigation: it makes sense when (and only when) the user has yet to enter data that is vital to finish the wizard.
Using WizardPage
My first example extends WizardPanelProvider to define the wizard. Another approach is to use org.netbeans.spi.wizard.WizardPage. This class is similarly easy to work with. Its main advantage is that it can populate the settings map automatically. To see how that works, please have a look at the following WizardPageDemo.java.
package wizarddemo;
import java.awt.FlowLayout;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.swing.JCheckBox;
import org.netbeans.api.wizard.WizardDisplayer;
import org.netbeans.spi.wizard.Wizard;
import org.netbeans.spi.wizard.WizardException;
import org.netbeans.spi.wizard.WizardPage;
import org.netbeans.spi.wizard.WizardPage.WizardResultProducer;
public class WizardPageDemo implements WizardResultProducer {
public WizardPageDemo() {
WizardPage page1 = new WizardPage("page1-id",
"Page #1");
JCheckBox cb = new JCheckBox(
"You can select me", false);
cb.setName("checkbox-1");
page1.setLayout(new FlowLayout(
FlowLayout.LEADING));
page1.add(cb);
Wizard wizard = WizardPage.createWizard(
new WizardPage [] {page1}, this);
Object result = WizardDisplayer.showWizard
(wizard);
}
public static void main(String [] args) {
new WizardPageDemo();
}
/*
* WizardResultProducer interface
*/
public Object finish(Map settings)
throws WizardException {
Set keys = settings.keySet();
Iterator it = keys.iterator();
while (it.hasNext()) {
Object key = it.next();
System.out.println(key + "=" +
settings.get(key));
}
return settings;
}
}
Creating this wizard consists of these steps:
Instantiate WizardPage and add components
Invoke setName() on any component which should appear in the settings map
Create the wizard using WizardPage.createWizard()
WizardPage.createWizard() takes an array of WizardPage objects. To get informed when the wizard has finished successfully you can implement the WizardResultProducer interface and pass an instance of your class as an additional argument.
WizardPage and WizardPanelProvider work very well for wizards with a fixed number of panels whose order is specified prior to execution. But what if the successor to a panel depends on user input? Think of a wizard to set up a connection to the internet. The required steps greatly depend on the desired connection method. The Wizard API can handle such situations, too.
Branches
The WizardBranchController class is used to dynamically change the order of panels. The class is abstract so you need to subclass it. Its constructor takes one argument, which provides the initial panels of a wizard. When the user reaches the last one, the WizardBranchController will be asked for a wizard to provide subsequent panels.
To decide what the next step is, your implementation of WizardBranchController should check the settings map. The base wizard must have put this information there, of course. To see how this works, I have implemented a small demo called WizardBranchControllerDemo.java. The complete source is in the sample code file wizarddemo.zip (see the Resources section for details), but for now, here is the method that does the check.
Your subclass of WizardBranchController must override one of the methods getWizardForStep() or getPanelProviderForStep(). Which one you choose depends on how you plan to create your subsequent panels. I have implemented getPanelProviderForStep() because this way I was able to reuse the class that implements the first panel. The default implementation of getWizardForStep() calls getPanelProviderForStep() and returns the result of this call.
Conclusion
The Wizard API is a very easy-to-use extension to the Swing core classes. As wizards have become mainstream, I think it would be very desirable to add Wizard-like behavior to a future release of the Java platform.
Although the Wizard API is still in development, it is very stable. According the project home page, things to do include the addition of post-finish summary panels and a possible change of the package name to net.java.
Adding a scrollbar to the left pane on the wizard?
2008-02-08 12:32:42 scottchapman
[Reply | View]
So, I hav a very large number of panels (by design). How can I either:
1) Add scroll bars to the left pane?
2) Remove the entire left pane and just display "Step x of y" down by the navigation buttons?
Thanks in advance!
How to add new elements to the a page of panel provider?
2007-03-14 08:45:55 tuncerfulya
[Reply | View]
Hi all,
I want to just add a combox to the panel( a wizard page) at every click to a button on a panel of the panel provider.Therefore, i add an action listener to the button, in which the a combobox is created and added to panel.
However, the comboboxes are not visible at first, when i navigate back and forward, they become visible.How do i handle this?
Thanks in advance
Best regars
How to add new elements to the a page of panel provider?
2007-03-16 07:44:55 tuncerfulya
[Reply | View]
The problem is OK now,
The refreshing of the panel could be done by
" panel.revalidate();
panel.repaint();"
Thanks ..
Wizard has been updated
2006-09-24 20:57:59 timboudreau
[Reply | View]
A number of the issues brought up here, such as doing expensive operations in the background, are now supported.
You need to set a system property. The following is a quote taken from the Wizard FAQ at https://wizard.dev.java.net/faq.html
Set the system property "wizard.sidebar.image" to a URL that points to the image file inside a jar that is on the classpath. The image must be in a format readable by ImageIO.read().
What is the mechanism to detect a panel change (previous or next)? Sometimes one panel is dependent on data in another and needs to process the data before showing.
Please keep in mind that the basic idea behind the Wizard API is that no changes to the users' environment should be made until he has hit the Finish button. Having said that, please have a look at my WizardBranchControllerDemo. It shows how a panel can take actions based on the previous panels. You should collect data in the map, so each panel can easily check what has been done so far and act accordingly. ...hope that helps...
The idea of a wizard is not to make any changes to the environment until the user clicks the Finish button. If you use branch controllers, you can perform actions when a new panel is to be shown. You could start a new thread at this time. But again, please keep in mind that whatever these actions are, they should not change anything or the user should be able to roll back these changes.
Thanks. I agree with the idea that no changes should be made to the environment until the Finish button is pressed. I believe the solution you've given me will work.
BTW: how do I get the step numbers on the left of the wizard dialog to start with 1 and not 0?
I have been working with a version of the Wizard API that I compiled myself. This version does not show the problem. It seems that the version available as a jar file has this behaviour, though. So I'd suggest you get the sources from the cvs repository and compile it on your own. This version should work as expected. Hope that helps...
Thanks. I agree with the idea that no changes should be made to the environment until the Finish button is pressed. I believe the solution you've given me will work.
BTW: how do I get the step numbers on the left of the wizard dialog to start with 1 and not 0?
I wanted to do multiple branching. Please show me an example how to implement. I'll give you what scenario i want it implemented for. I read the docs it says you also have to implement getWizardForStep() with getWizardPanelProvider() i actually got confused where to implement what.
I mean we have some steps which branch up and then the steps again need to Branch up
eg. A wizard goes from 1-8 steps in a row and hten branches to A9 - B9 then these steps A9 and B9 go further and then A branch splits to A11 -AA11 and B branch splits to B11-BB11.
Also one more question .....
When implementing a wizard using the Wizard Page we can update the UI of next step related to what was selected before.
But when using WizardPanel Provider how do we do it.....
The NetBeans Wizards have been working great for quite a while, so undoubtedly they offer an excellent and solid base for a stand alone wizard component.
webstart Demo would be welcomed ;-)
2006-02-28 14:59:22 bjb
[Reply | View]
All is in the title ...
webstart Demo would be welcomed ;-)
2006-03-01 10:09:20 tommi_kuenneth
[Reply | View]