Click here to Skip to main content
Click here to Skip to main content

Designer centric Wizard control

, 15 Dec 2004 Ms-PL
Rate this:
Please Sign up or sign in to vote.
A Wizard control designed for design time development.

Sample screenshot

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 WizardPages 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

ToolBox showing the three controls

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).

Sample form with undocked Wizard

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

WizardPage Collection Editor

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

An InfoPage Control

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 RichTextBoxes 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.

Header Control

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.

 ///<summary>
/// Called when the cancel button is pressed, before the form is 
/// closed. Set e.Cancel to true if 
/// you do not wish to close the wizard.
/// </summary>
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.

/// <summary>
/// Event called before this page is closed when the back button is 
/// pressed. If you don't want to show the next page then set 
/// e.page to be the new page that you wish to show
/// </summary>
public event PageEventHandler CloseFromBack;
/// <summary>
/// Event called before this page is closed when the next button is 
/// pressed. If you don't want to show the previous page then set 
/// e.page to be the new page that you wish to show 
/// </summary>
public event PageEventHandler CloseFromNext;
/// <summary>
/// Event called after this page is shown when the back button is pressed.
/// </summary>
public event EventHandler ShowFromBack;
/// <summary>
/// Event called after this page is shown when the next button is pressed. 
/// </summary>
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.

/// <summary>
/// Arguments passed to an application when Page is 
/// closed in a wizard. The Next page to be displayed 
/// can be changed, by the application, by setting 
/// the NextPage to a wizardPage which is part of the 
/// wizard that generated this event.
/// </summary>
public class PageEventArgs : EventArgs
{
    /// <summary>
    /// Gets/Sets the wizard page that will be displayed 
    /// next. If you set this it must be to a wizardPage 
    /// from this wizard.
    /// </summary>
    public WizardPage Page [..]
    
    /// <summary>
    /// Gets the index of the page 
    /// </summary>
    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)
    {
        //All successful, do nothing
    }
    else
    {
       //Tell the user Why
       MessageBox.Show(“Failed Validation”);
       //Stay on the correct page
       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.

/// <summary>
/// Gets/Sets the enabled state of the Next button. 
/// </summary>
[Category("Wizard")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool NextEnabled [..]
/// <summary>
/// Gets/Sets the enabled state of the Back button. 
/// </summary>
[Category("Wizard")]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
public bool BackEnabled [..]
/// <summary>
/// Gets/Sets the enabled state of the Cancel button. 
/// </summary>
[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 Controls 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))) 
    {
        //Next can handle that
        return true; 
    }
    if(wiz.btnBack.Enabled && 
      wiz.btnBack.ClientRectangle.Contains(wiz.btnBack.PointToClient(point)))
    {
        //Back can handle that
        return true; 
    } 
    //Nope not interested
    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.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)

Share

About the Author

Al Gardner
Software Developer (Senior)
United Kingdom United Kingdom
** Apologies but my daughter was born in October 2004, and so coding now comes second. My reponses tend to take a lot longer**
 
I've been coding since I got my first ZX Spectrum. From Basic to assembly, through C,C++ and arriving at C#. On the way I've throughly enjoyed Perl, Lisp and XML.
 
I find I can make the intellectual leap to understand the problem, I love big picture designs, patterns and reuse. I may be addicted to abstract classes Smile | :) GOF has a lot to answer for. I miss delete() even though I spent too much time finding the leaks.
 
My favourite part of coding is in UI design because of the complexity, the event driven nature, and the fact its (virtually) tactile. I hate GUI's that don't follow system guidelines, don't resize, and don't display properly when you change system colour and font.

Comments and Discussions

 
GeneralHelpbutton Pinmemberleggan21-Dec-09 1:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.1411023.1 | Last Updated 16 Dec 2004
Article Copyright 2004 by Al Gardner
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid