wxWizard

Introduction

In some applications creating a new document or project may require the user to make many choices. These applications can provide wizards to guide the user during the creation process. The wxWizard (wxWidgets: wxWizard Class Reference) class helps developers create such wizards.

We will now proceed with a brief explanation of how wizards are implemented and show a few examples after that.

Description

The wizard dialog

A wizard is a sequence of one or more pages displayed in a dialog. To create a wizard a new instance of the wxWizard (wxWidgets: wxWizard Class Reference) class is created and then pages are added to it.

The pages

Each page is an instance of a class that implements the wxWizardPage (wxWidgets: wxWizardPage Class Reference) abstract class.

The wxWizardPage class defines the pure virtual functions GetNext and GetPrev to determine the order in which the pages are displayed. Since the implementation of these functions is left to the derived classes there is considerable flexibility in the order in which the pages are displayed and for instance they can depend on the information already filled in by the user in the the previous and current pages of the wizard.

For simpler cases where the order of the pages is fixed and known at construction time the wxWizardPageSimple (wxWidgets: wxWizardPageSimple Class Reference) class can be used. For all the other cases a custom class derived from wxWizardPage will have to be written.

Size of the dialog

wxWizard has its own associated sizer class: wxWizardSizer. The sizer will compute the size of the dialog based on the size of its pages which means finding the page with the biggest minimum size and using that as the minimum size of the dialog. The pages are not automatically added to the sizer though. The GetPageAreaSizer method should be used to get the sizer and add the pages to it.

Class diagram

The class diagram in Figure 1 shows the various classes we just described.

Figure 1: The wxWizard classes

Simple Single Page Wizard

Our first example will be a simple wizard with a single page.

The example is a modification of the MinimalApp2 example we presented in the minimal application tutorial. The full source for this example is available from our GitHub repository: wxWidgetsTutorials/Dialogs/WxWizard1.

The source code for the application is shown below. The following changes were made to the MinimalApp2 application:

  1. A menu item has been added to launch the wizard dialog.
  2. A wxTextCtrl has been added to the frame. The text will be updated when the "Finish" button of the wizard is pressed.
  3. The OnRunWizard method handles the menu event. It creates the wxWizard, displays it and updates the wxTextCtrl on successful completion of the wizard.
  4. The wizard contains only one page. It uses the wxWizardPageSimple class. Controls are added to the page in the same way as they would be added to a wxPanel. In this case a wxStaticText and wxTextCtrl were added and laid out using a wxBoxSizer.
  5. The wizard sizer is retrieved using the GetPageAreaSizer method and the page is added to it.
 File: Dialogs/WxWizard1/src/WxWizard1Frame.h
#ifndef _TUTORIALS_WXWIDGETS_WXWIZARD1FRAME_H_
#define _TUTORIALS_WXWIDGETS_WXWIZARD1FRAME_H_

#include <wx/frame.h>
#include <wx/textctrl.h>

class WxWizard1Frame : public wxFrame
{
public:
    WxWizard1Frame(const wxString& title);

private:
    void OnRunWizard(wxCommandEvent& evt);

private:
    wxTextCtrl* m_textCtrl;

    wxDECLARE_EVENT_TABLE();
};

#endif
 File: Dialogs/WxWizard1/src/WxWizard1Frame.cpp
#include "WxWizard1Frame.h"
#include "WindowIDs.h"
#include <wx/menu.h>
#include <wx/wizard.h>
#include <wx/stattext.h>
#include <wx/sizer.h>

WxWizard1Frame::WxWizard1Frame(const wxString& title)
    : wxFrame(NULL, wxID_ANY, title), m_textCtrl(0)
{
    // Create a menu bar with a single item that
    // allows the user to launch the wizard
    wxMenuBar* menuBar = new wxMenuBar;
    wxMenu* menuFile = new wxMenu;
    menuFile->Append(RunWizardMenuID, "&Run Wizard...");
    menuBar->Append(menuFile, "&Wizard");
    SetMenuBar(menuBar);

    // Create a top-level panel to hold all the contents
    // of the frame
    wxPanel* panel = new wxPanel(this, wxID_ANY);

    // Add a wxTextCtrl that will be updated when the
    // wizard is completed (unless it is cancelled)
    m_textCtrl = new wxTextCtrl(panel, wxID_ANY,
        L"", wxDefaultPosition, wxSize(400, 200));

    // Set up the sizer for the panel
    wxBoxSizer* panelSizer = new wxBoxSizer(wxHORIZONTAL);
    panelSizer->Add(m_textCtrl, 1, wxEXPAND);
    panel->SetSizer(panelSizer);

    // Set up the sizer for the frame and resize the frame
    // according to its contents
    wxBoxSizer* topSizer = new wxBoxSizer(wxHORIZONTAL);
    topSizer->Add(panel, 1, wxEXPAND);
    SetSizerAndFit(topSizer);
}

