JavaJournal January 30, 1999
when it comes time to create a Wizard, you spend most of your time developing the containers

WordFind applet

IO, IO... January 15

Wizard Generator
Over many years as a GUI developer and occasional hacker, I've been faced several times with presenting a series of step-by-step screens. Call it a Wizard or --in IBM parlance-- a SmartGuide or whatever, the goal is to allow a user to quickly get a task done.

But while the user gets the task done, the developer is faced with a series of screens that is usually downright painful. And developing a wizard using the Java Foundations Classes doesn't represent any great leap forward in the ease department.

My observation is that the design effort in getting the Wizard to flow smootly often detracts from the design effort needed to capture the user's needs and work toward the particular requirements of a Wizard.

As a developer, I would greatly appreciate having the code to host and navigate through the wizard handled by a mechanism that knows what to do. The only task remaining is the content development --hopefully, the fun stuff.

Enter JFC...
This is one area that JFC exels at. You can construct a framework that, while drudgery to put together the first time, makes re-use relatively straightforward.

My object with the Wizard Generator is to allow the developer to concoct a number of panels -- all of the same size -- and allow a navigator-type class to handle the movement from one screen to another. The objective of the navigator is simple. Get to the last screen and let the user click on Finish. While presenting the panel array, the navigator has to handle the delicate task of not letting the user attempt to move ahead of the first panel or past the last.

The design works this way:
-- a JFrame-extending host class, has a handle to an array of JPanels each representing one step of the Wizard's process.
-- the host class, passes the panel array into the navigator class, which represent itself as a small panel with 4 buttons: previous, next, finish, cancel.
-- the host class knows to place the first panel of the array in the center of a BorderLayout and the Navigator in the South position of the layout.
-- the Navigator handles all the showing and hiding.
-- the Navigator expects the host to honor two interface methods: Finish and Cancel.
-- the Navigator and its host know nothing about the contents of the JPanel array and require little from it, except that all panels should be the same size.
  Code: navigator panel
  package wizard;

/**
* Controller for a navigator panel
*/

