| |
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. |