HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

As it turns out, designing and implementing this wizard framework exposes many of the real-world design and programming issues we face when creating Java applications.

Overview
The first step in designing our wizard class is to come up with an interface. An interface is a prototype for a class in which we describe the class' functionality without getting into implementation details. When designing an interface, think about how someone would use the class and jot down some pseudo-code. Translate these ideas into member functions to create the interface. One thing to remember is that the initial pass at an interface will rarely be the last. You will probably end up modifying and improving the interface several times during the course of development.

Since there are several different ways we could go about designing our wizard interface and corresponding implementation class, we need to decide what features are important to us. For instance, what types of containers do we want our wizard to be capable of displaying? What events do we want our wizard to support? Do we want to support multicasting of events, and, if so, how? Do we want to have random access to our wizard pages or is sequential access sufficient? What do we want our wizard buttons to look like?

There are three main elements to a wizard.

  • First, there are the navigation buttons (next, previous, cancel, etc.).
  • Second, there are the information and controls presented by the wizard, which I refer to as "pages."
  • Finally, there is the dialog, typically modal, that contains the active page and the navigation buttons.
The Layout
We now need to think about how the wizard dialog should be laid out. Because the wizard navigation buttons are independent of the content pages, we want to create two distinct areas within the dialog. The first area, which will occupy the upper region of the dialog, will consist of the information that changes between each wizard page. The second area, which will occupy the lower region of the dialog, will contain the wizard navigation buttons. This is where Java's layout manager classes will come in handy.

Messaging
The next issue we need to deal with is messaging. We need to provide a mechanism in our wizard class to notify the outside world of events. To do this we will need to create our own event and listener classes so that the appropriate event occurs when one of our navigation buttons is clicked.

If you are experienced in using the AWT, you are probably familiar with event listeners and event adapters. For instance, Java provides a class called WindowAdapter to accept events that occur on a window. The class is provided mainly for convenience reasons, providing empty methods for all the various window events. To actually do something, you must extend the WindowAdapter class and tell the window about it by calling the AddWindowListener function.

We are going to use a similar structure in our wizard framework. Our wizard class will allow a user to set a particular WizardAdapter to have the wizard messages sent to. We are going to design our wizard so that there can be only one WizardAdapter active at any given time. Note, however, that this does not prevent us from multicasting the events, if desired, since you can add that functionality to the WizardAdapter class. Why not allow multiple listeners to be registered with the wizard class as they can in Java's Window class? Consider the events that our wizard class is producing. In response to these events (next, previous, finish, etc.) we might validate some of the fields in the current page, then tell the wizard class to display the next page or the previous page, or to close the dialog. Can you see some of the potential problems if these events were to be processed by independent listeners? What if the first listener told the wizard to advance to the next page, while the next listener, not knowing about the first, did the same thing? In this case you would get the unintended result of advancing two pages whenever the next button was clicked.

By limiting the wizard class to one WizardAdapter, we require a central point of control by the user of the wizard class. This is important because it is the user of the class, not the wizard class itself, that knows how the wizard should be used. If users want events to be multicast, they can implement that multicasting inside their WizardAdapter, simply forwarding on the wizard events to all interested parties. The lesson here is to provide functionality appropriate for the object being modeled. Don't add capabilities just because you think they're cool.

Example Classes
To show how you use the wizard class, I created a sample vacation wizard. This wizard is relatively simple, yet illustrates most of the features of the wizard class, as well as some interesting "gotchas."

To effectively use the wizard class, several application-specific support classes are created, as shown in Figure 1. An application-specific version of the WizardAdapter class (VacationWizardAdapter) is created to handle messaging, while a class called VacationWizard is used to create the wizard content pages and control the interactions between wizard pages. The VacationWizard class knows about both the wizard class and the VacationWizardAdapter class. The VacationWizardAdapter takes a VacationWizard instance in its constructor so that it knows whom to send messages to. The three classes used together provide a flexible and extensible mechanism for handling wizards.

Figure 1
Figure 1:

The Details
Let us begin by taking a closer look at the interface definition for the wizard class (see Listing 1). From this interface definition we can see that in addition to the basic wizard navigation support, we also want our wizard to be able to support advanced features such as hidden panels, keyboard navigation, help button and messaging.

In implementing our wizard class, one of the first issues we must confront is what existing Java class we want to extend. As I mentioned in the overview, a modal dialog is typically used as the container for the various wizard elements. To get some exposure to some of the new JFC classes, we will extend the JFC class Jdialog.

