Master pages and themes give you the power of building similar-looking, rich pages that share graphics, control layout, and even some functionality. A special type of rich page is the page that implements a wizard. More common in Windows desktop applications than in Web scenarios, wizards are typically used to break up large forms to collect user input. A wizard is a sequence of related steps, each associated with an input form and a user interface. Users move through the wizard sequentially, but they are normally given a chance to skip a step or jump back to modify some of the entered values. A wizard is conceptually pretty simple, but implementing it over HTTP connections can be tricky. Everybody involved with serious Web development can only heartily welcome the introduction of the Wizard control in ASP.NET 2.0.
The Wizard control supports both linear and nonlinear navigation. It allows you to move backward to change values and to skip steps that are unnecessary due to previous settings or because users don't want to fill those fields. Like many other ASP.NET 2.0 controls, the Wizard supports themes, styles, and templates.
The Wizard is a composite control and automatically generates some constituent controls such as navigation buttons and panels. As you'll see in a moment, the programming interface of the control has multiple templates that provide for in-depth customization of the overall user interface. The control also guarantees that state is maintained no matter where you move backward, forward, or to a particular page. All the steps of a wizard must be declared within the boundaries of the same Wizard control. In other words, the wizard must be self-contained and not provide page-to-page navigation.
As shown in Figure 6-14, a wizard has four parts: header, view, navigation bar, and sidebar.
Figure 6-14: The four parts of a Wizard control.
The header consists of text you can set through the HeaderText property. You can change the default appearance of the header text by using its style property; you can also change the structure of the header by using the corresponding header template property. If HeaderText is empty and no custom template is specified, no header is shown for the wizard.
The view displays the contents of the currently active step. The wizard requires you to define each step in an <asp:wizardstep> element. An <asp:wizardstep> element corresponds to a WizardStep control. Different types of wizard steps are supported; all wizard step classes inherit from a common base class named WizardStepBase.
All wizard steps must be grouped in a single <wizardsteps> tag, as shown in the following code:
<asp:wizard runat="server" DisplaySideBar="true"> <wizardsteps> <asp:wizardstep runat="server" steptype="auto" > First step </asp:wizardstep> <asp:wizardstep runat="server" steptype="auto" > Second step </asp:wizardstep> <asp:wizardstep runat="server" steptype="auto" > Final step </asp:wizardstep> </wizardsteps> </asp:wizard>
The navigation bar consists of auto-generated buttons that provide any needed functionality typically, going to the next or previous step or finishing. You can modify the look and feel of the navigation bar by using styles and templates.
The optional sidebar is used to display content in the left side of the control. It provides an overall view of the steps needed to accomplish the wizard's task. By default, it displays a description of each step, with the current step displayed in boldface type. You can customize styles and templates. Figure 6-15 shows the default user interface. Each step is labeled using the ID of the corresponding <asp:wizardstep> tag.
Figure 6-15: A wizard with the default sidebar on the left side.
You can style all the various parts and buttons of a Wizard control by using the properties listed in Table 6-4.
Style | Description |
---|---|
CancelButtonStyle | Sets the style properties for the wizard's Cancel button |
FinishCompleteButtonStyle | Sets the style properties for the wizard's Finish button |
FinishPreviousButtonStyle | Sets the style properties for the wizard's Previous button when at the Finish step |
HeaderStyle | Sets the style properties for the wizard's header |
NavigationButtonStyle | Sets the style properties for navigation buttons |
NavigationStyle | Sets the style properties for the navigation area |
SideBarButtonStyle | Sets the style properties for the buttons on the sidebar |
SideBarStyle | Sets the style properties for the wizard's sidebar |
StartStepNextButtonStyle | Sets the style properties for the wizard's Next button when at the Start step |
StepNextButtonStyle | Sets the style properties for the wizard's Next button |
StepPreviousButtonStyle | Sets the style properties for the wizard's Previous button |
StepStyle | Sets the style properties for the area where steps are displayed |
The contents of the header, sidebar, and navigation bar can be further customized with templates. Table 6-5 lists the available templates.
Style | Description |
---|---|
FinishNavigationTemplate | Specifies the navigation bar shown before the last page of the wizard. By default, the navigation bar contains the Previous and Finish buttons. |
HeaderTemplate | Specifies the title bar of the wizard. |
SideBarTemplate | Used to display content in the left side of the wizard control. |
StartNavigationTemplate | Specifies the navigation bar for the first view in the wizard. By default, it contains only the Next button. |
StepNavigationTemplate | Specifies the navigation bar for steps other than first, finish, or complete. By default, it contains Previous and Next buttons. |
In addition to using styles and templates, you can control the programming interface of the Wizard control through a few properties.
Table 6-6 lists the properties of the Wizard control, excluding style and template properties and properties defined on base classes.
Property | Description |
---|---|
ActiveStep | Returns the current wizard step object. The object is an instance of the WizardStep class. |
ActiveStepIndex | Gets and sets the 0-based index of the current wizard step. |
DisplayCancelButton | Toggles the visibility of the Cancel button. The default value is false. |
DisplaySideBar | Toggles the visibility of the sidebar. The default value is false. |
HeaderText | Gets and sets the title of the wizard. |
SkipLinkText | The ToolTip string that the control associates with an invisible image, as a hint to screen readers. The default value is Skip Navigation Links and is localized based on the server's current locale. |
WizardSteps | Returns a collection containing all the WizardStep objects defined in the control. |
A wizard in action is fully represented by its collection of step views and buttons. In particular, you'll recognize the following buttons: StartNext, StepNext, StepPrevious, FinishComplete, FinishPrevious, and Cancel. Each button is characterized by properties to get and set the button's image URL, caption, type, and destination URL after click. The name of a property is the name of the button followed by a suffix. The available suffixes are listed in Table 6-7.
Suffix | Description |
---|---|
ButtonImageUrl | Gets and sets the URL of the image used to render the button |
ButtonText | Gets and sets the text for the button |
ButtonType | Gets and sets the type of the button: push button, image, or link button |
DestinationPageUrl | Gets and sets the URL to jump to once the button is clicked |
Note that names in Table 6-7 do not correspond to real property names. You have the four properties in this table for each distinct type of wizard button. The real name is composed by the name of the button followed by any of the suffixes for example, CancelButtonText, FinishCompleteDestinationPageUrl, and so on.
The Wizard control also supplies a few interesting methods for example, GetHistory, which is defined as follows:
public ICollection GetHistory()
GetHistory returns a collection of WizardStepBase objects. The order of the items is determined by the order in which the wizard's pages were accessed by the user. The first object returned the one with an index of 0 is the currently selected wizard step. The second object represents the view before the current one, and so on.
The second method, MoveTo, is used to move to a particular wizard step. The method's prototype is described here:
public void MoveTo(WizardStepBase step)
The method requires you to pass a WizardStepBase object, which can be problematic. However, the method is a simple wrapper around the setter of the ActiveStepIndex property. If you want to jump to a particular step and not hold an instance of the corresponding WizardStep object, setting ActiveStepIndex is just as effective.
Table 6-8 lists the key events in the life of a Wizard control in an ASP.NET 2.0 page.
Event | Description |
---|---|
ActiveViewChanged | Raised when the active step changes |
CancelButtonClick | Raised when the Cancel button is clicked |
FinishButtonClick | Raised when the Finish Complete button is clicked |
NextButtonClick | Raised when any Next button is clicked |
PreviousButtonClick | Raised when any Previous button is clicked |
SideBarButtonClick | Raised when a button on the sidebar is clicked |
As you can see, there's a common click event for all Next and Previous buttons you can find on your way. A Next button can be found on the Start page as well as on all step pages. Likewise, a Previous button can be located on the Finish page. Whenever a Next button is clicked, the page receives a NextButtonClick event; whenever a Previous button is clicked, the control raises a PreviousButtonClick event.
A WizardStep object represents one of the child views that the wizard can display. The WizardStep class ultimately derives from View and adds just a few public properties to it. A View object represents a control that acts as a container for a group of controls. A view is hosted within a MultiView control. (See Chapter 4.) To create its output, the wizard makes internal use of a MultiView control. However, the wizard is not derived from the MultiView class.
You define the views of a wizard through distinct instances of the WizardStep class, all grouped under the <WizardSteps> tag. The <WizardSteps> tag corresponds to the WizardSteps collection property exposed by the Wizard control:
<WizardSteps> <asp:WizardStep> ... </asp:WizardStep> <asp:WizardStep> ... </asp:WizardStep> </WizardSteps>
Each wizard step is characterized by a title and a type. The Title property provides a brief description of the view. This information is not used unless the sidebar is enabled. If the sidebar is enabled, the title of each step is used to create a list of steps. If the sidebar is enabled but no title is provided for the various steps, the ID of the WizardStep objects is used to populate the sidebar, as shown earlier in Figure 6-15.
While defining a step, you can also set the AllowReturn property, which indicates whether the user is allowed to return to the current step from a subsequent step. The default value of the property is true.
The StepType property indicates how a particular step should be handled and rendered within a wizard. Acceptable values for the step type come from the WizardStepType enumeration, as listed in Table 6-9.
Property | Description |
---|---|
Auto | The default setting, which forces the wizard to determine how each contained step should be treated. |
Complete | The last page that the wizard displays, usually after the wizard has been completed. The navigation bar and the sidebar aren't displayed. |
Finish | The last page used for collecting user data. It lacks the Next button, and it shows the Previous and Finish buttons. |
Start | The first screen displayed, with no Previous button. |
Step | All other intermediate pages, in which the Previous and Next buttons are displayed. |
When the wizard is in automatic mode the default type Auto it determines the type of each step based on the order in which the steps appear in the source code. For example, the first step is considered of type Start and the last step is marked as Finish. No Complete step is assumed. If you correctly assign types to steps, the order in which you declare them in the .aspx source is not relevant.
The following code shows a sample wizard step used to collect the provider name and the connection string to connect to a database and search for some data. For better graphical results, the content of the step is encapsulated in a fixed-height <div> tag. If all the steps are configured in this way, users navigating through the wizard won't experience sudden changes in the overall page size and layout.
<asp:wizardstep runat="server" title="Connect"> <div style="height:200px;width:400px;margin:10;"> <table> <tr><td>Provider</td><td> <asp:textbox runat="server" width="250px" text="System.Data.SqlClient" /> </td></tr> <tr><td>Connection String</td><td> <asp:textbox runat="server" width="250px" text="SERVER=(local);DATABASE=northwind;UID= ;" /> </td></tr> <tr><td height="100px"></td></tr> </table> </div> </asp:wizardstep>
Figure 6-16 shows a preview of the step. As you can guess, the step is recognized as a Start step. As a result, the wizard is added only to the Next button.
Figure 6-16: A sample Start wizard step.
A wizard is usually created for collecting input data, so validation becomes a critical issue. You can validate the input data in two nonexclusive ways using validators and using transition event handlers.
The first option involves placing validator controls in the wizard step. This guarantees that invalid input empty fields or incompatible data types is caught quickly and, optionally, already on the client:
<asp:requiredfieldvalidator runat="server" text="*" errormessage="Must indicate a connection string" setfocusonerror="true" controltovalidate="ConnString" />
If you need to access server-side resources to validate the input data, you're better off using transition event handlers. A transition event is an event the wizard raises when it is about to switch to another view. For example, the NextButtonClick event is raised when the user clicks the Next button to jump to the subsequent step. You can intercept this event, do any required validation, and cancel the transition if necessary. We'll return to this topic in a moment.
The sidebar is a left-side panel that lists buttons to quickly and randomly reach any step of the wizard. It's a sort of quick-launch menu for the various steps that form the wizard. You control the sidebar's visibility through the Boolean DisplaySideBar attribute and define its contents through the SideBarTemplate property.
Regardless of the template, the internal layout of the sidebar is not left entirely to your imagination. In particular, the <SideBarTemplate> tag must contain a DataList control with a well-known ID SideBarList. In addition, the <ItemTemplate> block must contain a button object with the name of SideBarButton. The button object must be any object that implements the IButtonControl interface.
Note | For better graphical results, you might want to use explicit heights and widths for all steps and the sidebar as well. Likewise, the push buttons in the navigation bar might look better if they are made the same size. You do this by setting the Width and Height properties on the NavigationButtonStyle object. |
When a button is clicked to move to another step, an event is fired to the hosting page. It's up to you to decide when and how to perform any critical validation, such as deciding whether conditions exist to move to the next step.
In most cases, you'll want to perform server-side validation only when the user clicks the Finish button to complete the wizard. You can be sure that whatever route the user has taken within the wizard, clicking the Finish button will complete it. Any code you bind to the FinishButtonClick event is executed only once, and only when strictly necessary.
By contrast, any code bound to the Previous or Next button executes when the user moves back or forward. The page posts back on both events.
You should perform server-side validation if what the user can do next depends on the data he or she entered in the previous step. This means that in most cases you just need to write a NextButtonClick event handler:
<asp:wizard runat="server" OnNextButtonClick="OnNext"> ... </asp:wizard>
If the user moves back to a previously visited page, you can usually ignore any data entered in the current step and avoid validation. Because the user is moving back, you can safely assume he or she is not going to use any fresh data. When a back movement is requested, you can assume that any preconditions needed to visit that previous page are verified. This happens by design if your users take a sequential route.
If the wizard's sidebar is enabled, users can jump from page to page in any order. If the logic you're implementing through the wizard requires that preconditions be met before a certain step is reached, you should write a SideBarButtonClick event handler and ensure that the requirements have been met.
A wizard click event requires a WizardNavigationEventHandler delegate:
public delegate void WizardNavigationEventHandler( object sender, WizardNavigationEventArgs e);
The WizardNavigationEventArgs structure contains two useful properties that inform you about the 0-based indexes of the page being left and the page being displayed. The CurrentStepIndex property returns the index of the last page visited; the NextStepIndex returns the index of the next page. Note that both properties are read-only.
The following code shows a sample handler for the Next button. The handler prepares a summary message to show when the user is going to the Finish page.
void OnNext(object sender, WizardNavigationEventArgs e) { // Collect the input data if going to the last page // -1 because of 0-based indexing, add -1 if you have a Complete page if (e.NextStepIndex == QueryWizard.WizardSteps.Count - 2) PrepareFinalStep(); } void PrepareFinalStep() { string cmdText = DetermineCommandText(); // Show a Ready-to-go message StringBuilder sb = new StringBuilder(""); sb.AppendFormat("You're about to run: <br><br>{0}<hr>", cmdText); sb.Append("<b><br>Ready to go?</b>"); ReadyMsg.Text = sb.ToString(); } string DetermineCommandText() { // Generate and return command text here }
Each page displayed by the wizard is a kind of panel (actually, a view) defined within a parent control the wizard. This means that all child controls used in all steps must have a unique ID. It also means that you can access any of these controls just by name. For example, if one of the pages contains a text box named, say, ProviderName, you can access it from any event handler by using the ProviderName identifier.
The preceding code snippet is an excerpt from a sample wizard that collects input and runs a database query. The first step picks up connection information, whereas the second step lets users define table, fields, and optionally a WHERE clause. The composed command is shown in the Finish page, where the wizard asks for final approval. (See Figure 6-17.)
The full source code of the wizard is in the companion code for this book.
Figure 6-17: Two successive pages of the sample wizard query details and the Finish step.
The WizardNavigationEventArgs structure also contains a read/write Boolean property named Cancel. If you set this property to true, you just cancel the ongoing transition to the destination page. The following code shows how to prevent the display of the next step if the user is on the Start page and types in sa as the user ID:
void OnNext(object sender, WizardNavigationEventArgs e) { if (e.CurrentStepIndex == 0 && ConnString.Text.IndexOf("UID=sa") > -1) { e.Cancel = true; return; } }
You can cancel events from within any transition event handler and not just from the NextButtonClick event handler. This trick is useful to block navigation if the server-side validation of the input data has failed. In this case, though, you're responsible for showing some feedback to the user.
Note | You can't cancel navigation from within the ActiveViewChanged event. This event follows any transition events, such as the NextButtonClick or PreviousButtonClick event, and occurs when the transition has completed. Unlike transition events, the ActiveViewChanged event requires a simpler, parameterless handler EventHandler. |
All wizards have some code to execute to finalize the task. If you use the ASP.NET 2.0 Wizard control, you place this code in the FinishButtonClick event handler. Figure 6-18 shows the final step of a wizard that completed successfully.
void OnFinish(object sender, WizardNavigationEventArgs e) { string finalMsg = "The operation completed successfully."; try { // Complete the wizard (compose and run the query) string cmd = DetermineCommandText(); DataTable table = ExecuteCommand(ConnString.Text, cmd); grid.DataSource = table; grid.DataBind(); // OK color FinalMsg.ForeColor = Color.Blue; } catch (Exception ex) { FinalMsg.ForeColor = Color.Red; finalMsg = String.Format("The operation cannot be completed due to:<br>{0}", ex.Message); } finally { FinalMsg.Text = finalMsg; } } string DetermineCommandText() { // Generate and return command text here } DataTable ExecuteCommand() { // Execute database query here }
Figure 6-18: The final step of a wizard that completed successfully.
If the wizard contains a Complete step, that page should be displayed after the Finish button is clicked and the final task has completed. If something goes wrong with the update, you should either cancel the transition to prevent the Complete page from even appearing or adapt the user interface of the completion page to display an appropriate error message. Which option you choose depends on the expected behavior of the implemented operation. If the wizard's operation can fail or succeed, you let the wizard complete and display an error message in case something went wrong. If the wizard's operation must complete successfully unless the user quits, you should not make the transition to the Complete page; instead, provide users with feedback on what went wrong and give them a chance to try again.