Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Wizard Tab Control

0.00/5 (No votes)
11 Oct 2002 4  
A wizard control for .NET

MFC Updating Status Bar Automatically

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 workaround 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 of whether it is 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.

  1. Add WizardControl and EtchedLine to your toolbar.
  2. Add an Inherited Form and derive from WizardTab.WizardForm.
  3. Add additional pages by modifying the control's TabPages.
  4. Don't forget to rename all TabPages WizardTabPages in the source code.
  5. 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.

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below. A list of licenses authors might use can be found here.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here