To read the article online, visit http://www.4GuysFromRolla.com/articles/062806-1.aspx

Creating a Step-by-Step User Interface with the ASP.NET 2.0 Wizard Control: Improving and Customizing the User Experience

By Scott Mitchell


Introduction


One of the many new Web controls available in ASP.NET 2.0 is the Wizard Web control, which takes the user through a series of discrete steps in order to accomplish some task. As discussed in Creating a Step-by-Step User Interface with the ASP.NET 2.0 Wizard Control: The Basics, the Wizard control is made up of a collection of <asp:WizardStep>s, with each step containing properties (such as its Title and StepType) along with HTML and Web controls specific to that step. The navigational user interface - the Next, Previous, Finish, and Complete buttons that appear at the bottom of the various steps - are automatically added by the Wizard control and are determined by the step's StepType property.

In Creating a Step-by-Step User Interface with the ASP.NET 2.0 Wizard Control: The Basics, we examined creating a Wizard control that broke down the process of adding a new employee to a database into four steps. The first step included instructions; the second prompted the user for the new employee's first and last name; the third step provided a TextBox and Calendar control for the employee's salary and hire date; and the final step included the TextBoxes to collect the new employee's contact information (address, phone, and email). We then created an event handler for the Wizard control's FinishButtonClick event where we added programmatic logic to insert the new employee record into the database.

While our demo worked, it had a couple of limitations and annoyances, such as not automatically setting focus to the first Web control when moving to a new WizardStep. Furthermore, the demo didn't explore some of the more advanced features of the Wizard control, such as adding a Complete step. Such a step appears after clicking the Finish button and summarizes the action(s) just performed. Additionally, the navigation user interface automatically created by the Wizard can be customized through templates and the sequence of steps can be customized based on user input. In this article we'll see how to accomplish all of these more advanced features. Read on to learn more!