public class Wizard extends com.sun.java.swing.JDialog
implements WizardControllerInterface

Notice that this declaration says we are going to implement the WizardInterface that we listed earlier. Therefore, our class definition must define all the member functions we defined in our WizardInterface.

The Layout
As I alluded earlier, Java's layout classes come in handy when implementing our Wizard class. Using a GridBagLayout, we can lay out the wizard dialog's contents as shown in Figure 2. The uppermost area is itself a Jpanel that uses its own layout manager, CardLayout, to manage the wizard pages. I won't go into the details of laying out controls using the GridBagLayout here, but look at the source code if you are interested.

Figure 2
Figure 2:

Messaging
How do we go about handling the messaging? First, we need to create a new event type called a WizardEvent by extending Java's EventObject class (see Listing 2). We will then need to create a WizardListener class by extending the Java's EventListener class. Our WizardListener class will handle the events shown in Listing 3.

Inside our wizard class we need to create ActionListeners for each of our wizard navigation buttons, such as:

previous_.addActionListener(new PreviousButtonListener());

Our listener classes simply fire off an appropriate event when one of our navigation buttons is clicked. Listing 4 shows how one of these functions, PreviousButtonListener, is implemented.

In addition, we need to add KeyListeners for each of our wizard navigation buttons since we want to support keyboard control of our navigation buttons using the Enter Key.

next_.addKeyListener(new NextEnterKeyListener());

All we are doing here is checking to see if the Enter Key is pressed while the focus is on one of our navigation buttons. If it is, we fire off the appropriate event. Listing 5 shows how the NextEnterKeyListener is implemented.

Page Navigation
How do we accomplish the task of moving between pages in the wizard class? As mentioned earlier, the Java Layout Manager called CardLayout is used to switch between wizard pages. The CardLayout is ideal for use with wizards because it allows easy access to all the pages in the layout either sequentially or randomly via a name. (See the complete listing for details.)

The wizard class offers the ability to hide certain pages of the wizard if desired. As we will see in our example, many times we might want to create a wizard that shows certain pages conditionally. This is accomplished by using the wizard functions hidePanel and unHidePanel. PanelAttribute class is used inside the wizard class to maintain state about each page of the wizard. If a page is marked as hidden, that page is bypassed by the forward and next logic of the wizard. (See the complete listing for details.)

Miscellaneous
Our wizard class also has the ability to display messages. The user of the wizard class might want to display a message stating that all the required fields of the wizard have not been filled in. A user might also want to display a specific field validation message, such as "value must be between 10 and 50." The wizard class handles these situations using a specific function called displayRequiredFieldMessage, as well as provides a generic error message function called displayErrorMessage that takes a title and a message string.

An Example
To create an actual wizard of your own, you need to decide on the content and order of the wizard pages. Once you have figured that out, you need to decide what container you will use for the wizard pages. Our wizard class allows you to use any class derived from the AWT's Component class. For the sample wizard I used a variety of components such as the AWT's Panel and ScrollPanel class, as well as the new JFC's Jpanel class.

Once you have your wizard pages designed, you need to deal with any required interactions between the various controls on the same page as well as the interactions between pages. For instance, in the VacationWizard example, the second page of the wizard is displayed only if Hawaii has been selected as the destination. Therefore, we need to be able to conditionally hide/unhide this wizard page based on the value of the radio buttons on the first wizard page.

To check the values of various controls at the appropriate time, messages need to be sent when a wizard page is activated and deactivated. This is the job of our VacationWizardAdapter. Since the VacationWizardAdapter is the class that is getting the messages (previous, next, finish, etc.) from the Wizard Class, it is responsible for calling the initializePage and finalizePage functions of the VacationWizard class, passing the current wizard page as a parameter. The finalizePage function can veto moving to the next page by returning false. This is a handy mechanism for validating fields and preventing the wizard from moving forward until all the appropriate fields and values are filled in.

The code in the VacationWizard class deals mostly with creating the content pages for the wizard. However, there is one interesting thing to look at. While creating and testing the wizard pages, I discovered some undesired behavior with the AWT's TextArea class. I wanted to use a non-editable TextArea to display some explanatory text at the top of each wizard page (see Figure 3). At first glance everything looked okay. However, when I used the tab key to tab around the dialog, I noticed that once the TextArea received the focus, it did not want to give it back. Although I'm sure the TextArea was happy, the rest of my controls were feeling left out.

Figure 3
Figure 3:

