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.
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.
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:
...
CFirstPage::CFirstPage()
: CPropertyPage(CFirstPage::IDD)
{
m_psp.dwFlags |= PSP_DEFAULT|PSP_HIDEHEADER;
}
...
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");
}
...
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.
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:
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);
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).
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
#endif
#ifndef _WIN32_WINNT
#define _WIN32_WINNT 0x0400
#endif
#ifndef _WIN32_WINDOWS
#define _WIN32_WINDOWS 0x0410
#endif
#ifndef _WIN32_IE
#define _WIN32_IE 0x0500
#endif
Finally we came to the result, which were expecting since the beginning (See Fig. 5).
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.