How to Create a Dynamic Wizard
https://dzone.com/articles/nb-how-to-create-dynamic-wizard
You've created some kind of Java desktop application on top of the NetBeans Platform for recording & analyzing plants (which is a realistic scenario in Mexico). So, somewhere along the line you create a wizard where a new plant can be defined:

If a plant is not marked as being "healthy", the second panel needs to appear, where details about the disease can be filled in:

Then, when Next is clicked, a final panel provides a summary relevant to
the current values, while enabling Finish to be clicked:

However, what if, in the first panel, "Healthy" is selected? I.e., the
checkbox below has been selected by the user, in this scenario:

In this case, the "Disease Details" panel is superfluous. Therefore,
when Next is clicked, the user should go directly to the final panel, to
be presented with a relevant summary:

This is a pretty common scenario, I believe. The basis of the wizard above is described in the NetBeans Wizard Module Tutorial. However, the dynamic aspect is not discussed in that tutorial. For that, currently, the best resource is here, in the documentation for the INT Viewer
application, which is yet another application on the NetBeans Platform.
I worked through that document and below is how you can do the same.
To download the completed source code of the sample to be outlined below, go here:
http://plugins.netbeans.org/plugin/38672/?show=true
To create the "Plant Creator Wizard" shown above, follow these steps:
- **Generate the initial wizard classes. **Create a module and
then go to the New File dialog. In the Module Development category,
you'll find the Wizard wizard. The first panel of this wizard is as
follows:

Click Next. Then, for this sample scenario, make the selections below,
i.e., register the wizard as a "Custom" wizard type (which means it
won't be invokable from the New File dialog, but from somewhere else,
i.e., via an Action that is yet to be defined), specify "Dynamic" (i.e.,
this is the specific selection that, in contrast to the scenario in the
tutorial referred to be above, will cause an Iterator class to be
generated, where we'll specify our own custom panel sequences), together
with the number of panels that you'd like the wizard to support (which
in this case is 3):

Finally, specify a class name prefix, in this case it is "PlantCreator"
and select a package where the generated classes will be found:

- **Design the user interface. **You now have a bunch of files. For each panel, you have a visual panel (i.e., a GUI) and a controller panel, exactly as in the NetBeans Wizard Module Tutorial.
Start by refactoring them to have more useful names:
GeneralWizardPanel.java with GeneralWizardVisualPanel.java,
DiseaseWizardPanel.java with DiseaseWizardVisualPanel.java, and
SummaryWizardPanel with SummaryWizardVisualPanel.java.
Now look at the first 5 screenshots, at the top of this article, and design your GUI panels to look like that. I.e., like this:
- GeneralWizardVisualPanel: Return "General" from "getName". Add a
JLabel with text "Plant Name", with a JTextField. You'll not be using
these in this simple scenario, they're just there to give some meaning
to the wizard. Then add a checkbox, with "Healthy" as its label. Then
add an accessor for the JCheckbox, like this:
boolean isHealthy() {
return jCheckBox1.isSelected();
}
-
DiseaseWizardVisualPanel: Return "Disease Details" from "getName".
Add a JTextArea that covers the whole space of the panel. Again, this
JTextArea is just here for decoration, it's not going to be used in this
sample scenario. -
SummaryWizardVisualPanel: Return "Summary" from "getName". Drag and
drop a JLabel onto the panel. Place it anywhere and let it keep its
default content, which will change dynamically based on the "Health"
value of the plant. In the source code, create an accessor for the
JLabel:
public JLabel getSummaryLabel() {
return summaryLabel;
}
OK. Your simple UI is complete. Let's now move to the controllers, which all implement the WizardDescriptor.Panel class.
- Define the panel controllers. In the GeneralWizardPanel and the
SummaryWizardPanel, rewrite the "getComponent" method to return the
specific type of the GUI panel you want to control, to make the GUI
panels more easily accessible. For example, in GeneralWizardPanel:
private GeneralWizardVisualPanel component;
@Override
public GeneralWizardVisualPanel getComponent() {
if (component == null) {
component = new GeneralWizardVisualPanel();
}
return component;
}
Do the same for the SummaryWizardPanel, i.e., return the specific type of the GUI panel that the controller should work with.
Next, in the GeneralWizardPanel, implement "storeSettings" as follows:
@Override
public void storeSettings(Object settings) {
NbPreferences.forModule(PlantCreatorAction.class).putBoolean("health", getComponent().isHealthy());
}
Now, whenever the panel is exited, e.g., the Next button is clicked,
the state of the checkbox will be saved to a "health" property in a file
that will be created automatically on disk.
In the SummaryWizardPanel, set the JLabel based on the current value of the "health" property:
JLabel summaryField = getComponent().getSummaryLabel();
@Override
public void readSettings(Object settings) {
if (!NbPreferences.forModule(PlantCreatorAction.class).getBoolean("health", false)) {
summaryField.setText("The plant is diseased. Get Keanu Reeves to save the world.");
} else {
summaryField.setText("The plant is not diseased.");
}
}
The user interface and the controllers are now complete. The only
remaining thing is the purpose of these instructions, i.e., you are
going to be shown how to include/exclude the second panel, depending on
whether the plant is marked as being healthy or not.
Define the iterator. The iterator is the class where you can
define a variety of different sequences in which the panels can be
organized. Below, you can see that we have a "healthySequence", which
consists of the first panel and the last panel as well as a
"diseaseSequence", which consists of all three panels. Together with the
sequence, we need to maintain the index of the currently selected
panel.
There are two properties from the NetBeans Wizard API that we need to
maintain via the iterator. Firrstly, you need to maintain
WizardDescriptor.PROP_CONTENT_DATA, which is the display texts
("General", "Disease Details", and "Summary") shown on the left side of
the wizard. When going to the next or previous panel,
WizardDescriptor.PROP_CONTENT_SELECTED_INDEX needs to be set with the
current panel index.
- Define the iterator as follows. It is very similar to the original,
except that the sequences outlined above have been added. In addition to
looking at the sequence definitions below (look at the end of the
"initializePanels" method), look very closely at the overridden method
"nextPanel". Depending on the current value of the "health" property, a
different sequence and index is selected.
import java.awt.Component;
import java.util.NoSuchElementException;
import javax.swing.JComponent;
import javax.swing.event.ChangeListener;
import org.openide.WizardDescriptor;
import org.openide.util.NbPreferences;
public final class PlantCreationIterator implements WizardDescriptor.Iterator {
private int index;
private WizardDescriptor wizardDesc;
private WizardDescriptor.Panel[] allPanels;
private WizardDescriptor.Panel[] currentPanels;
private WizardDescriptor.Panel[] healthySequence;
private WizardDescriptor.Panel[] diseaseSequence;
private String[] healthyIndex;
private String[] diseaseIndex;
public void initialize(WizardDescriptor wizardDescriptor) {
wizardDesc = wizardDescriptor;
}
/**
* Initialize panels representing individual wizard's steps and sets
* various properties for them influencing wizard appearance.
*/
private void initializePanels() {
if (allPanels == null) {
allPanels = new WizardDescriptor.Panel[]{
new GeneralWizardPanel(),
new DiseaseWizardPanel(),
new SummaryWizardPanel()
};
String[] steps = new String[allPanels.length];
for (int i = 0; i < allPanels.length; i++) {
Component c = allPanels[i].getComponent();
// Default step name to component name of panel.
steps[i] = c.getName();
if (c instanceof JComponent) { // assume Swing components
JComponent jc = (JComponent) c;
// Sets step number of a component
// TODO if using org.openide.dialogs >= 7.8, can use WizardDescriptor.PROP_*:
jc.putClientProperty("WizardPanel_contentSelectedIndex", new Integer(i));
// Sets steps names for a panel
jc.putClientProperty("WizardPanel_contentData", steps);
// Turn on subtitle creation on each step
jc.putClientProperty("WizardPanel_autoWizardStyle", Boolean.TRUE);
// Show steps on the left side with the image on the background
jc.putClientProperty("WizardPanel_contentDisplayed", Boolean.TRUE);
// Turn on numbering of all steps
jc.putClientProperty("WizardPanel_contentNumbered", Boolean.TRUE);
}
}
healthyIndex = new String[]{steps[0], steps[2]};
healthySequence = new WizardDescriptor.Panel[]{allPanels[0], allPanels[2]};
diseaseIndex = new String[]{steps[0], steps[1], steps[2]};
diseaseSequence = new WizardDescriptor.Panel[]{allPanels[0], allPanels[1], allPanels[2]};
currentPanels = healthySequence;
}
}
private void setHealthy(boolean healthy) {
String[] contentData;
if (healthy) {
currentPanels = healthySequence;
contentData = healthyIndex;
} else {
currentPanels = diseaseSequence;
contentData = diseaseIndex;
}
wizardDesc.putProperty(WizardDescriptor.PROP_CONTENT_DATA, contentData);
}
@Override
public WizardDescriptor.Panel current() {
initializePanels();
return currentPanels[index];
}
@Override
public String name() {
if (index == 0) {
return index + 1 + " of ...";
}
return index + 1 + " of " + currentPanels.length;
}
@Override
public boolean hasNext() {
initializePanels();
return index < currentPanels.length - 1;
}
@Override
public boolean hasPrevious() {
return index > 0;
}
@Override
public void nextPanel() {
if (!hasNext()) {
throw new NoSuchElementException();
}
if (index == 0) {
if (!NbPreferences.forModule(PlantCreatorAction.class).getBoolean("health", false)) {
setHealthy(false);
} else {
setHealthy(true);
}
}
index++;
wizardDesc.putProperty(WizardDescriptor.PROP_CONTENT_SELECTED_INDEX, index);
}
@Override
public void previousPanel() {
if (!hasPrevious()) {
throw new NoSuchElementException();
}
index--;
wizardDesc.putProperty(WizardDescriptor.PROP_CONTENT_SELECTED_INDEX, index);
}
@Override
public void addChangeListener(ChangeListener l) {
}
@Override
public void removeChangeListener(ChangeListener l) {
}
}
- **Create an action to start the wizard. **Use the New Action wizard
and set the Actio to "Always Enabled" (though, if you'd like the wizard
to be available for a certain domain object, that would be posssible
too, use "Conditionally Enabled", in that case):

Choose somewhere where the user will click to invoke the wizard:

Specify a name for the class, together with a display name:

Finally, define the content of the generated ActionListener as follows.
When the Action is invoked, the wizard is started and initialized with
the WizardDescriptor:
import java.awt.Dialog;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.text.MessageFormat;
import org.openide.DialogDisplayer;
import org.openide.WizardDescriptor;
import org.openide.awt.ActionRegistration;
import org.openide.awt.ActionReference;
import org.openide.awt.ActionReferences;
import org.openide.awt.ActionID;
import org.openide.util.NbBundle.Messages;
@ActionID(category = "File",
id = "org.plant.creator.PlantCreatorAction")
@ActionRegistration(displayName = "#CTL_PlantCreatorAction")
@ActionReferences({
@ActionReference(path = "Menu/File", position = 0)
})
@Messages("CTL_PlantCreatorAction=Create Plant")
public final class PlantCreatorAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent e) {
// To invoke this wizard, copy-paste and run the following code, e.g. from
// SomeAction.performAction():
PlantCreationIterator iterator = new PlantCreationIterator();
WizardDescriptor wizardDescriptor = new WizardDescriptor(iterator);
iterator.initialize(wizardDescriptor);
// {0} will be replaced by WizardDescriptor.Panel.getComponent().getName()
// {1} will be replaced by WizardDescriptor.Iterator.name()
wizardDescriptor.setTitleFormat(new MessageFormat("{0} ({1})"));
wizardDescriptor.setTitle("Plant Creator Wizard");
Dialog dialog = DialogDisplayer.getDefault().createDialog(wizardDescriptor);
dialog.setVisible(true);
dialog.toFront();
boolean cancelled = wizardDescriptor.getValue() != WizardDescriptor.FINISH_OPTION;
if (!cancelled) {
// do something
}
}
}
That's all. Install the module, invoke the wizard, and there's your
dynamic wizard in action. Then adapt the code to your own purposes.