// This event handler will be called when the menu item
// is activated
void WxWizard1Frame::OnRunWizard(wxCommandEvent& evt)
{
    // Create a wxWizard dialog
    wxWizard* wizard = new wxWizard(this, wxID_ANY);

    // Create the first (and only) page of the wizard.
    // It has a wxTextCtrl where the user can type some text.
    wxWizardPageSimple* page1 = new wxWizardPageSimple(wizard);
    wxStaticText* staticText = new wxStaticText(page1, wxID_ANY,
        "Enter some text:");
    wxTextCtrl* textCtrl = new wxTextCtrl(page1, wxID_ANY, "", 
        wxDefaultPosition, wxSize(450, 150));

    // Create a wxBoxSizer to do the layout of the page
    wxBoxSizer* page1Sizer = new wxBoxSizer(wxVERTICAL);
    page1Sizer->Add(staticText, 0, wxEXPAND);
    page1Sizer->Add(textCtrl, 1, wxEXPAND);
    page1->SetSizer(page1Sizer);

    // wxWizard has its own sizer to which the pages need
    // to be added so that it can compute the correct size
    // for the wizard.
    wizard->GetPageAreaSizer()->Add(page1);

    // Run the wizard
    if (wizard->RunWizard(page1))
    {
        // RunWizard will return true if it is completed without
        // cancelling. In this case transfer the text the user
        // entered in the wizard to the main window.
        m_textCtrl->SetValue(textCtrl->GetValue());
    }
    wizard->Destroy();
}

wxBEGIN_EVENT_TABLE(WxWizard1Frame, wxFrame)
    EVT_MENU(RunWizardMenuID, WxWizard1Frame::OnRunWizard)
wxEND_EVENT_TABLE()

The application and its wizard dialog are shown below in Figure 2.

Figure 1: The WxWizard1 Application

The other source files don't contain any significant changes but are shown here for completeness.

 File: Dialogs/WxWizard1/src/WindowIDs.h
#ifndef _TUTORIALS_WXWIDGETS_WINDOWIDS_H_
#define _TUTORIALS_WXWIDGETS_WINDOWIDS_H_

#include <wx/defs.h>

const wxWindowID RunWizardMenuID = wxID_HIGHEST + 1;

#endif
 File: Dialogs/WxWizard1/src/WxWizard1App.h
#ifndef _TUTORIALS_WXWIDGETS_WXWIZARD1APP_H_
#define _TUTORIALS_WXWIDGETS_WXWIZARD1APP_H_

#include <wx/app.h>

class WxWizard1App : public wxApp
{
public:
    virtual bool OnInit();
};

#endif
 File: Dialogs/WxWizard1/src/WxWizard1App.cpp
#include "WxWizard1App.h"
#include "WxWizard1Frame.h"

wxIMPLEMENT_APP(WxWizard1App);

bool WxWizard1App::OnInit()
{
    WxWizard1Frame* frame = new WxWizard1Frame("WxWizard1");
    frame->Show(true);
    return true;
}

Simple Validation

Validators can be used with controls placed on the pages of a wizard.

We modify the previous example by adding a validator that will make sure that the user has entered some text in the wizard. The full source for this example is available from our GitHub repository: wxWidgetsTutorials/Dialogs/WxWizard2.

The modified source code is shown below:

  1. A wxTextValidator was set on the wizard page's wxTextCtrl. It will transfer the contents of the control into the new text variable.
  2. The new variable is used to set the value of m_textCtrl instead of getting the value directly from the control. The old code would still have worked fine but we wanted to show that the text variable could be used instead.
 File: Dialogs/WxWizard2/src/WxWizard2Frame.cpp
#include "WxWizard2Frame.h"
#include "WindowIDs.h"
#include <wx/menu.h>
#include <wx/wizard.h>
#include <wx/stattext.h>
#include <wx/sizer.h>
#include <wx/valtext.h>

