Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Conquering Wizard97

0.00/5 (No votes)
9 Oct 2002 3  
This article describes the problems one can meet during using Wizard 97 with MFC and the way to resolve them

Introduction

Everybody knows what a Wizard is. However, for the sake of those who don't, one can find a short introduction below that gives the answer to the mysterious question: "What is a Wizard?" Those of you who feel that he/she knows the answer can skip to the next section.

A Wizard is one of the known GUI elements that guides a user, step by step, through an entire process. It consists of the series of dialogs that run, one by one, inside the frame window. A Wizard has buttons to navigate through the pages forward and back and buttons that allow the user to commit or cancel the process.

From the Win32 API programming point of view, a Wizard is the PropertySheet control that contains several pages, which are actually the PropertyPage controls. So basically, a Wizard is much the same as a PropertySheet control, but it allows the user to access only the one page at time. That's why its implementation in the Win32 API is the same as for a PropertySheet control. The only thing that has to be done is to define a flag PSH_WIZARD during the creation of a PropertySheet control.

A typical example of a Wizard is displayed on the Fig. 1.

The typical view of Wizard page

Fig. 1. The typical view of Wizard page

Together with a new style of Internet Explorer, Microsoft has presented a new fashion in Wizard design - the so-called Wizard 97. Since version 5.80 the common control library supports new stylized elements for the Wizard such as a header with title and subtitle, and watermarks (see the Fig. 2). To enable such features, a programmer should just provide a PSH_WIZARD97 flag instead of PSH_WIZARD during creation of a PropertySheet control.

An example of the Wizard 97 page with header

Fig. 2. An example of the Wizard 97 page with header

It's a shame to just pass by such a nice feature of the new common control library, when the implementation is so easy.

Research

First of all, be aware that one should never try to implement something, if it has already been done by someone else and already exists somewhere. Life is too short to repeat the same mistakes and rewriting the code. One must not "reinvent the wheel". So the first step to be done is to research existing examples for using Wizard 97.

The main sources for that in our case are

If one were ever trying to look for some examples about using Wizard 97 one would be astonished with the results of such exploration. There are unexpectedly few of them! The most useful links are

The analysis of these samples can make up a set of conclusions:

  • The Microsoft example from MSDN is using pure Win32 API with all its implied consequences. Such as, to create a dialog and implement its behaviour one has to write a dialog handler procedure instead of using the MFC way - with events and so forth.
  • The example from Codeproject site is overloaded with resizable facilities and it is outdated, because it uses obsolete pre-MFC 7.0 classes CPropertySheetEx and CPropertyPageEx. It is written in MSDN that now all functionality of these classes has been included in the former based classes - CPropertySheet and CPropertyPage respectively.
  • The example from Codeguru site isn't using CPropertySheet and CPropertyPage at all. It's based on the CDialog classes and the author has implemented all Wizard functionality by himself.

So, it seems like we have to "reinvent the wheel" after all, although it has almost certainly been already done by someone else. The evidence of this is all around us. There we go! Let's create our own example for Wizard 97 using MFC 7 class library.

Warming Up

The sky is bright and the road is clear. In MSDN, it is written that all functionality of Wizard 97 is inside two nice basic wrappers CPropertySheet and CPropertyPage. So all we have to do is to write just a few lines of code.

Start a new project, let say Wiz97, which is a dialog-based application to keep the things simple. The wizard we'll create contains two pages. First, one for testing the watermarks feature (introduction page), and the second one to use a header with a title and subtitle.

There are two dialogs in the resource, the two classes are based on CPropertyPage, and the one class that inherits from CPropertySheet. Also prepare two bitmaps - a small icon for the header and big picture for the watermark of first page.

Following the example from the MSDN, the constructors of the classes should be modified as shown below:

...
// Modified constructor of the first page (with watermark)

CFirstPage::CFirstPage()
    : CPropertyPage(CFirstPage::IDD)
{
    m_psp.dwFlags |= PSP_DEFAULT|PSP_HIDEHEADER;
}

...
// Modified constructor of the second page (with banner, title and subtitle)

CSecondPage::CSecondPage()
    : CPropertyPage(CSecondPage::IDD)
{
    m_psp.dwFlags |= PSP_DEFAULT|PSP_USEHEADERTITLE|PSP_USEHEADERSUBTITLE;
    m_psp.pszHeaderTitle = _T("Title");
    m_psp.pszHeaderSubTitle = _T("And subtitle");
}

