
Introduction
Unfortunately, Microsoft didn't include a wizard for us in .NET, nor do I see
any plans to implement one before late 2003, so it's necessary for us to create
a work around that is both elegant and flexible.
I want to first off give James Johnson credit for inspiring me to write a wizard
control and post it for you here on CodeProject. Although his solution is
a very usable, I didn't find it as elegant as I wished. It used
the standard model of having multiple pages separated into multiple files,
and if your system is like mine, the toolbox started to get quite crowded
with inherited controls. Thanks James for taking up precious time to give
us a solution. I based my example off of his sample, so it might look a
tad like his.
Traditionally wizards were created from property pages/sheets with a
few additional buttons and setting SetWizardMode for turning off
the tabs. I tend to like both a next/finished button instead of
renaming the next button on the last step. If there are circumstances
where I can finish up a wizard early, then the single button approach has no
solution.
Problems
Well, we don't have SetWizardMode anymore! Nor can we turn
off the tabs from appearing on the property sheets!! That created a big
problem in trying to make a property sheet based wizard functional. But,
the property sheets became incredibly more powerful with the ability to work on
all the pages simultaneously.
So, I cheated...
Solution
The first thing I had to do was turn off those darn tabs!! After months of
searching, I didn't get too far with any type of solution. I pray that
someone has a better solution than this. Then I can come back and edit it
as fast as possible to hide what I did.
I created a simple control called EtchedLine. It's actually a
decent little control that draws a simple line the width of the control.
Then whenever the base form is resized, I moved the EtchedLine over
onto the top of the tab control. I changed the drawing property of the
control to draw a white line on the bottom of itself to completely hide where
the tabs were and to make it look like one continuous window. Quit
laughing.
private void InitializeEtchedLine( )
{
if( this.DesignMode )
{
lblEtchedLine.Size = new Size ( 10, 10 );
lblEtchedLine.Location = new Point( 10, Height - 10 );
}
else
{
lblEtchedLine.Size = new Size( WizardControl1.Width - 1,
WizardControl1.DisplayRectangle.Y - 1 );
lblEtchedLine.Location = WizardControl1.Location;
}
}
WizardControl
Once I was allowed to use the property sheets, the WizardControl began
to evolve nicely. I have written a base form named WizardForm that
completely encompasses the entire control. So don't worry about having to
access any of the WizardControl properties directly.
Properties
-
NextButton - The form's next button. The control will enable/disable the
button when appropriate
-
BackButton - The form's back button
-
CancelButton
-
FinishedButton
-
TitleLabel - The main title of the page. The control will update the
title depending on the step.
-
AllowBack - Allows the user to use the back button to edit their previous
choices
Methods
-
MoveBack - Step back one page
-
MoveNext - Step forward one page
-
MoveFirst - Go to the first page and initialize itself
-
MoveTo - Go to a specific index or page
-
HideStep - Hide a step from the standard page order
-
ShowStep - Show a hidden step in the standard page order
Events
-
Event_Initialize - When the wizard is first initialized. Reset/set any
options here.
-
Event_MoveNext - Next button has been pressed. Override to validate
the page.
-
Event_MoveBack - Previous button has been pressed.
-
Event_PageChanged - Page has changed to this current page. Override
to initialize variables
-
Event_Cancelled - Cancel button was pressed. Override to warn if
appropriate.
-
Event_Finished - Finish button was pressed. Override to validate all
of the data on the form.
WizardTabPage
You will be adding pages to the WizardControl the exact same way that you would
be adding pages to a TabControl.
Important: Once you have added a TabPage to your control, you
will currently need to edit the source code and change all occurrences of
TabPage to the WizardTabPage. I'm sure there's some way of doing that
automatically through an attribute, and if you find out how, please let me
know!
this.tabPage2 = new WizardTab.WizardTabPage();
Properties
-
Title - The text that you would like to show on the top of the page.
-
ShowFinished - Show the finished button on this page regardless if it's
the last page or not.
-
Hidden - Hide this property page based on user decisions.
You can change the properties of the tab page directly either through the
Property settings or through the TabPages property in the WizardControl.
WizardForm
And the last piece of the wizard is the form that you will be using to inherit
from.
Important: To initialize the page correctly, we need to add a
MoveFirst to your base class right after the data is
initialized. This will initialize your next/prev buttons correctly.
public OrderPizza( )
{
InitializeComponent( );
this.WizardControl1.MoveFirst( );
}
Virtual Functions
-
OnWizInitialize
-
OnWizMoveNext
-
OnWizMoveBack
-
OnWizPageChanged
-
OnWizCancelled
-
OnWizFinished
Inheriting from WizardForm
Ok, you're ready to add a wizard to your project.
-
Add WizardControl and EtchedLine to your toolbar.
-
Add an Inherited Form and derive from
WizardTab.WizardForm.
-
Add additional pages by modifying the control's
TabPages
.
-
Don't forget to rename all TabPages
WizardTabPages
in the source code.
-
Add
MoveFirst after your InitializeComponents function
in your main class.
That's really about it!!
Validating Pages
To validate the data on the page, override the OnWizMoveNext function.
Put any type of validation that you wish and pass eCancel.Cancel = false if you
want to prevent the user from continuing.
protected override void OnWizMoveNext( WizardTab.WizardControl pWizard,
WizardTab.WizardTabPage pTabPage,
CancelEventArgs eCancel )
{
switch( pWizard.SelectedIndex )
{
case 1:
if( this.chkAnchovies.Checked )
{
MessageBox.Show( "We've never actually had anchovies. It's just something " +
"we've always put on our menu", "Sorry, no anchovies" );
eCancel.Cancel = true;
}
if( this.chkBacon .Checked == false && this.chkMushrooms.Checked == false &&
this.chkOnions.Checked == false && this.chkPepperoni.Checked == false )
{
MessageBox.Show( "You've got to put something on your pizza, that just ain't " +
"American", "What? No toppings?!" );
eCancel.Cancel = true;
}
break;
}
return;
}
Using the Data
What's really nice about this solution is that all of your variables are
accessible in your functions. All of the variables on your pages can be
accessed directly without the need to pass your variables via a parent object
or through function parameters.
protected override void OnWizPageChanged( WizardTab.WizardControl pWizard,
WizardTab.WizardTabPage pTabPage )
{
switch( pWizard.SelectedIndex )
{
case 3:
StringBuilder strOrder = new StringBuilder( );
strOrder.Append( "Crust: \r\n" );
if( this.btnThickCrust .Checked )
strOrder.Append( "\tThick Crust\r\n" );
if( this.btnThinAndCrispy.Checked )
strOrder.Append( "\tThin and Crispy\r\n" );
if( this.btnChicagoStyle .Checked )
strOrder.Append( "\tChicago Style Deep Dish\r\n" );
strOrder.Append( "\r\nToppings: \r\n" );
if( this.chkPepperoni.Checked )
strOrder.Append( "\tPepperoni\r\n" );
if( this.chkBacon .Checked )
strOrder.Append( "\tCanadian Bacon\r\n" );
if( this.chkMushrooms.Checked )
strOrder.Append( "\tMushrooms\r\n");
if( this.chkOnions .Checked )
strOrder.Append( "\tOnions\r\n" );
this.textBox1.Text = strOrder.ToString( );
break;
}
}
Conclusion
I hope that I've included enough flexibility that you find this Wizard a
superior solution to the old MFC wizards that we're all familiar
with. Let me know if there is more functionality that you would
like to see.
Oh yea, The Pizza Hut name, logos, and related marks are trademarks of Pizza
Hut, Inc.
Phillip has been a programmer long enough to remember cutting paper strip code out of Dr. Dobbs and running them thru a mechanical decoder to avoid typing in the samples.