Background

A year or so ago I implemented a wizard for the calibration of positions
of arrays of acoustic beacons on the seabed, as part of our Pharos
acoustic positioning application.

After doing a little research, I proposed that we use the (then relatively
new) Wizard97 style to implement it, to give it more visual impact. To this
end, I implemented MFC classes for a wizard sheet (CWizard) and
page (CWizardPage). Although most of the pages were conventional,
one of them contained a Chart view (with an associated document) within a frame
window, complete with two toolbars:

This page introduced a problem. To make it useable it really needed to be bigger
- or, even better, resizeable. Unfortunately, at the time we had higher priorities
so resizing stayed firmly on the "to do" list.

The breakthrough came with the publication of Herbert Menke's CResizeCtrl
on Code Project, which we started using to support resizeable dialogs and property
pages elsewhere within the project. In due course, the inevitable happened, and
we implemented another Wizard.

Unfortunately, when we integrated CResizeCtrl into the new wizard,
the Wizard97 style appeared to be incompatible with resizing - the watermark
and header bitmaps failed to paint correctly. As a result, the new wizard was
implemented with the Wizard97 features disabled (we reasoned that we could always
paint them ourselves if we couldn't find a solution later).

I've now finally got around to doing just that. Using code from Joerg Koenig's
CBitmapDialog
class, CWizard now paints the header bitmaps, and CWizardPage
the watermarks. I had to make some minor changes to CResizeCtrl
to accommodate wizard pages with header bitmaps, but other than that it worked
pretty much first time.

To demonstrate these classes I've incorporated them into the Microsoft Wizard97
sample (renamed as CNGWizard and CNGWizardPage respectively).

Usage

Before implementing your own Wizard97 classes, it's worth reading the
Wizard97 Specification in the Visual C++
Help (its also available on the MSDN website), which contains useful guidelines
for implementing wizards using the Wizard97 style.

Add an OnInitDialog() override to each page and add the page's
controls to the resizer (see Herbert Menke's CResizeCtrl
article for full details on how to do this)

Set CNGWizard::m_bResizeable in the constructor of your wizard
sheet class if you want your wizard to be resizeable

Set CNGWizard::m_bShowFinishAlways in the constructor of your
wizard sheet class if you want the Next and Finish buttons in
the wizard to both be visible at all times (this can be useful in some wizards)

Initialise your wizard sheet by specifying the watermark and header
bitmaps, together with their display styles (centred, tiled
or stretched for the watermark, and tiled or stretched
for the header)

Create your pages and add them to the wizard. CNGWizard will
configure the pages for you as they are added.

Note that each page will be (briefly) activated as it is added to allow the
resizer to initialise properly. A consequence of this is that OnInitDialog()
and OnSetActive() overrides in your pages will be called earlier
than they may expect. To prevent this causing problems, CNGWizardPage
has an IsInitialising() method which derived classes can use
to determine whether this is the case:

System Requirements and Limitations

Because these classes are based upon the CPropertySheetEx and
CPropertyPageEx classes introduced with Visual C++ 6.0, I'm afraid
this code will not work with Visual C++ 5.0.

On older systems (pre-WinMe/2000) the use of Wizard97 may require an upgrade
to the common controls library (comctl32.dll), since version 5.80 or
later is required to use Wizard97 controls. If your system requires this update,
you can download an installer (50comupd.exe) for the update from the Microsoft
web site at http://www.microsoft.com/msdownload/ieplatform/ie/comctrlx86.asp.

According to the Microsoft documentation on Wizard97, Wizard97 controls
will not work correctly on Windows 95 systems. However, when
I ran this sample on a Win95 machine the only problem I could see was that
static controls on pages which had watermarks didn't display correctly (presumably
because transparency doesn't work for static controls on Win95). Apart from
that minor problem, the wizard worked just fine and was perfectly legible.

In order to correctly repaint the watermark and header bitmaps when the
wizard is resized the windows must be created with theCS_HREDRAW
and CS_VREDRAW window class styles. Although this should be done
by registering a custom window class, CNGWizard::OnInitDialog()
currently just modifies the global style using the Platform SDK function SetClassLong().

Finally...

...a bit of a plug: if you want to see an example of these classes in action,
check out my Resource ID Organiser
Add-In for Visual C++ which uses these classes to implement a Wizard for
renumbering resource symbols.

Share

About the Author

I haven't always written software for a living. When I graduated from Surrey University in 1989, it was with an Electronic Engineering degree, but unfortunately that never really gave me the opportunity to do anything particularly interesting (with the possible exception of designing Darth Vader's Codpiece * for the UK Army in 1990).

* Also known as the Standard Army Bootswitch. But that's another story...

Since the opportunity arose to lead a software team developing C++ software for Avionic Test Systems in 1996, I've not looked back. More recently I've been involved in the development of subsea acoustic navigation systems, digital TV broadcast systems, port security/tracking systems, and most recently software development tools with my own company, Riverblade Ltd.

One of my personal specialities is IDE plug-in development. ResOrg was my first attempt at a plug-in, but my day to day work is with Visual Lint, an interactive code analysis tool environment with works within the Visual Studio and Eclipse IDEs or on build servers.

I love lots of things, but particularly music, photography and anything connected with history or engineering. I despise ignorant, intolerant and obstructive people - and it shows...I can be a bolshy cow if you wind me up the wrong way...

I'm currently based 15 minutes walk from the beach in Bournemouth on the south coast of England. Since I moved here I've grown to love the place - even if it is full of grockles in Summer!

The OnWizardFinish() gets called in the CNGWizardPage, but if I add the OnWizardNext() or OnWizardBack() to this class, they are not called.

Has anyone else had such experiences?

