Why? I hear you ask; well, my need was for a referral system in which a user could select one or more agencies to refer a job to and then complete each of the agency-specific forms (which in turn are connected to their own data sources). The challenge was on to link-up the user selections with the various forms in a simple end-to-end manner and then have each of the forms handle their own bit of data at the end of the process.
To demonstrate this, we'll create a clutch of user controls to load on demand, another control to take the user selections and then finally wrap it all up in an ASP.NET page to handle the paging.
First create a set of user controls that you want to make available to your user:
<%@ Control Language="VB" AutoEventWireup="false" CodeFile="Car.ascx.vb" Inherits="Car" %>
<h1>Car</h1>
<asp:Label ID="CarLabel" runat="server" AssociatedControlID="CarList" Text="Car:"></asp:Label>
<asp:DropDownList ID="CarList" runat="server">
<asp:ListItem Value="Rolls-Royce" Text="Rolls-Royce"></asp:ListItem>
<asp:ListItem Value="Bentley" Text="Bentley"></asp:ListItem>
</asp:DropDownList>
Then create a selector control so that the user can choose which of the controls to be presented with:
<%@ Control Language="VB" AutoEventWireup="false" CodeFile="Selector.ascx.vb" Inherits="Selector" %>
<h1>Acme Party Services</h1>
<asp:Label ID="SelectorLabel" runat="server" AssociatedControlID="SelectorList" Text="Please select your party services:"></asp:Label>
<asp:CheckBoxList ID="SelectorList" runat="server">
<asp:ListItem Value="Car" Text="Car"></asp:ListItem>
<asp:ListItem Value="Dress" Text="Dress"></asp:ListItem>
<asp:ListItem Value="Suit" Text="Suit"></asp:ListItem>
</asp:CheckBoxList>
In the code behind of the selector control expose the user selections through a property:
Partial Class Selector
Inherits System.Web.UI.UserControl
Public ReadOnly Property SelectedControlNames() As ArrayList
Get
Return GetSelectedControlNames()
End Get
End Property
Private Function GetSelectedControlNames() As ArrayList
Dim al As New ArrayList
For Each li As ListItem In SelectorList.Items
If li.Selected Then
al.Add(li.Value)
End If
Next
Return al
End Function
End Class
Finally, create the page to host the user controls along with the paging mechanism:
<%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default" %>
<%@ Register TagPrefix="uc" TagName="Selector" Src="~/Selector.ascx" %>
<%@ Register TagPrefix="uc" TagName="Car" Src="~/Car.ascx" %>
<%@ Register TagPrefix="uc" TagName="Dress" Src="~/Dress.ascx" %>
<%@ Register TagPrefix="uc" TagName="Suit" Src="~/Suit.ascx" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Control Pager</title>
</head>
<body>
<form id="form1" runat="server">
<div>
<%-- PlaceHolder to wrap the dynamic user controls --%>
<asp:PlaceHolder ID="PlaceHolder1" runat="server">
<%-- Declare the static 'Selector' user control --%>
<uc:Selector ID="Selector1" runat="server" />
</asp:PlaceHolder>
<br />
<%-- Buttons for paging through the controls --%>
<asp:Button ID="BackButton" runat="server" Text="< Back" Visible="false" />
<asp:Button ID="NextButton" runat="server" Text="Next >" />
</div>
</form>
</body>
</html>
The code behind shows that we start by loading all of the controls from the control history (using an array list held in view state), this is because dynamic controls do not survive page post backs on their own and must be reloaded in exactly the same order in which they were last provisioned. Next we load up any new controls that have been selected and then later in the page life cycle, if the selection has changed, unload any controls that are no longer required. Finally, using the 'Back' and 'Next' buttons we can display each control in the sequence:
Partial Class _Default
Inherits System.Web.UI.Page
' this property maintains a history of loaded controls
Public Property SelectedControls() As ArrayList
Get
Dim o As Object = ViewState("SelectedControls")
If o Is Nothing Then
Return New ArrayList
End If
Return CType(ViewState("SelectedControls"), ArrayList)
End Get
Set(ByVal value As ArrayList)
ViewState("SelectedControls") = value
End Set
End Property
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
LoadControls()
End Sub
Private Sub LoadControls()
' Maintain the control indexes and viewstate by loading the
' control history plus any new selections and then drop any
' de-selected controls later in the page life cycle.
' Read in control history
Dim controlHistory As ArrayList = SelectedControls
' Load controls
For Each controlItem As String In controlHistory
Dim uc As UserControl = LoadControl("~/" & controlItem & ".ascx")
uc.ID = controlItem
PlaceHolder1.Controls.Add(uc)
Next
' Read in the selected control names from the selector control
Dim controlList As ArrayList = Selector1.SelectedControlNames
' Load new selections
For Each controlItem As String In controlList
If Not controlHistory.Contains(controlItem) Then
Dim uc As UserControl = LoadControl("~/" & controlItem & ".ascx")
uc.ID = controlItem
PlaceHolder1.Controls.Add(uc)
' It matters to viewstate that properties are added after
' the control has been added to the control hierarchy
uc.Visible = False
controlHistory.Add(controlItem)
End If
Next
' Update the control history
SelectedControls = controlHistory
End Sub
Private Sub CleanUpControlHistory()
Dim controlHistory As ArrayList = SelectedControls
Dim controlHistoryCopy As ArrayList = controlHistory.Clone
' Read in the selected control names from the selector control...
Dim selectedControlNames As ArrayList = Selector1.SelectedControlNames
' ...and compare them to the control history
For Each controlItem As String In controlHistoryCopy
If Not selectedControlNames.Contains(controlItem) Then
' Drop unused control
Dim uc As UserControl = PlaceHolder1.FindControl(controlItem)
PlaceHolder1.Controls.Remove(uc)
controlHistory.Remove(controlItem)
End If
Next
' Update the control history
SelectedControls = controlHistory
End Sub
Protected Sub NextButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles NextButton.Click
' Clean up control history
If Selector1.Visible Then CleanUpControlHistory()
Move(1)
End Sub
Protected Sub BackButton_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles BackButton.Click
Move(-1)
End Sub
Private Sub Move(ByVal iDirection As Integer)
Dim ctrlTop As Integer = 0
Dim ctrlBottom As Integer = PlaceHolder1.Controls.Count - 1
' Iterate through the control collection
For i As Integer = ctrlTop To ctrlBottom
If ((iDirection = -1 And i > ctrlTop) Or (iDirection = 1 And i < ctrlBottom)) Then
Dim uc As UserControl = PlaceHolder1.Controls(i)
' Position on visible control
If uc.Visible Then
Dim nxt As Integer = (i + iDirection)
' Change view
uc.Visible = False
CType(PlaceHolder1.Controls(nxt), UserControl).Visible = True
BackButton.Visible = (nxt > 0)
Exit For
End If
End If
Next
End Sub
End Class
There you have it, a web user control loader and pager. Enjoy.
Demo
View a live demo at http://code.zyky.com/controlpagerCode
Download the Source CodeTrails
- Dynamic Web Controls, Postbacks, and View State by Scott Mitchell (4GuysFromRolla.com)
- Dynamic Loading of ASP.NET User Controls by Milind R Chavan (CPOL)