Introduction
There are many wizard controls out there: Wizard Control, .NET Wizard control, and of course the magic library. Here's another one, that might meet your needs when others don't.
Background
Last year, I was writing an application that needed a wizard control. I looked around and found none of the controls quite suited my needs, and so I wrote my own. However, following the loss of both the working source code and the CVS tree, I now found myself re-writing from scratch something I have already done once before. The advantage with this method is that my prototype really looked into every single problem that I could have encountered beforehand, and I believe I am left with a nice, neat, and clean result.
It comes in three controls, the first Wizard
handles all the functionality of the wizard, and provides a collection of WizardPage
s that you can author upon. Pressing the Back and Next buttons moves through the pages as you would expect (even at design time).
In order to provide the correct look, two additional controls are provided, InfoPage
and Header
. These take the correct colors from the System.Colors
namespace and render the expected Wizard
look in lightweight and simple controls. This approach means the GUI can be customized by inheriting or implementing entirely separate controls, and of course, these controls can provide the UI in other forms of your application without needing to use a Wizard
control.
One very important point to remember with Wizards is that they are supposed to support a single task and make it easy for an user. It is very easy to add extra features into a Wizard when actually you should split your task into multiple Wizards. Consider that a Wizard can be used to create/edit an object, but not delete it.
v1.1 of this Wizard adds another feature that can be used badly. This is the provision of multiple Finish pages (see below). A Finish page is usually a text only summary of what will happen after Finish is pressed, a last chance to change your mind; however, it can be easier to build this summary on a separate page and have a unique back path.
Using the code
I have subsequently reused this code in several applications; you can either create a DLL or copy it into the same project, whichever your preference.
If you move the code out of the project it is in, you will get three compilation errors:
The type or namespace name 'ParentControlDesigner' could not be found
(are you missing a using directive or an assembly reference?).
These can be easily fixed by adding a reference in the project to the System.Design.dll.
Design Time Features
First, let's have a look at the pretty stuff, via the designer. We will come to real code, event hookup, and manually calling methods, later.
Adding the Wizard to a Form
Once compiled, three controls will be added to your toolbox in user controls. If they don't appear, then you may simply need to open each of them in design mode.
Start by adding a Wizard
control to your form. This one shown below is as dropped on the form and has not been docked or anchored into the form yet.
Please note after first adding that you do not have a control surface to add controls to (as indicated by the lack of a grid inside the control).
Once the page has been added, either change the Docked
property to Full
or set the Anchors
, Size
and Position
via the Wizard
properties.
Adding Pages to the wizard
In order to add pages, go to the Pages
property and click the ellipsis (...) button. This opens the Collection editor which can be used to add pages to the collection. The example comes with three pages already added.
While writing this article, I realize I have not implemented the AddTab
and RemoveTab
verbs for the control. Since a WizardDesigner
is already implemented in this solution, the addition of the verbs and the tying up of the events should be a relatively small piece of work which I shall post up shortly.
Each WizardPage
is an overridden Panel
control, so any other Windows Control
can be dropped straight onto the WizardPage
. This allows you to build up complex Wizard interfaces as you may require; however, this control is currently lacking the standard look of a Wizard as you know it.
Defining a Finish Page
The final page of the Wizard will always be a Finish Page. This means that the Next button will display the word "Finish" and clicking this will return a DialogResult.OK
.
Should you design a Wizard that requires multiple different Finish Pages, then additional pages can also become Finish Pages by setting IsFinishPage=true
on the desired WizardPage
.
Adding a Wizard Look
The remaining three controls are now used to add the correct look to the Wizard. Start and End pages within the Wizard are handled by using either an InfoPage
or InfoContainer
control.
The InfoPage
contains a full height right docked image, a title and a section of descriptive text. It does not easily support contained controls. The main properties of interest are Image
, PageText
, and PageTitle
. Image
takes an image
that is the same size as the left image standard system Wizard, i.e., it is 164 pixels wide. However, should your Form
be taller than the image, it will repeat at the bottom. An example image (wizBigOnlineFolder.gif) is included in the project in order that you modify it. PageTitle
is the text which appears in the title section, and PageText
is that which appears below.
The InfoContainer
is very similar to InfoPage
but only shows a title and image. At its name suggests, it supports contained controls at design time and therefore allows any combination of controls to be added to build up your initial/final page. If the form is Sizable, then I would advise that attention is paid to correctly anchoring contained controls, and that RichTextBox
es with BorderStyle
=None
, ReadOnly
=true
, Background
=White
, and Scrollbars
=Vertical
are useful instead of labels where a possibly large piece of text is needed to be displayed.
The Header
is used at the top of every WizardPage
that does not include an InfoPage
. Once again, the interesting properties are the Image
, Title
, and Description
. Image
will resize if possible to best fit the image in. Title
is shown in larger font, and Description
the smaller font below.
Both of these controls resize, while attempting to be as accurate to the Wizards used in Windows XP as possible.
Code Features
This section focuses on the Wizard
control as the others are really only GUI placeholders.
Events raised during Wizard operation
In order to allow the most complicated Wizards to be facilitated, there are five events that are thrown by the Wizard
, four from the WizardPage
and one from the wizard
control.
The wizard
control throws an event whenever somebody clicks the Cancel button. If you decide to prevent the Close of the Wizard, then set the Cancel
of the CancelEvent
to true
.
public event CancelEventHandler CloseFromCancel;
Pressing Next or Back, however, raises two events from the WizardPage
, one to close the existing page and another to open the new page.
public event PageEventHandler CloseFromBack;
public event PageEventHandler CloseFromNext;
public event EventHandler ShowFromBack;
public event EventHandler ShowFromNext;
As you can see, the Closing events pass a more complex eventArgs
. The Closing events allow you to override the Next or Previous Page
that will be shown after this Page
closes. The PageEventArgs
provides you with the option of setting the following Page
via either its index (this is not advised for safe code however) or by passing a WizardPage
itself.
public class PageEventArgs : EventArgs
{
public WizardPage Page [..]
public int PageIndex [..]
}
Page Validation
The CloseFromBack
/CloseFromNext
events give the developer a final chance to validate each control on the WizardPage
. Ideally, you have already used an ErrorProvider
to indicate to the User the problems with validation on each control, but during this event, you can double check the values and allow or deny the move to the next WizardPage
.
private void wpLogin_CloseFromNext(object sender, Gui.Wizard.PageEventArgs e)
{
if (<field_1_Validation> == true && <field_2_Validation> == true)
{
}
else
{
MessageBox.Show("Failed Validation");
e.Page = wpLogin;
}
}
N.B.: I find that writing the logic to check for success is easier to read than checking for a fail. You are free to use DeMorgans Theorem and reverse this to check for a fail should you wish.
Controlling the Wizard Buttons
The Wizard
also has the ability to enable or disable its buttons depending on your requirements. Since the state of these are expected to change with each page, the state is not saved as part of the design process. They will always default to enabled, and you have the option of disabling them in each Show
event for the page.
[Category("Wizard")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool NextEnabled [..]
[Category("Wizard")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool BackEnabled [..]
[Category("Wizard")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool CancelEnabled [..]
The second page of this example code attempts to demonstrate this with the well known License agreement page. The only code added to the form is as follows:
private void wizardPage2_ShowFromNext(object sender, System.EventArgs e)
{
wizard1.NextEnabled = checkBox1.Checked;
}
private void checkBox1_CheckedChanged(object sender, System.EventArgs e)
{
wizard1.NextEnabled = checkBox1.Checked;
}
Non-Wizard behavior processing
Finally, there are four methods that have recently been added to the Wizard. Next()
and Back()
simply allow a programmatic means of going to the following or previous pages respectively. These methods step outside the normal processing of the wizard, so if you can achieve the results you desire via the above methods, I advise you to do so.
Slightly more dangerous are NextTo(WizardPage)
and BackTo(WizardPage)
. These methods skip raising the WizardPage
Close
event, which of course means that your validation is not performed. My reasoning for this is because the Close
event allows you to specify which page you want to go to. So, if you first tell the Wizard to display a particular page via a call to NextTo()
, and the closing event was called and informs the Wizard to go to a different page, which page should I go to? I have assumed that, as the coder, we understand enough to validate first and only then call NextTo
or BackTo
. An example of this is included in the penultimate page in the demonstration project.
Given, I am issuing the above warning, why should I wish to release these additional methods out to the wild? Well, they solve a couple of unusual circumstances.
The Perform Processing Page and Next() example
On occasions, you wish to display a Wizard page and have it perform processing. When the processing is done, you may wish to change to the next page automatically. (I have unfortunately seen users watch a progress control complete, and then leave a screen sitting there that says 'Press Next to continue...'.)
See the example on the third page of the Wizard to see this in action. (N.B.: the use of the timer is not a very good way of performing processing in the Wizard and receiving updates, but it was simple to code for this example.)
The Button to jump around the Wizard
The final example page has several buttons that allow you to directly access pages in a Wizard. 99% of the time, this can be avoided by setting the Page
property of the Close
event, and I urge you to do so. Please note the difference between using NextTo
and BackTo
compared to any other movement.
One (bad) example where this is of use is, if you are using a Wizard to build a new object and you have to choose something, where different choices then lead to different pages. (It's not a good thing to do, as you have to work out how to navigate back to the correct pages too. Instead, it's better to have one new Form/Wizard to handle selecting which type of object we want to handle, and another Wizard to perform the operation on that Wizard, just like Add Printer in Windows 2000+).
Points of Interest
Using the collection editor
Adding pages caused many problems. The implementation of the PageCollection
and capturing the events is vital in order to work with the VS.NET Collection Editor. If you fail to catch the events, you end up with Control
s that do not get correctly added to the form and frequently are valid only in the code.
Detecting the Next and Back buttons at design time
This also caused many problems and had several work-arounds until I looked at the code behind the other alternative Wizards listed at the start of the article. Thanks go to the authors of these examples (and many others) posted on CodeProject.
Update (v1.1)
This has now been rewritten from scratch. Have a look at the WizardDesigner
for the full code, but it basically works by overloading the ControlDesigner.GetHitTest
to detect where the buttons are, and allow MouseDown
and MouseUp
events to be generated.
protected override bool GetHitTest(Point point)
{
Wizard wiz = this.Control as Wizard;
if (wiz.btnNext.Enabled &&
wiz.btnNext.ClientRectangle.Contains(wiz.btnNext.PointToClient(point)))
{
return true;
}
if(wiz.btnBack.Enabled &&
wiz.btnBack.ClientRectangle.Contains(wiz.btnBack.PointToClient(point)))
{
return true;
}
return false;
}
In the Wizard, we then add handlers for MouseDown
on btnBack
and btnNext
and which only work in DesignMode
.
private void btnBack_MouseDown(object sender, <BR> System.Windows.Forms.MouseEventArgs e)
{
if (DesignMode == true)
Back();
}
History
- v1.1 - 15th Dec 2004. Rewrote to better use Designer features. Now uses
GetHitTest
instead of WndProc
. Verified to be compatible with SharpDevelop by user Validate.
- v1.0.1 - 9th Dec 2004. Added multiple finish pages. Not released to CodeProject.
- v1.0 - 15th Nov 2004. Considered stable. (10,000+ page views, 4.54 rating). Update with
InfoContainer
, and validation to prevent controls in Wizard (instead of pages) as suggested by user vbnetuk.
- v0.1b - 7th Sep 2004. Fixed the description of
Next
, NextTo
, Back
, BackTo
, and added the main picture.
- V0.1a - 6th Sep 2004. Initial release.