BookNBPlatformCookbookCH0202

Contents

NetBeans Platform Cookbook Chapter 02 02

Using Wizards

Some actions need to enter more data and more complex than can not only one form page contain. Some actions need to select some option, data-type, some alternative. For such an action you can create a wizard that passes the user through several steps to complete the task. Dialog API of the NetBeans Platform offers you simple standard way to describe steps, panels and verifying for your task.

The wizard API takes care about creating, displaying, step switching. You describe details only. Description of the wizard is divided into several objects:

  • WizardDescriptor is controller and descriptor of the wizard. It contains WizardPanels and the data model - you can use this object or your own data object (see next points).
  • WizardPanel# for each step is controller for view of the step's data. It creates view panel, loads and stores data from/into the data model, checks validity of entered data. WizardPanels can be subclassed from common abstract class if it cares common tasks.
  • VisualPanel# for each step is view, visual form manipulating data by the user. It does not contain any business logic. It is only view for data from data model and can be reused in other wizards.
  • WizardAction creates the WizardDescriptor instance, sets it visible and performs action/task using data collected by wizard.

As a data model you can use your business object or use the WizardDescriptor instance itself. The WizardDescriptor instance is passed as a parameter into WizardPanel# readSettings() and storeSettings() method where you can retrieve your data model instance from it or use its methods getProperty(pname) and putProperty(pname, pvalue).

image:nbpcook_02_06_wizard.png

Figure 2.6 Wizard


How to

Create new file, select Module Development Category and the Wizard type. The New Wizard wizard form appears.

Leave the Custom Type and Static Sequence selected (if you do not want some other) and enter Number of steps. Press Next button.

Type name into Class Name Prefix field - name of the entity or task you will maintain by this wizard (e. g. Book). You can change package for created files. The wizard shows list of files that will be created or modified. Press Finish button.

The wizard created set of classes describing your wizard and the action calling your new wizard. It registered automatically dependency on Dialogs API, Utilities API and UI Utilities API modules also.

In each VisualPanel# (e. g. BookVisualPanel1) create form for visual representation of entered data. Add listeners to components to fire property change events from your visual panel. Use property names of your object or define public constants in your panel. Insert access to get properties from fields by getXxx() methods rather than package friendly fields. Do not insert any other logic into visual panel.

Prepare each WizardPanel (e. g. BookWizardPanel1): note it is subclass of WizardDescriptor.Panel<Data> - rewrite it to WizardDescriptor.Panel<WizardDescriptor> or WizardDescriptor.Panel<YourBusClass>. Add PropertyChangeListener interface to implements clause - you must listen changes of the visual panel.

Think out if is useful extract common functionality of all wizard panels to abstract superclass - e. g. store PropertyChangeListeners, re-fire change events of panels, messages settings.

Change the getComponent() method in the first WizardPanel to initialize wizard properties:

 public BookVisualPanel1 getComponent() {
    if (component == null) {
       component = new BookVisualPanel1();
     
       // which step index I am
       component.putClientProperty(
              WizardDescriptor.PROP_CONTENT_SELECTED_INDEX
            , Integer.valueOf(0));

       // for the first panel - initialize wizard
       component.putClientProperty(
              WizardDescriptor.PROP_AUTO_WIZARD_STYLE
            , Boolean.TRUE);  // show steps, step title
       component.putClientProperty(  // show steps
              WizardDescriptor.PROP_CONTENT_DISPLAYED
            , Boolean.TRUE);
       component.putClientProperty(
              WizardDescriptor.PROP_CONTENT_NUMBERED
            , Boolean.TRUE);
     }
     return component;
 }

Set properties in readSettings() and storeSettings() methods:

    public void readSettings(WizardDescriptor settings) {
       super.dataModel = settings;
       getComponent().addPropertyChangeListener(this);
    }


    public void storeSettings(WizardDescriptor settings) {
       settings.putProperty(Book.PROP_TITLE,
                getComponent().getBookTitle() );
       settings.putProperty(Book.PROP_AUTHORS,
                getComponent().getAuthors() );
    }

Rewrite getName() method to return name of this step.

In propertyChange() method (of the panel or of inner listener) check validity of entered (just changed) data and propagate this change event from this object.

    public void propertyChange(PropertyChangeEvent evt) {
        boolean oldValid = valid;
        valid = checkValidity(); 
        fireChangeEvent(this, oldValid, valid);
    }

Create setMessage(String) or message(String) method to set error messages by validity check. To show your message in the wizard use descriptor's property:

    model.putProperty(WizardDescriptor.PROP_ERROR_MESSAGE, message);

