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)