Chapter 11. Wizards

 

 

Today, most people are familiar with the concept of an application providing a wizard for certain tasks. By separating a complex task into a series of steps, performed one at a time, it’s possible to condense an otherwise intimidating set of options into a relatively pleasant end-user experience. A good example can be found in Eclipse when creating a new project. Eclipse supports development in a wide variety of languages, and each of those languages has many different options that can be configured for a new project. Rather than dump you straight into a dialog box filled with combo boxes, text-entry fields, and checkboxes, however, Eclipse guides you through the process of creating a new project one step at a time. You can choose the language for your new project, then a location, and then configure language-specific settings. Eclipse can infer sensible defaults for most of these options; any time after you’ve provided the bare minimum of information, you can click the Finish button to tell the program to go ahead with creating the project. If you choose to configure details yourself, you can freely move back and forth between steps, changing choices you made earlier. The whole experience is made enjoyable because you can see what effects your choices have before they’re made permanent.

JFace provides a framework to help you create and use wizards in your own application. The framework is composed of a three-level hierarchy. Each level can contain multiple instances of the level below it—a container contains wizards, whereas wizards contain pages. Each level defines both an interface and a default implementation of that interface. It’s generally easiest to subclass the default implementations, but for maximum flexibility the framework is designed to only reference objects by the interfaces. Any complete implementation of a wizard-related interface may be freely mixed with the existing classes with no problems.

Figure 11.1 provides an overview of the classes used to create a typical wizard. You should recognize some of these classes from our discussion of dialogs in the previous chapter.

Figure 11.1. Wizard classes

Figure 11.1 shows how the classes and interfaces of the wizard framework fit together. The most important thing to take away is that the concrete classes at each level, WizardDialog and Wizard, depend only on the interface of the next level down, not the default implementation. This same property holds when traversing the other direction—although IWizardPage makes use of IWizard, it doesn’t matter whether IWizard is implemented by Wizard or by some other class. We’ll start at the bottom of the diagram and work our way upward through the hierarchy, discussing each of these classes in turn. We’ll then show you how they work together.

11.1. Multipage dialogs

All the dialogs we discussed in the previous chapter consisted of a single page. All the available options were displayed at the same time, and the dialog could only open and close. However, a wizard needs to display more than one page in a single dialog. JFace provides a generic interface for use in multipage dialogs that serves as the parent of the wizard-specific interfaces. We’ll briefly cover this generic interface before turning our attention to wizards.

11.1.1. IDialogPage

IDialogPage is the base interface for the pages in any multipage dialog. The interface provides methods to configure all the attributes for a given page, such as its title, a description, or an Image to display. The most important method is createControl(), which is called when it’s time for the page to create its contents. JFace provides a default implementation of IDialogPage with the abstract DialogPage class, which provides default implementations of all methods declared in the interface except createControl(). On its own, IDialogPage is neither very interesting nor useful, so we’ll move on to discuss its most commonly used subinterface: IWizardPage.

11.1.2. IWizardPage

The basic element of the wizard is a page. A page should represent one step for the user in whatever process you’re guiding them through. Key to creating a usable wizard is defining these steps well—too much information on the page is confusing, but too many separate steps are annoying for the user.

JFace uses the IWizardPage interface to represent a single page in a wizard. A variety of methods are defined in this interface; the most important ones are summarized in table 11.1.

Table 11.1. Important methods defined by IWizardPage

Method

Description

getName() Each page must have a unique name. This method is often used to retrieve a particular page from the wizard.
getNextPage(), getPreviousPage() These methods are called when the user clicks the Next or Previous button to move to another page. The proper page to move to (which may vary depending on selections the user has made) must be returned.
isPageComplete() Indicates whether the user has filled out everything that is necessary on this page.
canFlipToNextpage() Indicates whether the Next button should be available for use. This method typically returns true if the page is complete and at least one page is left in the wizard.

Several other straightforward getter and setter methods are also defined in IWizardPage. Implementing all of them could quickly become tedious. Luckily, JFace comes to your rescue with a default implementation—the WizardPage class.

11.1.3. WizardPage

WizardPage implements the IWizardPage interface and provides much of the basic logic for a page. You need only implement createControl() from IDialogPage to build the controls appropriate to your page, although a variety of other methods may be overridden if you wish to modify the page’s behavior.

Listing 11.1 shows a sample implementation of WizardPage. The page presents a single checkbox, asking the user whether to use the default directory (perhaps for setting up a new Java project, or some similar task). Taken on its own, the class doesn’t provide any interesting behavior. Later in the chapter, we’ll show you how to combine multiple implementations of IWizardPage to build a complete wizard.

Listing 11.1. DirectoryPage.java
package com.swtjface.Ch11;

import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;

public class DirectoryPage extends WizardPage
{
  public static final String PAGE_NAME = "Directory";
  private Button button;

  public DirectoryPage()
  {
    super(PAGE_NAME, "Directory Page", null);  1  Constructor
  }

  public void createControl(Composite parent)  2  createControls() method
  {
    Composite topLevel = new Composite(parent, SWT.NONE);
    topLevel.setLayout(new GridLayout(2, false));

    Label l = new Label(topLevel, SWT.CENTER);
    l.setText("Use default directory?");

    button = new Button(topLevel, SWT.CHECK);

    setControl(topLevel);   3  setControl() method
    setPageComplete(true);    4  setPageComplete() method
  }

  public boolean useDefaultDirectory()    5  useDefaultDirectory() method
  {
    return button.getSelection();
  }
}

  • The WizardPage constructor takes a page name (which must be unique in this wizard), the title for the page, and (optionally) an image descriptor for the image to display on this page.
  • Your page must implement this method. Here you create all the controls that will be displayed on the page. For this example, we create a single label and a checkbox.
  • When you’ve finished creating your controls, you must call setControl() to let the superclass know about your new creations. Failing to do so results in internal JFace assertions failing at runtime.
  • This method is used to signal whether the page has sufficient information to allow the user to move on. For simplicity, we set it to true here. In most applications, you’ll need to attach listeners to your controls and wait for events to signal that the user has entered all required data.
  • Here we make a public method available to allow the status of the checkbox to be queried. Other classes in the wizard can then query whether the user wants to use the default location, and act accordingly.

The page is responsible for maintaining its own state, but it doesn’t need to worry about other pages in the wizard or where it fits into the overall flow. Its only job is to let the wizard framework know whether it’s complete enough to move on.

11.2. The wizard

A wizard is a step up from an individual page. A wizard groups a collection of pages and represents the overall task that the user is trying to perform. In addition to grouping the pages, the wizard’s primary responsibility is to keep track of whether the overall task has enough information to finish and to do whatever processing is necessary when the task is finished.

Like wizard pages, the wizard has both an interface and a default implementation.

11.2.1. IWizard

The IWizard interface has quite a few methods, most of which are straightforward accessors for configuration options. A few are worth mentioning in greater detail, however; we present them in table 11.2.

Table 11.2. Important methods defined by the IWizard interface

Method

Description

canFinish() Called periodically to check whether it’s currently possible for this wizard to finish. The result is used to determine whether to enable the Finish button. Note that returning true doesn’t imply that the wizard will immediately finish, only that if the user clicked the Finish button right now; any information he hasn’t entered can be given reasonable defaults.
createPageControls() Intended to allow the wizard to create the controls for all of its pages in advance, so that it can calculate the maximum size needed and avoid having to resize when switching from one page to another. The Wizard implementation of this method calls createControl() on all pages included in the wizard, which is generally what you want. However, this method can be overridden if you want to delay creation of some of the pages, especially if the creation is slow and may not be needed.
performCancel() Provides notification that the user has asked to cancel the wizard. Any cleanup or other processing that should be done for a cancellation should be performed here. This method returns a boolean; returning false signals the framework that cancellation isn’t allowed at the current time.
performFinish() Provides notification that the user has successfully finished the wizard. All logic related to the wizard finishing should be performed here. Like performCancel(), this method returns a boolean, and returning false signals the framework that the finish request was refused.

As with wizard pages, JFace saves you from the drudgery of implementing all the methods defined in IWizard by providing a default implementation in the form of the Wizard class.

11.2.2. Wizard

Continuing our example, we’ll show the use of Wizard, JFace’s default implementation of IWizard. Our subclass is simple, because Wizard does most of the work for us. Note that Wizard provides a variety of configuration options (such as setting images or colors) that we don’t show here.

Our sample wizard continues the project setup that we discussed earlier, using the DirectoryPage class we developed. We’ll discuss only the skeleton of the class here and present the full listing later in the chapter:

public class ProjectWizard extends Wizard
{
  public ProjectWizard()
  {
    super();
  }

  public void addPages()    1  addPages() method
  {
    addPage(new DirectoryPage());
    //... add other pages as needed
  }
  public boolean performFinish()   2  performFinish() method
  {
    DirectoryPage dirPage =
        (DirectoryPage)getPage(DirectoryPage.PAGE_NAME);
    if(dirPage.useDefaultDirectory())
    {
      ...
    }
    return true;
  }

  public boolean performCancel()   3  performCancel() method
  {
    //... perform cancel processing
    return true;
  }
}

  • This method is called to tell the wizard to add any pages it desires. Pages are normally displayed in the order they’re added. To change this behavior, you must override getNextPage() and getPreviousPage().
  • Here we go through the actual process of creating our project. The DirectoryPage we added earlier is retrieved using its page name, and it can then be queried for the data we’re interested in. This also demonstrates why each page in a wizard must have a unique name—if there were duplicates, getPage() wouldn’t be able to determine which page to return.
  • If any cleanup must be done when the user cancels, we do it here.

As you can see, Wizard takes care of most of the work for you. Aside from setting configuration options, there is little to do other than implement performFinish() and, if you wish, performCancel().

11.3. Putting it all together

Finally, we come to the layer that controls the entire wizard: the wizard container. Although at first glance it may seem odd to have this separate from the wizard itself, it allows one container to group multiple wizards together and switch between them.

11.3.1. Wizard containers

A wizard container is meant to act as a host for one or more wizards. The IWizardContainer interface isn’t interesting in itself—it provides methods to get the current page, several methods to update aspects of the container’s window, and a method to programmatically change the currently displayed page. Most of the real action comes from WizardDialog, which implements IWizardContainer.

11.3.2. WizardDialog

Clients are free to provide their own implementations of IWizardContainer, but WizardDialog will be sufficient for most needs. Typically it isn’t even necessary to subclass WizardDialog. We’ll first show how to use WizardDialog as is, and then demonstrate creating a subclass that decides at runtime whether to display certain pages.

First, listing 11.2 shows the standard WizardDialog.

Listing 11.2. WizardDialogDemo.java
package com.swtjface.Ch11;

import org.eclipse.jface.window.ApplicationWindow;
import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.widgets.Display;

public class WizardDialogDemo
{
  public static void main(String[] args)
  {
    ApplicationWindow testWindow = new ApplicationWindow(null);

    testWindow.setBlockOnOpen(false);
    testWindow.open();

    ProjectWizard wizard = new ProjectWizard();
    WizardDialog wizardDialog = new WizardDialog(
                                  testWindow.getShell(),
                                  wizard);
    wizardDialog.create();
    wizardDialog.open();
  }
}

As our demo program shows, if all you wish to do is display a wizard’s pages in order, using a WizardDialog is simple. Just pass the IWizard to the dialog’s constructor and call open(), and your wizard’s pages will be displayed in the order in which they were added.