I imagine this behavior stems from the fact that in a standard editable text area, the tab key really means to put in a tab character. Fortunately, you can get around this problem by deriving a new class from TextArea. I called my new class UnfocusedTextArea (see Listing 6). After searching the documentation, I found a handy function in the AWT's Component class, from which TextArea descends, called isFocusTraversable. The key to solving the problem lies in overriding this function in our UnfocusedTextArea class so that it simply returns false. This means that the text area will never actually get the focus -- which is exactly what we want to happen in this case.

Conclusion
There are many different ways to design a wizard class and its supporting classes. I have presented one way that provides a flexible and extensible framework for creating real-world wizards. One thing to keep in mind when building applications of your own is that most real-world problems require more than one class to model. In most cases it is best to solve problems with mini-frameworks that use a general-purpose class or classes (like the Wizard class) combined with one or more specializing classes (like the VacationWizard and VacationWizardAdapter classes).

About the Author
Donald Fowler works as a software design developer at Rogue Wave Software where he is currently the technical lead for the Software Parts Manager product line. He has 15 years of software design and development experience specializing in GUI programming and 7 years' experience in object-oriented design and programming. Donald can be e-mailed at [email protected]

	

Listing 1: WizardInterface

import java.awt.Component;

//Interface for a Wizard Class
public interface WizardInterface
{
//
public void setWizardAdapter(WizardAdapter adapter);
public WizardAdapter getWizardAdapter();

public void setTitle(String title);

//Optional Help Button
public void setHelpVisible(boolean state);
public boolean isHelpVisible();

//Enter key behavior
public void setEnterKeyActive(boolean state);
public boolean isEnterKeyActive();

//Panels can be hidden - if hidden, they are skipped by previous,next
public void hidePanel(int index);
public void hidePanel(Component panel);
public boolean isPanelHidden(int index);
public boolean isPanelHidden(Component panel);
public void unHidePanel(int index);
public void unHidePanel(Component panel);

public int getNumberOfPanels();
public int getCurrentPanelIndex();
public Component getCurrentPanel();
public Component getPanel(int index);

public int getPanelIndex(Component panel);


//allows you to go directly to any panel in the wizard
//returns false if panel is hidden and could not be displayed
public boolean setCurrentPanel(int index);

public void doPrevious();
public void doNext();
public void doFinish();
public void doCancel();
public void doHelp();

public boolean isPreviousEnabled();
public boolean isNextEnabled();
public boolean isFinishEnabled();
public boolean isCancelEnabled();
public boolean isHelpEnabled();

}

Listing 2: WizardEvent

import java.util.*;

public class WizardEvent extends java.util.EventObject 
{
    Wizard wizard_;
    
    public WizardEvent(Object source, Wizard wiz)
	{
        super(source);
        wizard_=wiz;
    }
    public Wizard getWizard(){
        return wizard_;
    }
}
 
Listing 3: WizardListener

import java.util.EventListener;

public interface WizardListener extends EventListener 
{
    public void nextButtonClicked(WizardEvent evt);
    public void previousButtonClicked(WizardEvent evt);
    public void cancelButtonClicked(WizardEvent evt);
    public void finishButtonClicked(WizardEvent evt);
    public void helpButtonClicked(WizardEvent evt);
    public void wizardActivated(WizardEvent evt);
}

Listing 4: PreviousButtonListener Class

private class PreviousButtonListener implements ActionListener 
{
        public void actionPerformed(ActionEvent e) 
		{
            //broadcast WizardEvent out to everyone
            WizardEvent evt;
            evt=new WizardEvent(e,thisWizard_);
            wizardAdapter_.previousButtonClicked(evt);
        }//actionPerformed
    }//PreviousButtonListener

Listing 5: NextEnterKeyListener

private class NextEnterKeyListener extends KeyAdapter 
{
        public void keyReleased(KeyEvent k) 
		{
            if (enterKeyActive_)
			{
                if (k.getKeyCode() == java.awt.event.KeyEvent.VK_ENTER) 
				{
                    WizardEvent evt;
                    evt=new WizardEvent(k,thisWizard_);
                    wizardAdapter_.nextButtonClicked(evt);
                }//if keycode
            }//enterKeyActive_
        }//keyReleased
    }//NextEnterKeyListener

Listing 6: UnfocusedTextArea Class

private class UnfocusedTextArea extends TextArea {
        public UnfocusedTextArea(String s, int row, int col, int visibility)
		{
            super(s,row,col,visibility);
        }

        public boolean isFocusTraversable(){
            return false;
        }

    }
  
      
 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.