Tutorial: Creating an Installation Wizard

Michael Rudolf

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

  1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

  2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

  3. Neither the name Michael Rudolf nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


Table of Contents

Introduction
Writing the skeleton
The Welcome step
The License step
The Location step
The Finish step
Making it work

Abstract

This tutorial will guide you through the process of creating a simple installation wizard using jwizz. Each step will be accompagnied by source code and screenshots.

Introduction

Before you start coding please verify that you have a Java Development Kit (version 1.4 or greater) installed. Sun's JDK can be obtained at http://java.sun.com/j2se/1.4/

Furthermore make sure that you have the latest version of the jwizz library available. If not, download it at http://javaprog.net/jwizz/

For the compiler and the virtual machine to know about jwizz the library has to be added to the classpath. If you don't use an IDE with configurable build and execution environments you can pass command line options to the executables:

java -classpath jwizz.jar InstallWizard

Writing the skeleton

The main class responsible for setting up and starting the installation wizard will be called InstallationWizard. Its task is it to create a model containing the wizard steps, initialize the wizard and position it on the screen.

import net.javaprog.ui.wizard.*;

public class InstallWizard {
    public static void main(String[] args) {
        DataModel data = new DataModel();
        
        WizardModel model = new DefaultWizardModel(new Step[]{
            new WelcomeStep(),
            new LicenseStep(),
            new LocationStep(data),
            new FinishStep()
        });
        
        Wizard wizard = new Wizard(model, "Installation Wizard",
            new ImageIcon("computer.gif"));
        
        wizard.pack();
        wizard.setLocationRelativeTo(null);
        wizard.setVisible(true);
    }
}

The DataModel instance can later be used to query the installation location selected by the user.

The Welcome step

The first step shown to the user when the wizard ist started displays an explanatory welcome message. The super call in the constructor passes the step name and description to the AbstractStep superclass which implements most of the methods in the Step interface. While the createComponent method returns the JComponent instance actually being displayed, the prepareRendering method is not needed here and is therefore empty. Be aware that we will leave out the required import statements in the listings below.

import javax.swing.*;
import net.javaprog.ui.wizard.*;

class WelcomeStep extends AbstractStep {
    public WelcomeStep() {
        super("Welcome", "This is the Installation Wizard");
    }
    
    protected JComponent createComponent() {
        JPanel stepComponent = new JPanel();
        stepComponent.add(
            new JLabel("<html>This wizard will guide you through the installation process.<p>"
                + "You can navigate through the steps using the buttons below.</html>"));
        return stepComponent;
    }

    public void prepareRendering() {}
}

The License step

This step covers an elementary part of installation procedures: it shows a license and requires the user's agreement before continuing. The license text is displayed inside a text area with scroll bars. Below that are two radio buttons for approval respectively disagreement of the license. The buttons are grouped so that only one can be selected at a time.

class LicenseStep extends AbstractStep {
    protected JTextArea licenseArea = new JTextArea();
    
    public LicenseStep() {
        super("License Agreement", "Please read the license carefully");
    }
    
    protected JComponent createComponent() {
        JPanel stepComponent = new JPanel(new BorderLayout(0, 10));
        stepComponent.add(new JScrollPane(licenseArea));
        
        final JRadioButton noRadioButton = new JRadioButton(
                "No, I don't accept the license terms", true);
        final JRadioButton yesRadioButton = new JRadioButton(
                "Yes, I accept the license terms");
        
        ButtonGroup group = new ButtonGroup();
        group.add(noRadioButton);
        group.add(yesRadioButton);
                
        JPanel choicePanel = new JPanel(new GridLayout(2, 1, 0, 5));
        choicePanel.add(noRadioButton);
        choicePanel.add(yesRadioButton);
        stepComponent.add(choicePanel, BorderLayout.SOUTH);
        return stepComponent;
    }
}

In order to be able to react to the button clicks we register an action listener with them. The wizard's "Next" button is enabled and disabled accordingly.

ActionListener buttonListener = new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        setCanGoNext(e.getSource() == yesRadioButton);
    }
};
noRadioButton.addActionListener(buttonListener);
yesRadioButton.addActionListener(buttonListener);

Finally, because the disapproval button is selected by default, the wizard's "Next" button has to be disabled. In the prepareRendering method we read in the license from a file line by line and fill the text area with. This should actually be done in the createComponent method because that is only called once in contrast to the prepareRendering method which is invoked each time the step is displayed (i.e. also when the user navigates backwards). However, for the sake of readability we put it here.

public void prepareRendering() {
    try {
        BufferedReader reader = new BufferedReader(
            new FileReader("LICENSE"));
        String line;
        while ((line = reader.readLine()) != null) {
            licenseArea.append(line + "\r\n");
        }
        reader.close();
    } catch (IOException ioe) {
        ioe.printStackTrace();
    }
    setCanGoNext(false);
}

The Location step

Choosing an installation location is a common task for users. Therefore we show an input field together with a browse button. Due to the BoxLayout used we need to specify a maximum height for the panel.

class LocationStep extends AbstractStep {
    protected DataModel data;
    protected JTextField fileTextField = new JTextField();
    protected JFileChooser fc = new JFileChooser();
    
    public LocationStep(DataModel data) {
        super("Choose location", "Please choose the installation location");
        this.data = data;
    }
    
    protected JComponent createComponent() {
        fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
        final JPanel stepComponent = new JPanel();
        stepComponent.setLayout(new BoxLayout(stepComponent, BoxLayout.Y_AXIS));
        stepComponent.add(Box.createVerticalGlue());
        JPanel inputPanel = new JPanel(new BorderLayout(5, 0));
        inputPanel.add(new JLabel("Directory:"), BorderLayout.WEST);
        inputPanel.add(fileTextField);
        JButton browseButton = new JButton("Browse...");
        inputPanel.add(browseButton, BorderLayout.EAST);
        inputPanel.setMaximumSize(new Dimension(Integer.MAX_VALUE, 
                inputPanel.getPreferredSize().height));
        stepComponent.add(inputPanel);
        stepComponent.add(Box.createVerticalGlue());
        return stepComponent;
    }
    
    public void prepareRendering() {}
}

When clicking the browse button a directory chooser dialog should be displayed and the text field has to be updated to show the user's selection.

browseButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        if (fc.showOpenDialog(stepComponent) == JFileChooser.APPROVE_OPTION) {
            fileTextField.setText(fc.getSelectedFile().getPath());
        }
    }
});

In order to work with the chosen directory afterwards we attach the text field to an instance of the DataModel class using an identifyer string. The default implementation of the DataLookup interface only requires an object and a method to invoke it together with the parameter array.

Method method = null;
try {
    method = fileTextField.getClass().getMethod("getText", null);
} catch(NoSuchMethodException nsme) {}
data.registerDataLookup("location", 
        new DefaultDataLookup(fileTextField, method, null));

The Finish step

The last step in the sequence is similar to the welcome screen: a simple text is shown informing the user of the further proceedings. Additionally we enable the "Finish" button.

class FinishStep extends AbstractStep {
    public FinishStep() {
        super("Finish", "The installation will now be started");
    }
    
    protected JComponent createComponent() {
        JPanel stepComponent = new JPanel();
        stepComponent.add(
            new JLabel("<html>The installation wizard will now copy the necessary files<p>"
                + "to your hard drive. Please click \"Finish\".</html>"));
        return stepComponent;
    }
    
    public void prepareRendering() {
        setCanFinish(true);
    }
}

Making it work

You might have already asked yourself where the actual installation work, such as creating directories, copying files, etc., is done: whenever the wizard is finished or canceled, all instances of WizardModelListener registered with the wizard's model are notified. Thus, in order to perform the installation you have to create a listener and attach it to the model. In our case only the method wizardFinished needs to be implemented.

WizardModel model = ...;

model.addWizardModelListener(new WizardModelListener() {
    public void wizardFinished(WizardModelEvent e) {
        
    }

    public void wizardCanceled(WizardModelEvent e) {}
    public void stepShown(WizardModelEvent e) {}
    public void wizardModelChanged(WizardModelEvent e) {}
});

Wizard wizard = ...;