A warning about initialization errors

The example code will run as we’ve presented it, but there is a gotcha to be aware of if you’re testing wizard code on your own. When we were originally testing wizard functionality, we created a subclass of WizardDialog and added a main() method. Unfortunately, this doesn’t work due to a subtle interaction between SWT and the java classloader. When you type java TestWizardDialog, the Java VM first loads and initializes the TestWizardDialog class; it then looks for and executes the static void main() method defined there. To initialize TestWizardDialog, the VM needs to initialize all of its superclasses, which include org.eclipse.jface.dialogs.Dialog. Dialog, however, has static initialization code that attempts to retrieve certain images from the ImageRegistry. Because the system hasn’t been fully initialized at this point (remember, main() hasn’t even started execution), retrieving the values from the registry fails, throwing a NullPointerException and causing the main thread to terminate. In a typical application this won’t be an issue, because main() is usually located in a class by itself or in a subclass of ApplicationWindow. However, it’s worth being aware of this potential issue here and in other SWT classes, in case you’re ever bitten by it. The symptoms are strange, but the solution is simple—put your main() method in a class that doesn’t extend an SWT class.

11.4. Combining wizards

Occasionally you may have a situation that requires the user to select from one of several possible wizards. A good example occurs in Eclipse when you select File->New->Other. You’re shown a wizard with a variety of options to choose which new object you wish to create. Whichever one you choose launches a separate wizard as appropriate. JFace provides support for this use case with the WizardSelectionPage class and the IWizardNode interface.

11.4.1. WizardSelectionPage

WizardSelectionPage extends WizardPage and is in general intended to act like any other page in a wizard. One additional method is important for this class: setSelectedNode(), which takes an IWizardNode as a parameter. The subclass should call this method as appropriate when a node has been selected.

As is the case when you subclass WizardPage directly, you must implement createControl() in a subclass of WizardSelectionPage. The method should be implemented to present the available choices to the user—often it’s in the form of a tree, but the JFace designers chose to not make any assumptions about what might be the best presentation for your situation. Each available selection should be tied to an instance of IWizardNode.

11.4.2. IWizardNode

An IWizardNode is intended to be a placeholder for an actual instance of a wizard. WizardSelectionPage passes these instances to setSelectedNode() and retrieves them from getSelectedNode() when the selection page has completed. This interface includes two important methods (see table 11.3).

Table 11.3. Important methods defined by the IWizardNode interface

Method

Description

getWizard() Retrieves the wizard tied to this node. It’s assumed that the wizard won’t be created until this method is called for the first time, and that if this method is called multiple times, the same cached wizard instance will be returned rather than a new one being created every time.
isContentCreated() Queries the status of an IWizardNode and checks whether it has already instantiated a wizard.

Generally, you’ll use these classes by subclassing WizardDialog. After a WizardSelectionPage has finished, you’ll call getSelectedNode() to retrieve the node the user chose. You can then call getWizard() on that node to retrieve the wizard and pass the wizard instance to setWizard() in WizardDialog.

11.5. Persistent wizard data

Sometimes you need to save a wizard’s data between invocations. For example, the Create New Java Class wizard in Eclipse includes a series of checkboxes to generate code such as a public static void main() method or default implementations of abstract methods in the superclass. The state of these checkboxes is saved between uses of the wizard so that if you uncheck the box to generate a main() method once, you won’t have to change it every time.

JFace provides a convenient way to manage these persistent settings with the DialogSettings class. Although theses techniques are often used with wizards, there is no reason the same classes can’t be used by any other dialog that wishes to persist state.

11.5.1. DialogSettings

DialogSettings provides an implementation of the IDialogSettings interface using a hash table and backed by an XML file. This is sufficient for most needs; but you should generally reference objects in terms of the interface rather than the concrete implementation, in case you find it necessary to switch implementations at some point in the future.

Using IDialogSettings is simple:

IDialogSettings settings = new DialogSettings("mydialog");
settings.put("checkboxOneChecked", true);
settings.put("defaultName", "TestDialog");

settings.save("settings.xml");

IDialogSettings loadedSettings = new DialogSettings(null);
loadedSettings.load("settings.xml");

loadedSettings.getBoolean("checkboxOneChecked");
loadedSettings.get("defaultName");

When run, this code writes a simple XML file to the current directory that (once cleaned up for readability) looks something like this:

<?xml version="1.0" encoding="UTF-8"?>
<section name="mydialog">
  <item key="defaultName" value="TestDialog"/>
  <item key="checkboxOneChecked" value="true"/>
</section>

Storing values in XML this way has the advantage that it’s easy to edit them by hand, either to test odd combinations of values or to make emergency repairs if invalid values are somehow stored.

Storing values

The values are put into the settings object. It may be saved either to a file by giving the save() method a filename (as shown) or to any java.io.Writer. Likewise, it’s read from the file (or a java.io.Reader) using the load() method. Because DialogSettings loads and saves using XML, you’ll need xercesImpl.jar and xmlParserAPIs.jar (from $ECLIPSE_HOME/plugins/org.apache.xerces_x.y.z) in your classpath.

The name you pass to the constructor of DialogSettings creates a section. Sections are ways to group related data in the overall dialog settings. You can retrieve a section by name using getSection(), which returns another instance of IDialogSettings. As you can see from the code that loads the settings, there is no need to specify section names when loading; they’re picked up automatically from the file.

Retrieving values

Calling get() or getArray() returns null if no value has been set for the given key. However, the various numeric get()s throw NumberFormatExceptions if you attempt to retrieve a nonset value (or if the file has been edited by hand so it’s no longer a valid number), so you must be prepared to handle these cases if there is a possibility that some values haven’t been set.

How useful all this is depends on your target platform. In Java 1.4, similar functionality is provided by the classes in the java.util.prefs package. From a design standpoint, it’s generally better to stick to the facilities provided by the base platform, but you don’t have this luxury if you’re supporting Java 1.3 or earlier in your application; in this case, IDialogSettings can make a convenient alternative.

11.6. Updating WidgetWindow

To create a functional wizard, we need a few more classes than are usually required for WidgetWindow. You saw DirectoryPage earlier in the chapter. In order to finish the example, we need to add a couple more pages, complete the implementation of ProjectWizard, and add a Composite subclass.

First, let’s look at the ChooseDirectoryPage. This page is invoked when the user declines to use the default directory. The page presents a text input field for the user to enter a choice of directory. ChooseDirectoryPage is presented in listing 11.3.

 

Note

It’s important to remember that this design is purely for the purpose of demonstrating how multiple wizard pages work. In a real application, you should let the user enter his choice of directory on the same page as the use default checkbox, and you’ll probably use a DirectoryDialog as discussed in the previous chapter.

 

Listing 11.3. ChooseDirectoryPage.java
package com.swtjface.Ch11;

import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.*;

public class ChooseDirectoryPage extends WizardPage
{
  public static final String PAGE_NAME = "Choose Directory";

  private Text text;

  public ChooseDirectoryPage()
  {
    super(PAGE_NAME, "Choose Directory Page", null);
  }

  public void createControl(Composite parent)
  {
    Composite topLevel = new Composite(parent, SWT.NONE);
    topLevel.setLayout(new GridLayout(2, false));

    Label l = new Label(topLevel, SWT.CENTER);
    l.setText("Enter the directory to use:");

    text = new Text(topLevel, SWT.SINGLE);
    text.setLayoutData(new GridData(GridData.FILL_HORIZONTAL));

    setControl(topLevel);
    setPageComplete(true);
  }

  public String getDirectory()
  {
    return text.getText();
  }
}

This page is similar to DirectoryPage. The input field is presented to the user, and a public method is made available for the rest of the application to query the user’s choice.

The final page in our example wizard is a summary of the user’s choices. There is no user interaction on this page; it displays a text string indicating the choice made. SummaryPage appears in listing 11.4.

Listing 11.4. SummaryPage.java
package com.swtjface.Ch11;

import org.eclipse.jface.wizard.WizardPage;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;

public class SummaryPage extends WizardPage
{
  public static final String PAGE_NAME = "Summary";
  private Label textLabel;

  public SummaryPage()
  {
    super(PAGE_NAME, "Summary Page", null);
  }

  public void createControl(Composite parent)
  {
    Composite topLevel = new Composite(parent, SWT.NONE);
    topLevel.setLayout(new FillLayout());

    textLabel = new Label(topLevel, SWT.CENTER);
    textLabel.setText("");

    setControl(topLevel);
    setPageComplete(true);
  }

  public void updateText(String newText)
  {
    textLabel.setText(newText);
  }
}

In some respects, this class is the opposite of the previous two. Instead of providing a method for clients to query the state of the page, the class offers a method to update the displayed text. As an alternative to forcing the state of the page to be explicitly updated, it would also be possible to let this class query some shared state when it needs to display itself, such as a Project object that represents the project that’s in the process of being built. This approach would require overriding the setVisible() method defined in IDialogPage. When setVisible(true) is called, the textLabel and any other relevant widgets will be refreshed. In general, you should prefer this approach, because it localizes knowledge of how to display things to the SummaryPage. We implemented it as we did to avoid having to write a Project class and to keep the example short.

We next present the full implementation of ProjectWizard in listing 11.5. There are two enhancements over the snippet we showed earlier in the chapter. First, we’ve expanded the implementation of addPages() to add all the pages needed for the wizard. More importantly, we’ve expanded the logic in getNextPage().

Listing 11.5. ProjectWizard.java
package com.swtjface.Ch11;

import org.eclipse.jface.wizard.IWizardPage;
import org.eclipse.jface.wizard.Wizard;

public class ProjectWizard extends Wizard
{
public void addPages()
  {
    addPage(new DirectoryPage());
    addPage(new ChooseDirectoryPage());
    addPage(new SummaryPage());
  }

  public boolean performFinish()
  {
    DirectoryPage dirPage = getDirectoryPage();
    if (dirPage.useDefaultDirectory())
    {
      System.out.println("Using default directory");
    }
    else
    {
      ChooseDirectoryPage choosePage = getChoosePage();
      System.out.println(
        "Using directory: " + choosePage.getDirectory());
    }
    return true;
  }

  private ChooseDirectoryPage getChoosePage()
  {
    return (ChooseDirectoryPage) getPage(
      ChooseDirectoryPage.PAGE_NAME);
  }

  private DirectoryPage getDirectoryPage()
  {
    return (DirectoryPage) getPage(DirectoryPage.PAGE_NAME);
  }

  public boolean performCancel()
  {
    System.out.println("Perform Cancel called");
    return true;
  }

  public IWizardPage getNextPage(IWizardPage page)
  {
    if (page instanceof DirectoryPage)
    {
      DirectoryPage dirPage = (DirectoryPage) page;
      if (dirPage.useDefaultDirectory())
      {
        SummaryPage summaryPage =
          (SummaryPage) getPage(SummaryPage.PAGE_NAME);
        summaryPage.updateText("Using default directory");
        return summaryPage;
      }
    }

    IWizardPage nextPage = super.getNextPage(page);
    if (nextPage instanceof SummaryPage)
    {
      SummaryPage summary = (SummaryPage) nextPage;
      DirectoryPage dirPage = getDirectoryPage();
      summary.updateText(
        dirPage.useDefaultDirectory()
         ? "Using default directory"
         : "Using directory:" + getChoosePage().getDirectory());
    }
    return nextPage;
  }
}