WxWizard2Frame::WxWizard2Frame(const wxString& title)
    : wxFrame(NULL, wxID_ANY, title), m_textCtrl(0)
{
    // Create a menu bar with a single item that
    // allows the user to launch the wizard
    wxMenuBar* menuBar = new wxMenuBar;
    wxMenu* menuFile = new wxMenu;
    menuFile->Append(RunWizardMenuID, "&Run Wizard...");
    menuBar->Append(menuFile, "&Wizard");
    SetMenuBar(menuBar);

    // Create a top-level panel to hold all the contents 
    // of the frame
    wxPanel* panel = new wxPanel(this, wxID_ANY);

    // Add a wxTextCtrl that will be updated when the
    // wizard is completed (unless it is cancelled)
    m_textCtrl = new wxTextCtrl(panel, wxID_ANY,
        L"", wxDefaultPosition, wxSize(400, 200));

    // Set up the sizer for the panel
    wxBoxSizer* panelSizer = new wxBoxSizer(wxHORIZONTAL);
    panelSizer->Add(m_textCtrl, 1, wxEXPAND);
    panel->SetSizer(panelSizer);

    // Set up the sizer for the frame and resize the frame
    // according to its contents
    wxBoxSizer* topSizer = new wxBoxSizer(wxHORIZONTAL);
    topSizer->Add(panel, 1, wxEXPAND);
    SetSizerAndFit(topSizer);
}

// This event handler will be called when the menu item
// is activated
void WxWizard2Frame::OnRunWizard(wxCommandEvent& evt)
{
    // Create a wxWizard dialog
    wxWizard* wizard = new wxWizard(this, wxID_ANY);

    // Create the first (and only) page of the wizard.
    // It has a wxTextCtrl where the user can type some text.
    wxWizardPageSimple* page1 = new wxWizardPageSimple(wizard);
    wxStaticText* staticText =  new wxStaticText(page1, wxID_ANY,
        "Enter some text:");
    wxTextCtrl* textCtrl = new wxTextCtrl(page1, wxID_ANY, "",
        wxDefaultPosition, wxSize(450, 150));

    // Set a validator that will check that the textCtrl control is
    // not empty and transfers its contents to the text string.
    wxString text;
    textCtrl->SetValidator(wxTextValidator(wxFILTER_EMPTY, &text));

    // Create a wxBoxSizer to do the layout of the page
    wxBoxSizer* page1Sizer = new wxBoxSizer(wxVERTICAL);
    page1Sizer->Add(staticText, 0, wxEXPAND);
    page1Sizer->Add(textCtrl, 1, wxEXPAND);
    page1->SetSizer(page1Sizer);

    // wxWizard has its own sizer to which the pages need
    // to be added so that it can compute the correct size
    // for the wizard.
    wizard->GetPageAreaSizer()->Add(page1);

    // Run the wizard
    if (wizard->RunWizard(page1))
    {
        // RunWizard will return true if it is completed without
        // cancelling. In this case transfer the text the user
        // entered in the wizard to the main window.
        m_textCtrl->SetValue(text);
    }
    wizard->Destroy();
}

wxBEGIN_EVENT_TABLE(WxWizard2Frame, wxFrame)
    EVT_MENU(RunWizardMenuID, WxWizard2Frame::OnRunWizard)
wxEND_EVENT_TABLE()

With these changes the following error is displayed when the user presses the "Finish" button without entering any text in the wizard.

Figure 2: The WxWizard2 Application

The other files don't have significant changes compared to the WxWizard2 example but are shown here for completeness.

 File: Dialogs/WxWizard2/src/WxWizard2Frame.h
#ifndef _TUTORIALS_WXWIDGETS_WXWIZARD2FRAME_H_
#define _TUTORIALS_WXWIDGETS_WXWIZARD2FRAME_H_

#include <wx/frame.h>
#include <wx/textctrl.h>

class WxWizard2Frame : public wxFrame
{
public:
    WxWizard2Frame(const wxString& title);

private:
    void OnRunWizard(wxCommandEvent& evt);

private:
    wxTextCtrl* m_textCtrl;

    wxDECLARE_EVENT_TABLE();
};

#endif
 File: Dialogs/WxWizard2/src/WindowIDs.h
#ifndef _TUTORIALS_WXWIDGETS_WINDOWIDS_H_
#define _TUTORIALS_WXWIDGETS_WINDOWIDS_H_

#include <wx/defs.h>

const wxWindowID RunWizardMenuID = wxID_HIGHEST + 1;

#endif
 File: Dialogs/WxWizard2/src/WxWizard2App.h
#ifndef _TUTORIALS_WXWIDGETS_WXWIZARD2APP_H_
#define _TUTORIALS_WXWIDGETS_WXWIZARD2APP_H_

#include <wx/app.h>

class WxWizard2App : public wxApp
{
public:
    virtual bool OnInit();
};

#endif
 File: Dialogs/WxWizard2/src/WxWizard2App.cpp
#include "WxWizard2App.h"
#include "WxWizard2Frame.h"

wxIMPLEMENT_APP(WxWizard2App);

bool WxWizard2App::OnInit()
{
    WxWizard2Frame* frame = new WxWizard2Frame("WxWizard2");
    frame->Show(true);
    return true;
}

Further Reading