...
// Modified constructor of the property sheet class

CWiz97::CWiz97(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage)
    :CPropertySheet(nIDCaption, pParentWnd, iSelectPage)
{
    AddPage(&m_pgFirst);
    AddPage(&m_pgSecond);

    m_psh.dwFlags |= PSH_WIZARD97|PSH_WATERMARK|PSH_HEADER;
    m_psh.pszbmWatermark = MAKEINTRESOURCE(IDB_WATERMARK);
    m_psh.pszbmHeader = MAKEINTRESOURCE(IDB_BANNER_ICON);
}

To start a Wizard we just need to add couple lines of code - that's the power of MFC we were expecting:

#include "Wiz97.h"


...
void CWiz97_1Dlg::OnBnClickedButton1()
{
    CWiz97 dlg(_T("Wizard 97"));
    dlg.DoModal();
}

So we are ready for the first bunch of errors. Compile, run and first result is here.

The first page with watermark

The second page with header

Fig. 3. The first page was supposed to contain watermark, the second header expected an icon

There is something wrong here! Of course, the watermark and the header with small image we expected to see are absent.

The first victory

Walking through the source code of the MSDN example and analyzing it can bring us an explanation of first defeat and the way to resolve the problem. The solution is hiding under the one line of code:

// Modified constructor of the property sheet class

CWiz97::CWiz97(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage)
    :CPropertySheet(nIDCaption, pParentWnd, iSelectPage)
{
    SetWizardMode();

    AddPage(&m_pgFirst);
    AddPage(&m_pgSecond);

    m_psh.dwFlags |= PSH_WIZARD97|PSH_WATERMARK|PSH_HEADER;
    m_psh.pszbmWatermark = MAKEINTRESOURCE(IDB_WATERMARK);
    m_psh.pszbmHeader = MAKEINTRESOURCE(IDB_BANNER_ICON);
    // Next line is very important

    m_psh.hInstance = AfxGetInstanceHandle(); 
}

It was easy to discover the problem, although one could expect that the instance handler will be preset in the constructor of the MFC class. So let's compile and run again.

Don't stress yourself with a new version of our program. We shall fix it a bit later, but right now just take a look at the new view (Fig. 4).

The first page with watermark

The second page with header

Fig. 4. A new astonishing view of the Wizard

The positive point is that we can see pictures we've created. But the whole view of our Wizard is still different from the MSDN example.

The final battle

If you have already regained consciousness, then we can continue.

It can be fairly hard to discover the roots of the problem. What could we have done wrong? It can take a while to figure out the problem and fix it.

Don't try to search in Internet - it's already been done, you won't find some tips or tricks for this problem. Don't try to check a source code either, because our mistakes are not missing flags. I tested a lot of combinations of different flags, attributes, functions, but the final results of these efforts, in contrast to MSDN example, resulted in the difference between Fig. 3 and 4.

But nothing is eternal in the Universe. The reason was found. This problem occurs because of the default settings of the project wizard we have used to create the application, the settings for the IE version in the stdafx.h file. Here is the change one should make to get the Wizard to behave as it is supposed to:

#ifndef WINVER
#define WINVER 0x0400          // Default value is 0x0400

#endif

#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400    // Default value is 0x0400

#endif

#ifndef _WIN32_WINDOWS
#define _WIN32_WINDOWS 0x0410  // Default value is 0x0410

#endif

#ifndef _WIN32_IE
#define _WIN32_IE 0x0500       // Default value is 0x0400

#endif

Finally we came to the result, which were expecting since the beginning (See Fig. 5).

The first page with watermark

The second page with header

Fig. 5. The final result

You can find more explicit information about constants we have modified under the next link http://msdn.microsoft.com/library/en-us/sdkintro/sdkintro/using_the_sdk_headers.asp.

So, concluding the whole process we just have done, one would find that it's really easy to implement a Wizard 97 by using MFC classes, which are fairly good wrappers for that. But to use a power of MFC, one must be aware of some pitfalls of this approach.

I hope this article was useful for you and you'll not have to repeat my mistakes. Thank you all who had enough passion to persevere to the end.

Special thanks to Parland Johnstone for his support.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here