Implement checkValidity() method and return valid status in isValid() method. To show any message use the setMessage() method. Note this is validation of one step only.

If you will enable to finish the wizard earlier (by no last step) implement the WizardDescriptor.FinishablePanel<DataModelClass> interface and return true in overridden isFinishPanel() method. The wizard enables the Finish button. You can return the true value according to entered data.

Do not forget implement management of PropertyChangeListeners in wizard panel listeners. It can be in superclass.

If you need perform check extra validity when the user clicks Next or Finish button, implement ValidatingPanel<Data> or AsynchronousValidatingPanel<Data> interface (inner interface of the WizardDescriptor class). Than implement the validate() method which is executed in Event Dispatch Thread (EDT) in the first or asynchronously in the second case (for longer checking). If you detect any error throw new WizardValidationException. Then the wizard makes next step unavailable. If you use asynchronous validation all access to GUI components do in the prepareValidation() method where you can store validated data. This method is executed in the EDT whereas the validate() method is executed asynchronously.

If you will enable to skip some steps according to entered data provide implementation of WizardDescriptor.Iterator<Data> interface to set your own way. Standard implementation is ArrayIterator<Data> returning all steps in normal order. For more information see the There's more... section a documentation.

Use this wizard in an action:

public void actionPerformed(ActionEvent e) {
    BookWizardDescriptor descriptor = new BookWizardDescriptor();
    Dialog dialog = DialogDisplayer.getDefault().createDialog(descriptor);
    dialog.setVisible(true);
    dialog.toFront();
    
    if (descriptor.getValue() == WizardDescriptor.FINISH_OPTION) {
        Map<String, Object> props = descriptor.getProperties();
        // Create new book
        Book book = new Book();
        book.setTitle( (String)props.get(Book.PROP_TITLE) );
        String [] authors = (String []) props.get(Book.PROP_AUTHORS );
        if (authors != null)
           for (int i = 0; i < authors.length; i++) {
               book.addAuthor( authors[i] );
           }

        book.setPublisher( (String) props.get(Book.PROP_PUBLISHER) );
        book.setYear( (String) props.get(Book.PROP_YEAR) );
        book.setIsbn( (String) props.get(Book.PROP_ISBN) );
        book.setTranslator( (String) props.get(Book.PROP_TRANSLATOR) );
        book.setDescription( (String) props.get(Book.PROP_DESCRIPTION) );

        String [] tags = (String []) props.get(Book.PROP_TAGS );
        if (tags != null)
            for (int i = 0; i < tags.length; i++) {
                book.addTag( tags[i] );
            }

        WizardTopComponent w = WizardTopComponent.findInstance();
        w.setBook(book);
        w.open();
        w.requestActive();
    }  // if FINISH_OPTION
}

Let's Explain!

When the user activates an action it creates new wizard descriptor. Then show it using DialogDisplayer. The Dialog API asks the wizard descriptor for information and creating first used wizard panel which creates visual panel. User types information into visual panel that is listening its fields changes and re-fires them to listening wizard panel. The wizard panel performs our checkValidity() method and sets valid property. If method isValid() returns true (and method validate() for ValidatingPanel does not fail) the Dialog API enables the Next (or Finish) button allowing the user go to next step. If descriptor.getValue() returns WizardDescriptor.FINISH_OPTION value the action can perform its job – create some object.

Dynamic step sequence

If you use dynamic step sequence choose it in the Wizard wizard. IDE generates the iterator class for you. You can use implement the InstantiatingIterator or AsynchronousInstantiatingIterator interface instead of simple Iterator. See the documentation.

Notes/Tips

Note that the WizardDescriptor extends DialogDescriptor and NotifyDescriptor classes. So you can use their properties, constants and functionality

Have a look at WizardDescriptor's documentation. There are constants you can use to set wizard's properties - PROP_INFO_MESSAGE, PROP_ERROR_MESSAGE, PROP_IMAGE, etc.

If you will provide different helps for the wizard even for some panel return corresponding help context by getHelpCtx() or panel's getHelp() method. You can set dynamically while creating the descriptor by setHelpCtx() method.

Notes/Tips - tutorial

http://platform.netbeans.org/tutorials/nbm-wizard.html


Text and sources were tested under NB 6.8, 6.9, 70, 7.1, 7.2

Navigation

Not logged in. Log in, Register

By use of this website, you agree to the NetBeans Policies and Terms of Use. © 2012, Oracle Corporation and/or its affiliates. Sponsored by Oracle logo