Introduction
Example Wizard Description
Wizard Creation Steps
Example Wizard Storyboard
Example Wizard Client Tree
Example Wizard Server Side
Creating Custom Panels
Example Wizard Builder
1. Introduction
This tutorial is provided to get you started writing wizards using the Web Start wizard API. This tutorial walks through the steps required to create a simple wizard, and demonstrates the implementation of wizard panels, a ServerObject, and a wizard builder..2. Example Wizard Description
This tutorial will help you create a wizard that displays the amount of free space available on disk filesystems that the user selects. This example wizard is designed to demonstrate how to write wizard panels and demonstrate the communication between the wizard client and server. Also included in this tutorial is an example ServerObject implementation and the builder that creates the wizard archive.
3. Wizard Creation Steps
There are two steps required to create a working wizard:Creating the wizard builder is the focus of this tutorial, and can be broken into several steps:
- Write the wizard builder.
- Run the wizard builder to create the wizard archive.
- Create a wizard storyboard
- Design the wizard client tree structure.
- Design the server-side objects (Task, ServerObject).
- Implement required panels
- Initialize the WizardState object.
- Add required resources to the wizard.
4. Example Wizard Storyboard
Before any code is written, it is useful to have a really good idea what the wizard is supposed to do, identify the contents and behavior of the panels included in the wizard, design the client tree, and create an interface for communication between the client objects and the server objects.The storyboard created in this section of the tutorial will reveal the purpose of the wizard, the information the wizard must collect from the user, the information the wizard will display to the user, and how that information is divided into wizard panels.
Our wizard's purpose is to display filesystem free space to the user. To this end, our wizard will have custom panels to request information from the user and then display the filesystem information.
A filesystem is a section of a disk. A filesystem is often viewed by the user as a real disk. On Microsoft Windows, for example, the filesystems are labeled with drive letters (A:\, B:\, C:\, etc). The user will often refer to the "C:\" filesystem as the "C drive".
The information the wizard will request from the user is the list of filesystems that should be queried for free space. It may seem that we should just get it over with and request the free space on all filesystems. Be aware that on many operating systems (including Microsoft Windows) a filesystem name will be listed for removable drives even if no disk is inserted. This has the quite annoying effect of asking the user via an error dialog box to insert a disk in the specified drive. That is why this example wizard will query the user for the list of filesystems to report.
The information this wizard will display is a list of filesystem names (which the user selected) and the corresponding amount of available space on that filesystem.
This example wizard will consist of three panels: Welcome, FilesystemSelection, and FilesystemSummary.
The Welcome panel will be used to indicate to the user the purpose of this wizard. This is done to align the expectations of the user. This panel might have a label that reports:
"This wizard reports the amount of space available on your disk filesystems."
If the user's intent was to create a new disk filesystem, this message would indicate that this wizard does not do that.
The Welcome panel will be an instance of TextImagePanel, configured to display the desired text.
The FilesystemSelection panel will display a list of filesystem names and Checkbox components that allows the user select which filesystems should be queried. The user would check the box next to the filesystems that are to be queried for available space.
The FilesystemSummary panel will indicate to the user the amount of free space left on each filesystem selected in the FilesystemSelection panel. The display of this information will be done using standard Label objects.
5. Example Wizard Client Tree
The wizard client tree is a structure that defines the order of the wizard panels and the rules for displaying those panels.The example wizard consists of only three panels, and has a very simple client panel tree.
All three of the panels in the example wizard will be displayed. The client panel tree that describes this behavior is a root node (WizardComposite) that is the parent of all three panels. This is illustrated in the following diagram:
6. Example Wizard Server Side
The server side of the example wizard will be responsible for getting the list of filesystem names and the amount of available space on each filesystem the user specifies. These activities could not be done within the wizard client because the client may be running in a web browser. Certainly querying for filesystem information would not be allowed by the browser's security manager.There are two possibilities for creating this server-side support: a Task or a ServerObject.
A Task is an element within a Sequence which can be performed or reversed. A Sequence can be executed in blocking mode or non-blocking mode. The non-blocking mode is useful if the Sequence is expected to take a long time. If the Sequence is executed in blocking mode, the caller (usually a panel) will have its thread blocked until the Sequence is complete. This effect can be quite annoying because the result is that the user interface is frozen until the Sequence is complete. The Tasks within the Sequence do not report directly back to the caller. Instead, a callback method is registered, so at the end of the Sequence execution an object can be notified of its completion.
In this example wizard, the behavior we would like to achieve is that the client makes a call to the server and the return value is the information we want. This is the way ServerObjects work. For this to work, the method that is called should return without delay. Since the two operations we need (getting filesystem names and getting filesystem free space) do not take a significant amount of time, the ServerObject strategy seems to fit nicely.
ServerObject is an interface with two required methods: addRuntimeResources() and setWizardState().
The addRuntimeResources method is part of the resource collection mechanism. This method should add all required Java classes that are not part of the Java core classes to the specified Vector object. This method is only executed at build time, and is used by the builder to figure out which classes need to be included in the wizard archive.
The setWizardState method is used to initialize the ServerObject with a reference to the correct WizardState. This method is executed only at wizard initialization time (when the wizard is executed). The WizardState reference is required to communicate with other server or client objects.
The example ServerObject must have a method to retrieve the filesystem names from the system. This method will be called getFilesystemNames, and will return a String array. Each element within the returned array will be a String object representing one filesystem. The length of the array will reveal the number of valid filesystems.
Another method required by this example wizard will return the amount of free space for specified filesystems. This method will be called getFilesystemFreeSpace. getFilesystemFreeSpace will accept a String array representing the filesystem names to query for free space and will return a Long array, each element of which will reveal the amount of free space available on the respective filesystem.
getFilesystemNames() and getParitionFreeSpace() will use the SystemInterface reference maintained by the ArchiveReader. The SystemInterface is used to supplement the Java support by providing platform-specific functionality.
FilesystemServerObject
Following is the source to the FilesystemServerObject. This code should be written into a file called FilesystemServerObject.java within the "classes" directory of this SDK.
import com.sun.wizards.core.*;
import java.io.*;
import java.util.*;
public class FilesystemServerObject implements ServerObject, Serializable
{
private transient WizardState wizardState = null;
/**
* The noarg constructor is required because this object
* will be deserialized at runtime.
*/
public FilesystemServerObject()
{
}
/**
* This method sets the WizardState into the object at runtime.
*
* @param wizardState The WizardState parent.
*/
public void setWizardState(WizardState wizardState)
{
this.wizardState = wizardState;
}
/**
* This method uses the SystemInterface to get the list of filesystem
* names.
*
* @return The list of filesystem names.
*/
public String[] getFilesystemNames()
{
String[] filesystemNames = com.sun.wizards.core.ArchiveReader.systemInterface.getFilesystemNames();
return(filesystemNames);
}
/**
* This method returns the free space (in bytes) for each filesystem within the
* filesystems array.
*
* @param filesystems An array of filesystem names to query.
*
* @return A respective list of filesystem free space.
*/
public Long[] getFilesystemFreeSpace(String[] filesystems)
{
if(filesystems != null)
{
Long[] freeSpace = new Long[filesystems.length];
for(int index = 0; index < filesystems.length; index++)
{
long filesystemBytes =
com.sun.wizards.core.ArchiveReader.systemInterface.getFilesystemFreeSpace(filesystems[index]);
freeSpace[index] = new Long(filesystemBytes);
}
return(freeSpace);
}
return(null);
}
/**
* Get the runtime classes required by this ServerObject.
*/
public void addRuntimeResources(Vector resourceVector)
{
resourceVector.addElement(new String[] {null, "FilesystemServerObject"});
}
}
7. Creating Custom Panels
Often, wizard implementation will include writing a custom wizard panel. The panel will appear in the wizard frame above the Navigation Panel and to the right of the Image Panel.A wizard panel is an object subclassed from WizardLeaf. The methods that a WizardLeaf subclass often override are createUI, beginDisplay, isDisplayComplete, abortDisplay, and addRuntimeResources.
createUI is the method in which the user interface of the wizard panel should generally be created. The reason the gui should be created here is that this method is only called once (just before the panel is displayed) in the lifecycle of the wizard panel.
The beginDisplay method is called each time the panel is displayed. This method is often used to synchronize the information being displayed in the panel with the information contained in the wizard server.
The isDisplayComplete method is called when the user presses the "Next" button. This method returns a boolean. False means that the user did not complete this panel correctly. Either the information entered was incorrect or the panel has not been completed. True means that the panel has been completed, and the next panel may be displayed.
The abortDisplay method is called when the user presses the "Back" button. This gives the panel a chance to clean up before the previous panel is displayed.
The addRuntimeResources method is used to collect the names of the classes required when the panel in question is added to the wizard. Each class added to the specified Vector will be included in the wizard archive.
Panels communicate with other panels through the wizard server. A wizard panel can set data into the server that another panel can read. In this way, data can be passed between panels quite easily.
In this example, two custom panels must be written. The FilesystemSelection panel will collect from the user a list of filesystems to query for free space. This list will be set into the wizard server under the key "selectedFilesystems". The FilesystemSummary panel will read the data from the server and display information about the filesystems in the list.
The FilesystemSelection Panel
Following is the source to the FilesystemSelection panel. This code should be written into a file called FilesystemSelection.java within the "classes" directory of this SDK.
import com.sun.wizards.core.*;
import com.sun.wizards.awt.*;
import java.awt.*;
import java.io.*;
import java.util.*;
public class FilesystemSelection extends WizardLeaf
{
/**
* This string array contains the list of filesystem names
* returned from the wizard server when the user interface
* of this panel is created.
*/
private transient String[] filesystemNames = null;
/**
* This panel will contain the checkboxes.
*/
private transient Panel filesystemPanel = null;
/**
* This array saves the Checkbox objects so their state can
* be queried when the "Next" button is pressed.
*/
private transient Checkbox[] filesystemCheckboxes = null;
/**
* This is the noarg constructor that is required for
* objects that must be deserialized.
*/
public FilesystemSelection()
{
}
/**
* This is the constructor used at wizard runtime. This constructor
* is used to set the Route object for this panel and the WizardTreeManager
* that is used to communicate with the wizard server objects.
*
* @param name The name of this panel, which is set at wizard buildtime.
* @param Route The route object that directs messages to the correct
* WizardState object.
* @param treeManager The WizardTreeManager object used to communicate with
* the wizard server objects.
*/
public FilesystemSelection(String name, Route route, WizardTreeManager treeManager)
{
super(name, route, treeManager);
}
/**
* This is the constructor used at wizard buildtime (within the wizard
* builder). The WizardState object is provided so this panel can set
* required initialzation data into the wizard.
*
* @param wizardState The buildtime WizardState.
* @param name The name of this panel.
*/
public FilesystemSelection(WizardState wizardState, String name)
{
super(wizardState, name);
/*
* Set an empty vector into the WizardState object that will
* contain selected filesystems. At runtime you must use
* callServerObjectMethod() to access the wizardState.
*/
wizardState.setData("selectedFilesystems", new Vector());
}
/**
* createUI is called before the panel is displayed. This is the opportunity
* for the wizard panel to create its user interface. createUI is only
* called once in the life of the panel.
*/
public void createUI()
{
super.createUI();
/*
* Here, I will query for the list of filesystem names. Begin by creating a
* route to the FilesystemServerObject.
*/
Route filesystemServerObjectRoute = getRoute().getChildServerRoute("FilesystemObject");
/*
* Now call in to the server to get the filesystem information. The route created
* above identifies the correct server-side object. "getFilesystemNames" is the
* method within that object, and the other two paramaters indicate the argument
* types and objects.
*/
String[] filesystemNames = (String[])wizardManager.callServerObjectMethod(filesystemServerObjectRoute,
"getFilesystemNames",
null,
null);
/*
* Cache these names for later use. Remember, this code will only be executed
* once.
*/
this.filesystemNames = filesystemNames;
/*
* Now create the user interface based on the list.
*/
this.filesystemPanel = new Panel(new ColumnLayout());
this.filesystemCheckboxes = new Checkbox[filesystemNames.length];
add(this.filesystemPanel, "Center");
for(int index = 0; index < filesystemNames.length; index++)
{
this.filesystemCheckboxes[index] = new Checkbox(filesystemNames[index]);
this.filesystemPanel.add(this.filesystemCheckboxes[index]);
}
}
/**
* beginDisplay is called every time the panel is made visible.
* This presents an opportunity to synchronize this panel's display
* with the data within the WizardState object.
* <p>
* Every time this panel is displayed, the currently selected filesystems
* will be checked.
*/
public void beginDisplay()
{
super.beginDisplay();
/*
* Get the list of currently selected filesystems from the WizardState.
*/
Vector selectedFilesystems =
(Vector)wizardManager.callServerObjectMethod(getRoute(),
"getData",
new String[] {"java.lang.String.class"},
new Object[] {"selectedFilesystems"});
/*
* Set the appropriate checkboxes.
*/
for(int index = 0; index < this.filesystemNames.length; index++)
{
if(selectedFilesystems.contains(this.filesystemNames[index]))
{
/*
* Select the filesystem.
*/
this.filesystemCheckboxes[index].setState(true);
}
else
{
/*
* Deselect the filesystem.
*/
this.filesystemCheckboxes[index].setState(false);
}
}
}
/**
* This method is called when the user presses the "Next" button.
* This presents an opportunity to update the server with the information
* collected in this panel.
* <p>
* Every time the "Next" button is pressed from this panel, the
* list of selected filesystems will be sent to the WizardState object.
*/
public boolean isDisplayComplete()
{
Vector selectedFilesystems = new Vector();
for(int index = 0; index < this.filesystemCheckboxes.length; index++)
{
if(this.filesystemCheckboxes[index].getState())
{
/*
* This filesystem has been selected. Add it to the vector.
*/
selectedFilesystems.addElement(this.filesystemNames[index]);
}
}
/*
* Set the selected filesystems into the WizardState object.
*/
wizardManager.callServerObjectMethod(getRoute(),
"setData",
new String[] {"java.lang.String.class",
"java.lang.Object.class"},
new Object[] {"selectedFilesystems",
selectedFilesystems});
/*
* This method must return true or false. If false is returned,
* the next panel will not be displayed.
*/
return(true);
}
/**
* This method must add the non Java core classes required by this
* panel to the specified vector. If classes are missing, the
* wizard will not execute outside of the wizard build environment.
*/
public void addRuntimeResources(Vector resourceVector)
{
super.addRuntimeResources(resourceVector);
/*
* Add the non Java core classes required by this panel.
*/
resourceVector.addElement(new String[] {null, "FilesystemSelection"});
resourceVector.addElement(new String[] {null, "com.sun.wizards.awt.ColumnLayout"});
}
}
The createUI method of the FilesystemSelection panel creates the checkbox objects the user will manipulate to select the desired filesystems. This method uses a call to a server object (callServerObjectMethod) to get the filesystem information.The beginDisplay method of the FilesystemSelection panel ensures that the selected filesystems on the panel agree with the selected filesystems within the WizardState. The data in the WizardState is always correct, because that is the information that the other objects will look at.
The isDisplayComplete method is used to send the selected filesystem information to the WizardState.
The addRuntimeResources method is used to include non Java core classes required by the FilesystemSelection panel into the resulting wizard archive. If any required class is missing, the wizard archive will not execute outside of the build environment.
The FilesystemSummary Panel
The FilesystemSummary panel is also a custom panel. This panel will take the list of selected filesystems and request free space information for each one. This information will be displayed on the panel.The FilesystemSummary panel will be subclassed from WizardLeaf. This panel will generate its user interface each time it is traversed. This is desired, because the data will change completely each time the user makes a new filesystem selection. To accomplish this behavior, the user interface will be created within the beginDisplay method instead of the createUI method (as in the FilesystemSelection panel).
The FilesystemSummary panel will make use of the FilesystemServerObject to get the free space information from the server. This is the same mechanism used in the FilesystemSelection panel.
Following is the source to the FilesystemSummary panel. This code should be written into a file called FilesystemSummary.java within the "classes" directory of this SDK.
import com.sun.wizards.core.*;
import com.sun.wizards.awt.*;
import java.awt.*;
import java.io.*;
import java.util.*;
public class FilesystemSummary extends WizardLeaf
{
private transient Panel summary = null;
/**
* This is the noarg constructor that is required for
* objects that must be deserialized.
*/
public FilesystemSummary()
{
}
/**
* This is the constructor used at wizard runtime. This constructor
* is used to set the Route object for this panel and the WizardTreeManager
* that is used to communicate with the wizard server objects.
*
* @param name The name of this panel, which is set at wizard buildtime.
* @param Route The route object that directs messages to the correct
* WizardState object.
* @param treeManager The WizardTreeManager object used to communicate with
* the wizard server objects.
*/
public FilesystemSummary(String name, Route route, WizardTreeManager treeManager)
{
super(name, route, treeManager);
}
/**
* This is the constructor used at wizard buildtime (within the wizard
* builder). The WizardState object is provided so this panel can set
* required initialzation data into the wizard.
*
* @param wizardState The buildtime WizardState.
* @param name The name of this panel.
*/
public FilesystemSummary(WizardState wizardState, String name)
{
super(wizardState, name);
}
/**
* beginDisplay is called every time the panel is made visible.
* This presents an opportunity to synchronize this panel's display
* with the data within the WizardState object.
* <p>
* Every time this panel is displayed, the currently selected filesystems
* will be retrieved from the WizardState object. Each selected filesystem
* will be queried for its free space, and that information will
* be displayed on the panel.
*/
public void beginDisplay()
{
super.beginDisplay();
if(summary != null)
{
remove(summary);
}
summary = new Panel(new ColumnLayout());
add(summary, "Center");
/*
* Get the list of currently selected filesystems from the WizardState.
*/
Vector selectedFilesystemsVector =
(Vector)wizardManager.callServerObjectMethod(getRoute(),
"getData",
new String[] {"java.lang.String.class"},
new Object[] {"selectedFilesystems"});
/*
* Create an array from the vector that can be passed into the
* getFilesystemFreeSpace method.
*/
String[] selectedFilesystems = new String[selectedFilesystemsVector.size()];
for(int index = 0; index < selectedFilesystems.length; index++)
{
selectedFilesystems[index] = (String)selectedFilesystemsVector.elementAt(index);
}
/*
* Get the filesystem free space. First, we must construct a Route object
* that references the FilesystemServerObject.
*/
Route filesystemServerObjectRoute = getRoute().getChildServerRoute("FilesystemObject");
Long[] filesystemFreeSpace =
(Long[])wizardManager.callServerObjectMethod(filesystemServerObjectRoute,
"getFilesystemFreeSpace",
new String[] {"java.lang.String[].class"},
new Object[] {selectedFilesystems});
/*
* Display the results of the query.
*/
for(int index = 0; index < filesystemFreeSpace.length; index++)
{
Panel entryPanel = new Panel(new FlowLayout());
entryPanel.add(new Label(selectedFilesystems[index] + " "));
entryPanel.add(new Label(filesystemFreeSpace[index].toString() + " Bytes Free"));
summary.add(entryPanel);
}
}
/**
* This method must add the non Java core classes required by this
* panel to the specified vector. If classes are missing, the
* wizard will not execute outside of the wizard build environment.
*/
public void addRuntimeResources(Vector resourceVector)
{
super.addRuntimeResources(resourceVector);
/*
* Add the non Java core classes required by this panel.
*/
resourceVector.addElement(new String[] {null, "FilesystemSummary"});
resourceVector.addElement(new String[] {null, "com.sun.wizards.awt.ColumnLayout"});
}
}
The beginDisplay method contains all of the logic within this panel. This method gets the list of selected filesystems from the WizardState, and then requests the free space information for those filesystems from the FilesystemServerObject.Again, the addRuntimeResources method must indicate which non Java core classes to include in the archive. If this method is omitted or does not specify all of the required classes, the result will be a wizard that does not execute outside of the build environment.
8. Example Wizard Builder
It seems that we have all of the pieces of our wizard written. This section of the tutorial ties it all together into a working wizard.The wizard builder is responsible for creating the wizard archive. The builder is subclassed from ArchiveWriter, and must override the createClientTree method. The createClientTree method is responsible for constructing the wizard client tree and the initialization of the WizardState object.
In practice, the ArchiveWriter subclass will also provide a main method so that it can be executed from the java command line to create the archive.
Initialization of the WizardState Object
The initialization of the WizardState object is also performed within the createClientTree method of the wizard builder. This initialization consists of setting the purpose of the wizard, adding server-side objects to the wizard (including Sequences and ServerObjects), and providing default values where required.In this example wizard, we will set the wizard purpose (using the setPurpose method) and add our FilesystemServerObject to the wizard.
Following is the source to the FilesystemWizardBuilder. This code should be written into a file called FilesystemWizardBuilder.java within the "classes" directory of this SDK.
import com.sun.wizards.builder.*;
import com.sun.wizards.core.*;
import com.sun.wizards.panels.*;
public class FilesystemWizardBuilder extends ArchiveWriter
{
/**
* This constructor is called from the main method.
*/
public FilesystemWizardBuilder()
{
super();
/*
* Set the name of the wizard archive. Omit the
* ".class" extension.
*/
this.archiveName = "filesystemWizard";
}
/**
* Creates the client panel tree and initializes the WizardState
* object.
*/
protected void createClientTree()
{
super.createClientTree();
/*
* Set the purpose of the wizard.
*/
wizardState.setPurpose("filesystem");
/*
* Construct the client tree.
*/
TextImagePanel welcomePanel = new TextImagePanel(wizardState, "welcome");
welcomePanel.addText("Welcome to the <bold>Filesystem Free Space Wizard</bold>. " +
"This wizard reports the amount of space available on your disk " +
"filesystems.", 0, 10);
root.addChild(welcomePanel);
root.addChild(new FilesystemSelection(wizardState, "Please select the filesystems to query."));
root.addChild(new FilesystemSummary(wizardState, "Filesystem free space summary."));
/*
* Add the FilesystemServerObject to the WizardState.
*/
wizardState.addChildObject("FilesystemObject", new FilesystemServerObject());
}
/**
* This method allows the user to run this class from the Java
* command line. This method will create the filesystemWizard
* and then exit.
*/
public static void main(String[] args)
{
FilesystemWizardBuilder wizardBuilder= new FilesystemWizardBuilder();
wizardBuilder.writeArchive();
System.exit(0);
}
}
The FilesystemWizardBuilder creates the wizard archive. This example can be created by copying the source code for each class from this document into the "classes" directory of this SDK. After that is done, execute the following commands from the "classes" directory:
javac FilesystemWizardBuilder.java java FilesystemWizardBuilderThe result of these two commands is a class file named "filesystemWizard.class". This is the wizard. To execute the wizard, run the filesystemWizard with the following command from the "classes" directory:
java filesystemWizard
Last modified: Mon Apr 26 13:47:03 PDT 1999