Creating a Wizard

About Wizards
  • Creating Wizards
  • Implementing Wizards
  • Advanced Wizards

About Wizards

A wizard is a very useful user interface concept. It allows an application to easily guide a user through a complete task. The purpose of a wizard is to collect information on a task in a structured way, while being an organised user interface, and not displaying large numbers of dialogs.

A wizard achieves this by creating multiple page dialogs that the user steps through using the navigation buttons at the bottom of the dialog.

Examples of wizards are the AppWizard in Visual C++ and the Install New Hardware wizard in Windows.

Creating Wizards

In MFC, wizards are very easy to implement. MFC provides the CPropertySheet and CPropertyPage classes for creating property sheets, and wizards are implemented in the same fashion, but with some minor modifications.

The easiest way to implement wizards is to use the Insert Components and Controls option on the Project menu of Visual C++, and select Property Sheet. This displays a wizard, which guides you through the process of creating the wizard. In step 1, select the Wizard options button. Click Next, and then Next again. This then displays a wizard page with the current classes in your program. Select the class you want to call the wizard from and click Next. This wizard page asks you how many pages you want in your wizard.

Select the number you want, from 1 to 9, and click Next. A list box is then displayed with the names of the wizard and its property pages. Click Change to change the selected one, and change the selected information, click OK, then Finish.

Close the insert component dialog box, and open the Resource View, and expand the Dialog item. You should see that more dialogs have been created, depending on the number of pages you wanted in your wizard. They are probably called IDD_PROPPAGE1 or something like that. You can rename them, but you must also change their names in the wizard page header (.h) file.

Implementing Wizards

To call the wizard from your program, open the code for the command to call it from and use:


CWizard().DoModal();

Change CWizard to your wizard's main class name.

Compile and run your program. Select the command that calls your wizard. You should see your wizard appear, but it will look very basic. You can add controls to the dialogs just like other dialogs, and use them just like on dialogs.

To control the navigation buttons at the bottom of the wizard pages, you have to use the features provided by CPropertyPage, using these messages:

    OnSetActive is called when the page becomes active

    OnKillActive is called when the page is no longer active

    OnWizardNext is called when the wizard’s Next button is pressed

    OnWizardBack is called when the wizard’s Back button is pressed

    OnWizardFinish is called when the wizard’s Finish button is pressed

The OnSetActive() message is the message that plays the main role in adding the button functionality. Once a page is notified that it is active, it must then call SetWizardButtons() in the parent CWizardDialog object to change the button states.

Use ClassWizard to add a OnSetActive() handler to each CPage class in your wizard.

In the first page's OnSetActive() function, add this code:


CWizard* pParent = (CWizard*)GetParent();
ASSERT_KINDOF(CWizard, pParent);
pParent->SetWizardButtons(PSWIZB_NEXT);

This code gets a pointer to the parent CWizard and calls SetWizardButtons() to only enable the Next button, and disable the Back button as it is the first wizard page.

Replace CWizard with your wizard’s main class name, and add an include to the wizard header file.

In the second page's OnSetActive() function, add this code:


CWizard* pParent = (CWizard*)GetParent();
ASSERT_KINDOF(CWizard, pParent);
pParent->SetWizardButtons(PSWIZB_BACK | PSWIZB_NEXT);

This code gets a pointer to the parent CWizard and calls SetWizardButtons() to enable both the Next button and the Back button.

 In the third page's OnSetActive() function, add this code:


CWizard* pParent = (CWizard*)GetParent();
ASSERT_KINDOF(CWizard, pParent);
pParent->SetWizardButtons(PSWIZB_BACK | PSWIZB_FINISH);

 This code gets a pointer to the parent CWizard and calls SetWizardButtons() to enable the Next button, and to display the Finish button instead of the Next button as it is the last page.

 The SetWizardButtons() function can have the following flags:

    SetWizardButtons(PSWIZB_NEXT) Disables the Back button and enables the Next button.

    SetWizardButtons(PSWIZB_BACK) Disables the Next button and enables the Back button.

    SetWizardButtons(PSWIZB_FINISH) Disables the Back button, and replaces the Next button with an enabled Finish button

    SetWizardButtons(PSWIZB_BACK | PSWIZB_NEXT) Enables both the Back and Next Buttons

    SetWizardButtons(PSWIZB_BACK | PSWIZB_FINISH) Enables both the Back and Finish Buttons

    SetWizardButtons(PSWIZB_BACK | PSWIZB_DISABLEDFINISH) Enables the Back and displays a disabled Finish button instead of the Next button.

The PSWIZB_NEXT and PSWIZB_FINISH flags cannot be used together, because they both use the same button position. See Advanced Wizards to find out how to do this.

To find out what button was pressed in the wizard when you display the wizard, use:


If( CWizardDialog().DoModal() == ID_WIZFINISH ) { // Your Code }

This code will process the code placed where // Your Code is if the Finish button is pressed on your wizard.

The titles of the dialogs which make up your wizard will be used as the page titles when the wizard is run.
For example, if the dialog of the wizard's first step has a title "Wizard Step 1", then your wizard's title for the first wizard page will also be "Wizard Step 1".

Advanced Wizards

I have often been asked how to display three navigation buttons, (Back, Next and Finish) on a wizard as Microsoft has done in the AppWizard. CPropertySheet only allows two of the buttons to be displayed at a time, but really they are all there, but one is hidden under another. The only way I have found to overcome this problem is to move the buttons on the OnInitDialog of the first wizard page using this code:



BOOL CWizardPage::OnInitDialog(void)
{

    CPropertyPage::OnInitDialog();

    ((CPropertySheet*) GetParent())->SetWizardButtons(PSWIZB_NEXT|PSWIZB_FINISH);

    CWnd *buttonBack = GetParent()->GetDlgItem(ID_WIZBACK);
    CWnd *buttonNext = GetParent()->GetDlgItem(ID_WIZNEXT);

    // Now we have to move the buttons around

    CRect BackButton;

    buttonBack->GetWindowRect(&BackButton);

    ScreenToClient(&BackButton);

    buttonBack->SetWindowPos (NULL, BackButton.left - BackButton.Width() - 5, BackButton.top, BackButton.Width(),  BackButton.Height(),
        SWP_NOACTIVATE || SWP_NOSIZE || SWP_NOZORDER || SWP_SHOWWINDOW);

    buttonNext->SetWindowPos(buttonBack, BackButton.left, BackButton.top,
		BackButton.Width(), BackButton.Height(), SWP_NOACTIVATE || SWP_SHOWWINDOW);

	buttonBack->EnableWindow(TRUE);
	buttonBack->ShowWindow(SW_SHOW);

    buttonNext->EnableWindow(TRUE);
    buttonNext->ShowWindow(SW_SHOW);


    return(TRUE);
}

Date Posted: 05/09/98