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



Comments

  • How Can Enable the Next Button if a radio button is Clicked

    Posted by Legacy on 06/15/2003 12:00am

    Originally posted by: Mrad James

    Hi, Iwanted To Know How Can i enable the next button if he detect that a radio button is clicked.
    Please Help.
    Thanks,
    Bye

    Reply
  • Dynamic Wizard

    Posted by Legacy on 06/06/2003 12:00am

    Originally posted by: shashidhar

    How to write a dynamic wizard. Which can read data(The number of controls and the control types etc.) from a flat file or database and display on respective page. Is there any tool available out side.

    Shashi

    Reply
  • Sharing data between pages

    Posted by Legacy on 02/18/2002 12:00am

    Originally posted by: Bruce

    Just get a pointer back to the PropertySheet, like this:
    
    

    CPropertySheet* pParent = (CPropertySheet*)GetParent();

    and use that pointer to access the individual pages (and contols on the pages).

    - Bruce

    Reply
  • Accessing page data from other pages

    Posted by Legacy on 01/04/2002 12:00am

    Originally posted by: Geoff Henry

    I sort of stumbled upon this solution to the problem of sharing data between property sheet pages, particularly for making a summary page. Basically, you give your page object a pointer to the sheet it is attached to - you can then access data for other pages through member vars.

    I was doing this in a wizard, using classes derived from CPropertySheetEx and CPropertyPageEx, but it should work for all sheet/page applications (I think..).

    In the page class header, add the following...
    An identifier for the sheet class before the start of the page class definition, eg:

    class CWizardSheet;

    A public member variable to hold a pointer to your sheet class, eg:

    CWizardSheet *m_pWizSheet;

    and finally declare your sheet class as friend:

    friend class CWizardSheet;

    (you should also probably be good and initialise the pointer to NULL in the class constructor)

    When you construct the sheet object to be displayed, set the page class member variable for each page you want to be given access:

    CWizardSheet dlg((LPCTSTR)"Title", NULL, 0, NULL, NULL,NULL);
    dlg.m_FinishPage.m_pWizSheet = &dlg;
    dlg.DoModal();

    if you store your property page data in member variables (remembering to do an UpdateData(true) when you leave each page), you can then access them from other pages with something like:

    CString AString = m_pWizSheet->m_APage.m_AString;

    Hope this all makes sense

    Reply
  • OnWizardFinish(): DDV doesn't set Focus to invalid control

    Posted by Legacy on 07/19/2001 12:00am

    Originally posted by: Thomas Brammer

    MFC's DDV mechanism works fine with the "Previous" and "Next" buttons: If a control contains an invalid value a message appears and the focus is set to this control.
    
    

    This works through the following call stack:
    CPropertyPage::OnKillActive()
    CWnd::UpdateData(int 1)
    CMyPropPage::DoDataExchange()
    --> sets focus to invalid control

    But this doesn't work when you hit "Apply" with an invalid value in a control. Normaly, the PropertySheet just terminates. I tried:

    BOOL CMyPropPage::OnWizardFinish()
    {
    if (!UpdateData())
    return 0;
    return CPropertyPage::OnWizardFinish();
    }

    This prevents the Sheet from terminating with invalid values. You also get the message, but the focus isn't set.

    Does anyone know how to set the focus?

    Reply
  • How to use CPropertySheetEx in Win98?

    Posted by Legacy on 11/24/2000 12:00am

    Originally posted by: Ray Yang

    We found that this class will cause some errors in win 98. This error happens on some VGA drivers such as trd_3d.drv and sis300om.drv
    Who found this problem too?

    Reply
  • OnWizardNext return values ???

    Posted by Legacy on 10/25/2000 12:00am

    Originally posted by: John Dyer

    Returning -1 and zero make sense to me, it is the dialog identifier that I am having problems with. I first thought they wanted the resource ID but this causes the program to blow up. Looking around to see exactly what the dialog identifier is defined as I find nothing.

    Has anyone got this to work ? What have you returned ?

    Thanks,
    John

    Reply
  • Hiding the help button, adding a finish button

    Posted by Legacy on 08/09/2000 12:00am

    Originally posted by: Jeff Biery

    CPSSamp::CPSSamp(CWnd* pWndParent)
    
    : CPropertySheet(IDS_PROPSHT_CAPTION, pWndParent)
    {
    m_psh.dwFlags &= ~PSH_HASHELP;

    m_Page1.m_psp.dwFlags &= ~PSP_HASHELP;
    m_Page2.m_psp.dwFlags &= ~PSP_HASHELP;
    m_Page3.m_psp.dwFlags &= ~PSP_HASHELP;
    m_Page4.m_psp.dwFlags &= ~PSP_HASHELP;
    // must be done on all pages or the
    // help button will be disabled but visible

    AddPage(&m_Page1);
    AddPage(&m_Page2);
    AddPage(&m_Page3);
    AddPage(&m_Page4);

    SetWizardMode();

    m_psh.dwFlags |= PSH_WIZARDHASFINISH; // add finish button
    }

    Reply
  • How can I exchange data between the pages of a 5 pages Wizard

    Posted by Legacy on 07/24/2000 12:00am

    Originally posted by: Nizar

    Hi,
    I have created a wizard of 5 pages using the MFC AppWizard(exe). However, I do not know how to exchange data between the pages of the wizard. I tried the Foreign Class and Foreign Variables to use a certain variable in a certain class, however, I'm getting an error message on runtime.
    Please, any hint will be appreciable and useful.
    Thanks in advance.

    Reply
  • How do I use the last page of a wizard to summarize data from previous pages?

    Posted by Legacy on 04/23/2000 12:00am

    Originally posted by: Clayton Roe

    Let us say that I have a four-page wizard. Each of the first three pages has an edit box for accepting a number from the user. On the last page I have a read-only edit box or a static text control to display the sum of the entries to the three edit boxes on the previous pages. I also have on the last page, a push button labeled "Sum", which when clicked should cause the sum to be displayed without terminating the wizard. Can anyone tell me how to implement this? Of course, the actual situation I am confronting is a lot more complex, but if I am told how to implement the simplified version as described above, I will be able to handle the actual case.

    Thanks to anyone who can help me. I am using Visual C++ 6.0.

    Clayton Roe

    • Last page to summarize data from other pages.

      Posted by MeghaKhosla on 05/26/2005 02:58am

      I tried this by declaring a staic variable in PropertySheet class and gather data from each page and then finally display on the last page by transfering the data from variable to control in the OnInitDialog()function of the last page .

      Reply
    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • On-demand Event Event Date: September 10, 2014 Modern mobile applications connect systems-of-engagement (mobile apps) with systems-of-record (traditional IT) to deliver new and innovative business value. But the lifecycle for development of mobile apps is also new and different. Emerging trends in mobile development call for faster delivery of incremental features, coupled with feedback from the users of the app "in the wild." This loop of continuous delivery and continuous feedback is how the best mobile …

  • Java developers know that testing code changes can be a huge pain, and waiting for an application to redeploy after a code fix can take an eternity. Wouldn't it be great if you could see your code changes immediately, fine-tune, debug, explore and deploy code without waiting for ages? In this white paper, find out how that's possible with a Java plugin that drastically changes the way you develop, test and run Java applications. Discover the advantages of this plugin, and the changes you can expect to see …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds