A better way to reference your wizard steps using named steps

Note: this article uses the plain vanilla <asp:Wizard> but the concepts apply equally well to its popular counterpart <asp:CreateUserWizard>.

By far the most common way that I see wizard steps reference in code snippets is by their index.

Something like this is common:

void Wizard1_NextButtonClick(object sender, System.Web.UI.WebControls.WizardNavigationEventArgs e)
{
     if (Wizard1.ActiveStepIndex == 1)
     {
          // jump to step three
          Wizard1.ActiveStepIndex = 3;
     }
}

This was actually taken from some MSDN documentation. But what's the problem with this? It works doesn't it?

Yes it works today but the problem is when you come back in a few weeks and want to re-order your steps, or add a new one in somewhere. Suddenly all those numbers seem to have a lot less meaning. Its a chore to change the steps over and its not something that will get caught by the compiler of you miss one or get it wrong.

There is a better way though because you don't have to tie yourself down to the index. The trick in a nutshell is to give each of your <asp:WizardStep>'s an ID. By default the Visual Studio GUI doesn't do this but you can add them in yourself. This gives you a meaningful name that you can use and its not tied to the order of the steps.

Example 1 - Finding a control in a WizardStep

The first challenge that is enhanced by this technique is finding controls that are inside wizard steps. Instead of having to go through the Wizard control you can now use FindControl() directly on your WizardStep because you have a way to access it.

TextBox firstName = (TextBox)StepNameHere.FindControl("FirstName");

Example 2 - Comparing against CurrentStepIndex

The second example is my favourite trick because I didn't find out about it for a while after I had started using named WizardSteps.

If you have a WizardStep called StepBillingDetails for example, you can use the Wizard1.WizardSteps.IndexOf() to find the index of that named step. This turns your named step into an integer which can be used to compare against properties such as ActiveStepIndex property of the Wizard and CurrentStepIndex in your NextButtonClick event.

protected void Wizard1_NextButtonClick(object sender, WizardNavigationEventArgs e)
{
    Wizard w = (Wizard)sender;

    // check if billing details step has been completed
    if (e.CurrentStepIndex == w.WizardSteps.IndexOf(StepBillingDetails))
    {
        // user has just completed the wizard step "StepBillingDetails"
    }
}

Example 3 - Predictable Hot Jumping

From looking back at the first example that I gleaned from MSDN you can probably see how we can improve it using these new techniques. While it is a slightly contrived example (I doubt I would always want to unconditionally jump from step one to step three) it does illustrate my point.

Lets take a look at what it looks like now that it uses named steps:

void Wizard1_NextButtonClick(object sender, System.Web.UI.WebControls.WizardNavigationEventArgs e)
{
     Wizard wizard1 = (Wizard)sender;

     if (wizard1.ActiveStepIndex == wizard1.WizardSteps.IndexOf(StepPersonalDetails))
     {
          // jump to step three
          wizard1.ActiveStepIndex = wizard1.WizardSteps.IndexOf(StepOrderComplete);
     }
}

Complete Sample

Here is a complete sample demonstrating how to take advantage of named WizardSteps with both of the techniques described above.

Markup:

        <asp:Wizard ID="Wizard1" runat="server" 
            onnextbuttonclick="Wizard1_NextButtonClick">
            <WizardSteps>
                <asp:WizardStep ID="StepPersonalDetails" runat="server" Title="Personal Details">
                    <asp:TextBox ID="FirstName" runat="server"></asp:TextBox>
                </asp:WizardStep>
                <asp:WizardStep ID="StepBillingDetails" runat="server" Title="Billing Details">
                </asp:WizardStep>
                <asp:WizardStep ID="StepOrderComplete" runat="server" Title="Order Complete">
                    First Name: <asp:Label ID="FirstNameLabel" runat="server" Text="Unknown"></asp:Label>
                </asp:WizardStep>
            </WizardSteps>
        </asp:Wizard>

Code behind:

    protected void Wizard1_NextButtonClick(object sender, WizardNavigationEventArgs e)
    {
        Wizard w = (Wizard)sender;

        // check if we just completed the first step
        if (e.CurrentStepIndex == w.WizardSteps.IndexOf(StepPersonalDetails))
        {
            // find the first name control
            TextBox firstName = (TextBox)StepPersonalDetails.FindControl("FirstName");

            // check it has a value
            if (!String.IsNullOrEmpty(firstName.Text))
            {
                // find the label on the last page
                Label firstNameLabel = (Label)StepOrderComplete.FindControl("FirstNameLabel");

                // assign the value
                firstNameLabel.Text = firstName.Text;
            }

            // jump to order complete step
            w.ActiveStepIndex = w.WizardSteps.IndexOf(StepOrderComplete);
        }
    }

Downloadable version:

Further Reading

Not a whole lot of further reading for you to today as I have really said everything I wanted to say. Here is one just for completeness:

You will find a few gotcha's on there that aren't related to this article but things that might trip you up in the future.

2 comments :

Laura said...

I'm trying to determine the difference between CurrentStepIndex and ActiveStepIndex. You use both in your code samples. Can you clarify the difference?

Anonymous said...

Exactly what I was looking for - thank you!