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!
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.
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:
Create a method that, given a control, will search its control hierarchy and return the first TextBox control found
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
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:
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:
Check to see if the user has just arrived at the "Employee Salary & Hire Date Information" step
If so, check to see if the CheckBox to skip the "Employee Salary & Hire Date Information" step has been checked
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.