(If you've not yet read Creating a Step-by-Step User Interface with the ASP.NET 2.0 Wizard Control: The Basics, please do so before continuing on with this article...)

Customizing the User Account Creation Process Using the Wizard Control
ASP.NET 2.0 includes a membership system that makes it a cinch to setup the infrastructure needed to create and manage user accounts along with a series of user account-related Web controls. The CreateUserWizard control, as its name implies, provides an interface for a user to create a new account. This control, by default, prompts the user for memebership specific user-related attributes, such as username, password, email, and so on. However, it can be customized quite easily and broken into a step-by-step process using the exact same techniques discussed in this article and its precursor.

For more information on customizing the CreateUserWizard control see Erich Peterson's article Customizing the CreateUserWizard Control. For more on ASP.NET 2.0's membership system, check out my article series, Examining ASP.NET 2.0's Membership, Roles, and Profile.

Improving the User Experience


The Wizard control demo from Creating a Step-by-Step User Interface with the ASP.NET 2.0 Wizard Control: The Basics had a couple usability shortcomings. First off, there was no input field validation. For example, while the FirstName field is a required field in the database, the user is able to omit this value, leading to an OleDb-level exception upon attempting to insert the employee record. We will remedy this using standard ASP.NET validation controls, which will prevent the user from moving to any other step than the previous one if the validation controls do not report the data as being valid. (Any additional server-side validation checks can be performed by creating an event handler for the NextButtonClick event.)

Another annoyance was that when moving from one step to another, we had to continuously click in the input field to start entering data. Ideally, moving to the next screen would automatically place the keyboard's focus in the first input control in the current wizard step. To set the focus to the first TextBox in the Wizard when moving from one step to the next, we need to do two things:

  1. Create a method that, given a control, will search its control hierarchy and return the first TextBox control found
  2. The Wizard's ActiveStepChanged event fires each time the user moves from one step to the next. Therefore, we need to add an event handler for this event that searches the current Wizard step's control hierarchy for the first TextBox, using the method created in step 1, and then sets focus to that TextBox
To find the first TextBox in a control hierarchy, I've created a recursive method named FindFirstTextBox(control), which takes in a control and does a depth-first search for a TextBox. As soon as a TextBox is found, the recursion unwinds and the TextBox returned:

'Recurses through the specified WizardStep's control hierarchy,
'searching for the first TextBox Web control
Private Function FindFirstTextBox(ByVal c As Control) As TextBox
    If c Is Nothing Then Return Nothing
    If TypeOf c Is TextBox Then Return c

    Dim results As Control
    For Each child As Control In c.Controls
        results = FindFirstTextBox(child)

        If results IsNot Nothing AndAlso TypeOf results Is TextBox Then Return results
    Next

    'If we reach here, we didn't find a TextBox
    Return Nothing
End Function

Then, in the event handler for the Wizard's ActiveStepChanged event, we get the current WizardStep instance from the Wizard's ActiveStep property and pass this control into the FindFirstTextBox method to find the first TextBox within its control hierarchy. If a TextBox exists, it's Focus() method is called, which automatically injects a bit of client-side JavaScript that causes the TextBox to receive focus on page load (see How to: Set Focus on ASP.NET Web Server Controls for more information).

Protected Sub AddEmployeeWizard_ActiveStepChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles AddEmployeeWizard.ActiveStepChanged
    'Set the focus to the first TextBox in the current step
    Dim currentWizardStep As WizardStepBase = AddEmployeeWizard.ActiveStep

    'Find the first TextBox
    Dim firstTextBox As TextBox = FindFirstTextBox(currentWizardStep)

    'If we found a TextBox, set the Focus
    If Not firstTextBox Is Nothing Then
        firstTextBox.Focus()
    End If
End Sub

With the FindFirstTextBox method and ActiveStepChanged event handler in place, moving from one Wizard step to another automatically sets focus in the current wizard step's first TextBox (if one exists).

All of the code examined can be downloaded from the end of this article...

Adding a Completed Step


After finishing a wizard, many include a Completed step that summarizes the action(s) performed. For the Add Employee Wizard demo, we might want to include a summary page after the Finish button is clicked that shows the values of the new employee record the user just created. To add a Completed step, simply add a new <asp:WizardStep> with its StepType property set to Complete.

The following declarative markup adds a Completed step to the Add Employee Wizard demo. This step appears as the last step in the Wizard and has its StepType property set to Complete. The HTML and Web controls within the step displays summary information, including Label controls that hold the values entered by the user:

<asp:WizardStep runat="server" StepType="Complete" Title="Summary">
    <h2>Employee Added!</h2>
    <p>
        An employee has been added with the following information:
    </p>
    <table border="0">
        <tr>
            <td class="InputLabel">Name:</td>
            <td class="InputControl">
                <asp:Label runat="server" ID="LastNameLabel"></asp:Label>,
                <asp:Label runat="server" ID="FirstNameLabel"></asp:Label>
            </td>
        </tr>
        <tr>
            <td class="InputLabel">Salary:</td>
            <td class="InputControl">
                <asp:Label runat="server" ID="SalaryLabel"></asp:Label>
            </td>
        </tr>
        ... Remaining Labels removed for brevity ...
    </table>
    <p>
        To see the employee listed among the others, return to <a href="Default.aspx">Home</a>.
    </p>
</asp:WizardStep>

In the Page_Load event handler (on each and every postback), the summary's Labels' values are assigned the values from their respective TextBoxes:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
    Const NA_TEXT As String = "<i>N/A</i>"
    'Assign the values for the Labels' Text properties in the Wizard's Completed step
    FirstNameLabel.Text = FirstName.Text
    LastNameLabel.Text = LastName.Text

    If Salary.Text.Length > 0 Then
        SalaryLabel.Text = Convert.ToDecimal(Salary.Text).ToString("C")
    Else
        SalaryLabel.Text = String.Empty
    End If

    HireDateLabel.Text = HireDate.SelectedDate.ToShortDateString()

    StreetLabel.Text = Street.Text
    If StreetLabel.Text.Length = 0 Then StreetLabel.Text = NA_TEXT

    CityLabel.Text = City.Text
    If CityLabel.Text.Length = 0 Then CityLabel.Text = NA_TEXT

    PhoneLabel.Text = Phone.Text
    If PhoneLabel.Text.Length = 0 Then PhoneLabel.Text = NA_TEXT

    EmailLabel.Text = Email.Text
    If EmailLabel.Text.Length = 0 Then EmailLabel.Text = NA_TEXT
End Sub

With the Completed step implemented, the previous four steps for the Wizard control remain the same. The user experience is identical up until the Finish button is clicked from the "Employee Contact Information" step. After clicking Finish, the user is displayed the contents of the Completed step.

Customizing the Navigation User Interface


The navigation user interface for the wizard steps are, by default, automatically added by the Wizard control based on the step's StepType property. The actual buttons can be rendered as normal Button Web controls, LinkButtons, or ImageButtons, depending on the value of the Wizard control's *ButtonType properties (there's the StartNextButtonType, FinishPreviousButtonType, and CancelButtonType properties). One downside of this auto-generated navigational user interface is that the Previous button is rendered before the Next button. When displaying these buttons as Button Web controls (the default), this makes the Previous button the "default button" in the browser, meaning that if a user hits Enter in a TextBox to submit the form, the Previous button is the one used for submission. The net effect is that with the default, auto-generated navigational UI, typing in a value into a Wizard TextBox and hitting Enter sends us back to the Previous step.

The Button Web control in ASP.NET 2.0 provides a UseSubmitBehavior property that can be used to indicate which Button Web control should have the Submit behavior associated with it. In short, we can set the Previous button's UseSubmitBehavior property to False and the Next button's UseSubmitBehavior to True; this will mean that hitting enter in a TextBox in the Wizard step will have the same effect as clicking the Next button.

To craft a custom navigational UI, we need to convert the entire Wizard step into an <asp:TemplateWizardStep>. The <asp:TemplateWizardStep> expects two templates:

  • ContentTemplate - contains the HTML and Web controls for the step (the markup that was previously declared between the <asp:WizardStep> and </asp:WizardStep> tags)
  • CustomNavigationTemplate - contains the HTML and Web controls for the navigational UI
For example, to change the "Employee Name Information" step from a standard <asp:WizardStep> to a <asp:TemplateWizardStep>, replace the markup for that step with the following:

<asp:TemplatedWizardStep ID="NameTemplate" runat="server" Title="Name">


    <ContentTemplate>
        <h3>Employee Name Information</h3>
        <table border="0">
            <tr>
                <td class="InputLabel">First Name:</td>
                <td class="InputControl">
                    <asp:TextBox ID="FirstName" runat="server" MaxLength="50"></asp:TextBox>
                </td>
            </tr>
            <tr>
                <td class="InputLabel" style="height: 42px">Last Name:</td>
                <td class="InputControl" style="height: 42px">
                    <asp:TextBox ID="LastName" runat="server" MaxLength="50"></asp:TextBox>
                </td>
            </tr>
        </table>
    </ContentTemplate>
    <CustomNavigationTemplate>
        <asp:Button UseSubmitBehavior="False" ID="StepPrevButton" runat="server"
            CommandName="MovePrevious" Text="Previous" /> 
            
        <asp:Button UseSubmitBehavior="True" ID="StepNextButton" runat="server"
            CommandName="MoveNext" Text="Next" />
    </CustomNavigationTemplate>
</asp:TemplatedWizardStep>

The ContentTemplate contains the same markup that was previously found within the <asp:WizardStep> tags. The CustomNavigationTemplate includes the Button Web controls for the Next and Previous buttons. Note how the Previous button's UseSubmitBehavior property is set to False, while hte Next button's is set to True. Furthermore, note the CommandName properties for each button. It's vital that the Previous button's CommandName be set to "MovePrevious" and the Next button's to "MoveNext".

With this change, hitting enter from either one of the TextBoxes in the "Employee Name Information" step will send the user to the Next step, as opposed to the previous one. This pattern would need to be repeated for each step in order to have the Enter send the user to the Next step.

One final word on creating a custom navigational user interface - when switching over to templates, the code to programmatically access a Web control from the ContentTemplate is different than when accessing Web controls in the <asp:WizardStep>. When using the ContentTemplate, you must use the FindControl("controlID") approach to reference a control from within the template, like so:

Dim firstNameTextBox As TextBox
firstNameTextBox = CType(NameTemplate.ContentTemplateContainer.FindControl("FirstName"), TextBox)

In short, with the <asp:TemplateWizardStep> you lose the direct access to its content Web controls.

Implementing a Custom or Non-Linear Step Sequence


Normally, a wizard proceeds linearly, from the first step to the last. However, it's possible to alter the workflow of the Wizard control based on user input or programmatic logic. In the Add Employee Wizard, the contact information is optional. Therefore, we might want to add a CheckBox in the "Employee Name Information" step with the text, "Specify Contact Information?" If this is checked, the wizard will proceed as normal; however, if it's unchecked, indicating that the user does not want to provide contact information, we can update the "Employee Salary & Hire Date Information" step, setting its StepType property to Finish. This will add a Finish button to the navigational UI for the "Employee Salary & Hire Date Information" step (and remove the Next button). (Note: if you use a custom navigational UI, you'll need to manually show/hide the Next and Finish buttons.)

The Wizard control's ActiveStepChanged event fires after moving from one step to another. Therefore, we can create an event handler that sets the "Employee Salary & Hire Date Information" step's StepType based on the value of the CheckBox in the "Employee Name Information" step.

Protected Sub AddEmployeeWizard_ActiveStepChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles AddEmployeeWizard.ActiveStepChanged
    'Set the StepType for the SalaryAndHireDateStep based on the SpecifyContactInformation CheckBox value
    If SpecifyContactInformation.Checked Then
        SalaryAndHireDateStep.StepType = WizardStepType.Step
    Else
        SalaryAndHireDateStep.StepType = WizardStepType.Finish

        'Clear out the values for contact information
        Street.Text = String.Empty
        City.Text = String.Empty
        Phone.Text = String.Empty
        Email.Text = String.Empty
    End If
End Sub

This event handler starts by checking if the SpecifyContactInformation CheckBox (which was added to the "Employee Name Information" step) was checked or not. If it was, then we want to let the user reach the "Employee Contact Information" step; therefore, we set the SalaryAndHireDateStep's StepType property to Step. (I've set the ID of the <asp:WizardStep> for the "Employee Salary & Hire Date Information" step to SalaryAndHireDateStep.) If, however, the CheckBox is unchecked, then we want to make the "Employee Salary & Hire Date Information" step the final step and clear out any values the user might have already entered for the new employee's contact information.

This same concept - creating an event handler for the Wizard's ActiveStepChanged event - can be used to jump from one step to another in a non-linear fashion. For example, imagine that we wanted to allow the user to not specify the new employee's salary and hire date, instead using some pre-canned, default values. Again, we could include a CheckBox in the "Employee Name Information" step and then, in the ActiveStepChanged event, we could:

  1. Check to see if the user has just arrived at the "Employee Salary & Hire Date Information" step
  2. If so, check to see if the CheckBox to skip the "Employee Salary & Hire Date Information" step has been checked
  3. If the CheckBox has been checked, then set the Wizard's ActiveStepIndex property to the index of the "Employee Contact Information" step
The following event handler illustrates how to implement such logic. Note that in addition to having set the ID for the "Employee Salary & Hire Date Information" step to SalaryAndHireDateStep, I've also set the ID of the "Employee Contact Information" step to ContactInfoStep. Also, don't forget that the ActiveStepChanged event fires after the Wizard's active step has changed - that's why our check at the start of the following event handler sees if we're currently at the "Employee Salary & Hire Date Information" step:

Protected Sub AddEmployeeWizard_ActiveStepChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles AddEmployeeWizard.ActiveStepChanged
    'See if we have just reached the SalaryAndHireDateStep step
    If AddEmployeeWizard.ActiveStep.Equals(SalaryAndHireDateStep) Then
        'See if the user wants to provide Salary & HireDate
        If SpecifySalaryAndHireDate.Checked Then
            'Send the user to the SalaryAndHireDateStep step
            AddEmployeeWizard.ActiveStepIndex = AddEmployeeWizard.WizardSteps.IndexOf(SalaryAndHireDateStep)
        Else
            'Put in default values for Salary & HireDate and send user to ContactInfoStep
            Salary.Text = 0
            HireDate.SelectedDate = DateTime.Today

            AddEmployeeWizard.ActiveStepIndex = AddEmployeeWizard.WizardSteps.IndexOf(ContactInfoStep)
        End If
    End If
End Sub

Conclusion


In this article we examined a number of the more advanced features of the ASP.NET 2.0 Wizard Web control, seeing how to improve and customize the user's experience. We saw how to have the first TextBox in a Wizard step receive focus when moving to a new step; we looked at adding a Completed step and customizing the navigational user interface through the use of templates. This article concluded with a look at how to introduce custom and non-linear workflows with the Wizard.

Happy Programming!

  • By Scott Mitchell


    Attachments


  • Download the code used in this article
  • Suggested Readings


  • Creating a Step-by-Step User Interface with the ASP.NET 2.0 Wizard Control: The Basics
  • Customizing the CreateUserWizard Control
  • Article Information
    Article Title: ASP.NET.Creating a Step-by-Step User Interface with the ASP.NET 2.0 Wizard Control: Improving and Customizing the User Experience
    Article Author: Scott Mitchell
    Published Date: June 28, 2006
    Article URL: http://www.4GuysFromRolla.com/articles/062806-1.aspx


    Copyright 2014 QuinStreet Inc. All Rights Reserved.
    Legal Notices, Licensing, Permissions, Privacy Policy.
    Advertise | Newsletters | E-mail Offers