import com.sun.java.swing.*;
import java.awt.event.*;
import java.awt.BorderLayout;
import java.util.Vector;
public class Navigator extends Object implements ActionListener {

private Vector panels = new Vector();
private JPanel navPanel = null;
private JPanel oldPanel = null;
private int lastPanel = 0;
private int currentPos = 0;//assuming we start at 0
private JButton prevButton = null;
private JButton nextButton = null;
private JButton cancelledButton = null;
private JButton finishedButton = null;
private Host parentFrame = null;

/**
* Navigator constructor comment.
*/
public Navigator(Vector p, Host j) {
super();
this.panels = p;
this.parentFrame = j;
this.lastPanel = panels.size();
show(0);
}
/**
* handles the events from the buttons
*/
public void actionPerformed(ActionEvent e) {
int firstPanel = 0;


if (e.getActionCommand().equals("Cancel")){
System.exit(0);//or normally dispose of the parent
} else if (e.getActionCommand().equals("Finished")){
this.getParent().doIt();
} else if (e.getActionCommand().equals("Previous")){
currentPos = currentPos - 1;
this.show(currentPos);
nextButton.setEnabled(true);
finishedButton.setEnabled(true);
//disable if at position 0
if (currentPos == 0) {
prevButton.setEnabled(false);
} else {
prevButton.setEnabled(true);
}
} else if (e.getActionCommand().equals("Next")){
currentPos = currentPos + 1;
this.show(currentPos);
//enable Finished (if necessary) and disable Next
prevButton.setEnabled(true);
if (currentPos == lastPanel - 1) {
nextButton.setEnabled(false);
finishedButton.setEnabled(true);
} else {
finishedButton.setEnabled(false);
}
}
}
/**
* the count of panels
* @return int
*/
protected int getPanelCount() {

return this.lastPanel;
}
/**
* this is the parent container;
* throws an exception if you have placed the
* Navigator on anything other than a Host content pane..
*
* @return wizard.Host
*/
private Host getParent() {

return this.parentFrame;
}
/**
* The view for navigator
* @return JPanel
*/
protected JPanel getView() {


if (navPanel == null){
navPanel = new JPanel();
//navPanel.setSize(300,50);

prevButton = new JButton();
prevButton.setText("< Previous");
prevButton.setActionCommand("Previous");
prevButton.addActionListener(this);
navPanel.add(prevButton);
prevButton.setEnabled(false); //initially disabled

nextButton = new JButton();
nextButton.setText("Next >");
nextButton.setActionCommand("Next");
nextButton.addActionListener(this);
navPanel.add(nextButton);

finishedButton = new JButton();
finishedButton.setText("Finished");
finishedButton.setActionCommand("Finished");
finishedButton.addActionListener(this);
navPanel.add(finishedButton);
finishedButton.setEnabled(false);

cancelledButton = new JButton();
cancelledButton.setText("Cancel");
cancelledButton.setActionCommand("Cancel");
cancelledButton.addActionListener(this);
navPanel.add(cancelledButton);


}

return navPanel;
}
/**
* set the size of the view
*
* @param w int
* @param h int
*/
protected void setSize(int w, int h) {

this.getView().setSize(w,h);
}
/**
* ye olde methode to display a particular panel in the Vector
* @param pos int
*/
private void show(int pos) {
JPanel newPanel;

//remove the old one
if (oldPanel != null) {
this.getParent().getContentPane().remove(oldPanel);
}

newPanel = (JPanel) this.panels.elementAt(pos);
newPanel.setVisible(true);


//add new one
this.getParent().getContentPane().add(newPanel,BorderLayout.CENTER);
this.getParent().validate();
this.getParent().repaint();

oldPanel = newPanel; //swap for next call

}
}
  Code: host JFrame
  package wizard;

import com.sun.java.swing.*;
import java.awt.*;
import java.util.Vector;


public class Host extends com.sun.java.swing.JFrame {
/**
* Host constructor comment.
*/
public Host() {
super();
}
/**
* Host constructor comment.
* @param title java.lang.String
*/
public Host(String title) {
super(title);
}
/**
* This method called when the Wizard process is complete
* usually when someone clicks on Finish.
*/
protected void doIt() {

//figure out what is in the panels
JOptionPane.showMessageDialog(this, "Finished clicked");
}
/**
* Starts the application.
* @param args an array of command-line arguments
*/
public static void main(java.lang.String[] args) {
Host j = new Host("Class Wizard");
j.getContentPane().setLayout(new BorderLayout());
j.setSize(400,500);

//create a sample vector of panels
Vector v = new Vector();
JPanel jp;
JPanel jp2;
JPanel jp3;


jp = new JPanel();
jp.setBorder(new com.sun.java.swing.border.TitledBorder("Step 1 - Class "));
jp.setBackground(Color.red);

v.addElement(jp);

jp2 = new JPanel();
jp2.setBorder(new com.sun.java.swing.border.TitledBorder("Step 2 - Fields"));
jp2.setBackground(Color.green);
v.addElement(jp2);


jp3 = new JPanel();
jp3.setBorder(new com.sun.java.swing.border.TitledBorder("Step 3 - Methods"));
jp.setBackground(Color.blue);
v.addElement(jp3);




Navigator n = new Navigator(v, j);
n.setSize(400,100);
j.getContentPane().add(n.getView(),BorderLayout.SOUTH);
j.show();
}
}
  Where to next?
  The developer has to ensure that the panel array is represented by one controller that knows how to handle the transition between screens. My aim would be to have a panel controller class that listens for events coming from the panel's widgets and maintain state.

Each panel should have a method that is called when it is first painted, which maintains a link between the widgets and the controller class.

When that is done -- the fun begins.

  Copyright (c) Gervase Gallant 1999.