The meat of this class is contained in the getNextPage() method. It’s here that we control the navigation between pages. We must handle two scenarios correctly.

First is the case when the user is leaving the DirectoryPage, which is where she can choose to use the default directory. The parameter passed to getNextPage() is the page the user is coming from, so we check whether it’s the DirectoryPage. If so, after casting the parameter to the correct implementation of IWizardPage, we query the status of the checkbox. If it has been checked, we want to skip straight to the status page, so we retrieve it using getPage() and return it.

If the previous page wasn’t DirectoryPage, or if the user unchecked the checkbox, we fall back on the default behavior for determining the next page by calling super.getNextPage(). However, if the next page will be the summary page, we need to make sure to update the text to reflect the user’s current choice. In this case, we cast the IWizardPage to a SummaryPage and then retrieve the other pages as necessary to determine the correct text to display. As noted after our discussion of SummaryPage, this logic is caused by our not having a shared state available to SummaryPage; in general it should be avoided because the complexity can quickly become overwhelming in a wizard with more pages.

The final class to complete our example is the composite used by WidgetWindow, shown in listing 11.6. Like the composite used in the previous chapter, this one isn’t very interesting. It presents a button that, when clicked, initializes and displays a WizardDialog with our ProjectWizard.

Listing 11.6. Ch11WizardComposite.java
package com.swtjface.Ch11;

import org.eclipse.jface.wizard.WizardDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;

public class Ch11WizardComposite extends Composite
{
  public Ch11WizardComposite(Composite parent)
  {
    super(parent, SWT.NONE);
    buildControls();
  }

  protected void buildControls()
  {
    final Composite parent = this;
    FillLayout layout = new FillLayout();
    parent.setLayout(layout);

    Button dialogBtn = new Button(parent, SWT.PUSH);
    dialogBtn.setText("Wizard Dialog...");
    dialogBtn.addSelectionListener(new SelectionListener()
    {

      public void widgetSelected(SelectionEvent e)
      {
        WizardDialog dialog =
          new WizardDialog(
            parent.getShell(),
            new ProjectWizard());
        dialog.open();
      }

      public void widgetDefaultSelected(SelectionEvent e) {}
    });
  }

}

The part of this class to notice is in widgetSelected(), where we initialize the dialog. Notice that WizardDialog works exactly as we want with no modifications. We can pass a new instance of our ProjectWizard to it and let it do its thing.

11.7. Summary

The wizard framework builds on the dialog classes we’ve already discussed by adding support for multiple pages in a single dialog. There are three levels in the hierarchy of the wizard framework. IWizardContainer, which is implemented by WizardDialog, contains instances of IWizard, usually Wizard. An IWizard, in turn, contains multiple IWizardPages.

The life cycle of a wizard isn’t complex. Each IWizardPage implements the createControl() method to build whatever controls are necessary to display. addPages() is called on IWizard, which instantiates and tracks the pages it intends to use. In the default Wizard implementation, this requires nothing more than calling addPage() for each one. As each page is displayed, isPageComplete() and canFlipToNextPage() are called to determine whether the Next and Previous buttons should be enabled. When the user clicks either Next or Previous, getNextPage() or getPreviousPage() is called on the current page to determine which page should be displayed next. Finally, once the user clicks Finish or Cancel, performFinish() or performCancel() is called on the wizard itself to signal that the appropriate processing should be performed.

In addition to the standard three interfaces necessary to implement any wizard, JFace provides an additional level in the hierarchy that you can use to assemble multiple wizards. A WizardSelectionPage uses instances of IWizardNode to present multiple wizards to the user, allowing one to be selected. Each node is set up to instantiate the wizard only after it’s selected, to avoid doing unnecessary work.

The IDialogSettings interface provides a way to easily store persistent settings data, as long as those settings can be represented by Java primitives. The default implementation, DialogSettings, serializes the objects to an XML file.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.14.231