Here is the reason for wanting them.

I do not know how to tell from within the UpdateData of a class derived from CNGWizardPage if the UpdateData is being called because of Wizard BACK or Wizard NEXT. A property page's UpdateData gets called coming and going - forwards or backwards. I do not want to necessarily force a DDX failure if the user is going BACKWARDS in the Wizard, only want to prevent them from moving FORWARDS if the data is invalid.

I could also care less about data validation if they press the Cancel button or otherwise 'escape' from the property sheet.

The memories of a man in his old age are the deeds of a man in his prime.

Hi Blake. I hope I can help you unravel a bit of the puzzle, at least!

Firstly, I've not had any problems overriding either OnWizardBack() or OnWizardNext(). The only things I can think of that might cause overrides not to be called are mismatches in their signatures (check the base class definitions) or an incomplete rebuild.

Both methods are called from CPropertyPage::OnNotify() in response to the PSN_WIZBACK and PSN_WIZNEXT, so you should be able to trace the calls through from there.

To prevent UpdateData() from being called you'll have to override CPropertyPage::OnKillActive(), which will otherwise call it whenever the page goes inactive (and regardless of which button was pressed):

Note that you'll also want to override CPropertyPage::OnSetActive() to prevent it from transfering the contents of the page's member vars to the screen controls when going forwards - but only if the page has been active before (and therefore the user has pressed "Back").

I triply checked the signatures, and the ones in your class matched the base MFC class exactly, and the ones in my derived class matched as well (even copying lines exactly from MFC's sources !!!). I also did many full rebuilds of the project. In all cases, the OnNotify() of MFC went directly to the MFC class members.

I decided to override OnNotify() myself in your class, and I track which button was pressed. In my derived class' data exchange handlers, I just examine the button tracking variable and decided whether to save the data or not. In any case, I wanted the data to go back and forth to the controls from the pages' member data, just not necessarily from the pages' member data to the data store.

Therefore, the idea was not to avoid the UpdateData entirely, but rather to know from WIHTIN one of the data exchange handlers if the wizard was going forward or backward. I did not want to necessarily force data validation on a control's data if the user was going backwards.

Blake Miller wrote:Thanks for the suggestions and the time spent on the analysis.

Anytime.

Blake Miller wrote:I triply checked the signatures, and the ones in your class matched the base MFC class exactly, and the ones in my derived class matched as well (even copying lines exactly from MFC's sources !!!). I also did many full rebuilds of the project. In all cases, the OnNotify() of MFC went directly to the MFC class members.

That is really, really, odd. I can't think what else to suggest I'm afraid.

Blake Miller wrote:I decided to override OnNotify() myself in your class, and I track which button was pressed. In my derived class' data exchange handlers, I just examine the button tracking variable and decided whether to save the data or not. In any case, I wanted the data to go back and forth to the controls from the pages' member data, just not necessarily from the pages' member data to the data store.

Again i am disturbing you. i am using your wizard97 style. In the First Page, i am adding editbox with the property of the Multiline, and readonly the purpose is which i display in the License Information in the edit Box.

Now i am facing problem is?.. the waterMark color is also display in the editbox. that means its overlapping. How do i avoid this?... i cannt view the content in the editbox(not properly).

Hi
You've implemented a SetHeader & SetWatermark methods. How about also implementing a SetSideBitmap method that displays a bitmap on the left hand side of each page. Similar to the way an InstallShield wizard looks.

This will add a header to the first page, but unfortunately the watermark will dissappear, since CNGWizard::AddPage assumes (as does the Wizard97 control itself) that a page won't have both a watermark and header.

Fortunately, the solution is straightforward, though it involves a little legwork. In your CNGWizard derived class, override AddPage() as above but omit the check for PSP_HIDEHEADER, and ensure you call SetWatermark() for all pages you want to have a watermark.

Looking through the code for CPropertyPageEx, I noticed that although the class takes IDs for string table entries, all it does is load them into two member variables - m_strHeaderTitle and m_strHeaderSubTitle.

These are used to set m_psp.pszHeaderTitle and m_psp.pszHeaderSubTitle in each page, in CPropertySheetEx::BuildPropPageArray(), which is called from CPropertySheet::DoModal():

Hence the titles of the pages can be set at any time up until the Wizard is invoked.

Once the wizard pages have been created you may still be able to change the title/subtitle by allocating a new string and changing m_psp.pszHeaderTitle and m_psp.pszHeaderSubTitle to point to the new strings (don't forget to free the old ones though!). Be aware though that it may not work - it depends upon how co-operative the underlying Wizard97 control is.

That's a little worrying, though you're probably right - not many people are using Win95 anymore!

Running the executable through Dependency Walker (supplied with VC 6.0) confirms that the only non-core DLLs it uses are indeed MFC42.DLL and MSVCRT.DLL (versions 6.0.8665.0 and 6.1.8924.0 respectively on my Win2000 laptop), so you did exactly the right thing.

The only thing I can think of is that the MFC42.DLL and MSVCRT.DLL you installed were earlier versions (pre-SP3 is bad news!), but that's admittedly speculation on my part.

I have actually run the demo on a Win95 (OSR2) machine before now, and the only thing wrong was that pages with watermarks didn't paint quite right - presumably controls don't support transparent backgrounds on Win95.

One thing you could try is to rebuild the demo with debug info turned on in the release build and run it under the debugger (remote debugging is good for this if the box is an old one) to see where the crash happens. I'd be interested to see the results.

i want to use the water256 and Baner256 bitmap file dynamically. how i can use in the wizard97 property style. for ex. these to bitmap file in my current working dir. if the user update the Bmp means then the wizard97 property page also need to chage how?....