Last Christmas I really needed to get away from it all, just me, my wife, the beach, my laptop, and a few thousand lines of code. So we packed up the car, printed out the source to most of the Community Starter Kit, and headed to the family beach retreat--cool! The Community Starter Kit is very cool, and I'd like to thank Microsoft and Stephen Walther for putting it together and supporting it via the forums; that code has been very good to me as I've managed to resale many of the concepts from within it.
One of the most notable features is the fact that the entire application is skinnable. You can create custom skins for the application with a relatively minor amount of skill. One of the best things that I learned from that app was that you can load templates into templated controls at runtime. Let me explain!
Create a new project and add a web form named DynamicTemplates.aspx. Inside of the form tags add the following declaration for a Repeater control:
<asp:Repeater ID="stampsRepeater" Runat="server" />
This repeater will display a listing of stamps, so create a definition for a stamp class:
Public Class Stamp
Private mTitle, mImagePath As String
Private mWidth, mHeight, mYearOfRelease As Integer
Public ReadOnly Property Title() As String
Get
Return mTitle
End Get
End Property
Public ReadOnly Property Width() As Integer
Get
Return mWidth
End Get
End Property
Public ReadOnly Property Height() As Integer
Get
Return mHeight
End Get
End Property
Public ReadOnly Property YearOfRelease() As Integer
Get
Return mYearOfRelease
End Get
End Property
Public ReadOnly Property ImagePath() As String
Get
Return mImagePath
End Get
End Property
Public Sub New( _
ByVal title As String, _
ByVal w As Int32, ByVal h As Int32, _
ByVal year As Integer, _
ByVal path As String _
)
Me.mTitle = title
Me.mWidth = w
Me.mHeight = h
Me.mYearOfRelease = year
Me.mImagePath = path
End Sub
End Class
In the DynamicTemplates.aspx Page_Load event handler, create a few instances of stamps and bind them to the repeater:
Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles MyBase.Load
Dim arr As New ArrayList
Dim sTitles() As String = {"StampA", "StampB", "StampC", "StampD", "StampE"}
Const width As Int32 = 60
Const height As Int32 = 100
Dim years() As Integer = {1962, 1984, 1933, 1977, 1947}
For i As Integer = 0 To 4
Dim st As New Stamp( _
sTitles(i), _
width, height, _
years(i), _
sTitles(i) & ".gif" _
)
arr.Add(st)
Next
stampsRepeater.DataSource = arr
stampsRepeater.DataBind()
End Sub
If you build the assembly and browse to the page now, you will get an empty screen--duh! Time to add some templates. Under the project root add a folder named "UserControls" and add the following three user controls:
StampsHeader.ascx
<%@ Control %>
<table width="300" border="0" cellpadding="12" cellspacing="0">
<tr bgcolor="DarkMagenta">
<th colspan="2">
<strong color="#ffffff">
Great Stamps of our time!
</font>
</th>
</tr>
StampsItem.ascx
<%@ Control %>
<tr valign="top">
<td>
<%# DataBinder.Eval( CType( Container, RepeaterItem ).DataItem, "Title" ) %>
( <%# DataBinder.Eval( CType( Container, RepeaterItem ).DataItem, "YearOfRelease" ) %> )
</td>
<td>
<img
src="<%# DataBinder.Eval( CType( Container, RepeaterItem ).DataItem, "ImagePath" ) %>"
width="<%# DataBinder.Eval( CType( Container, RepeaterItem ).DataItem, "Width" ) %>"
height="<%# DataBinder.Eval( CType( Container, RepeaterItem ).DataItem, "Height" ) %>"
/>
</td>
</tr>
StampsFooter.ascx
<%@ Control %>
<tr bgcolor="DarkMagenta">
<td colspan="2"> </td>
</tr>
</table>
Notice that in the Item.ascx template I first cast the Container item to a RepeaterItem before accessing the DataItem. Now, back in DynamicTemplates.aspx, add some more code to dynamically load the template controls into the repeater control:
Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles MyBase.Init
stampsRepeater.HeaderTemplate = LoadTemplate(Request.ApplicationPath & _
"/UserControls/StampsHeader.ascx")
stampsRepeater.ItemTemplate = LoadTemplate(Request.ApplicationPath & _
"/UserControls/StampsItem.ascx")
stampsRepeater.FooterTemplate = LoadTemplate(Request.ApplicationPath & _
"/UserControls/StampsFooter.ascx")
End Sub
Recompile the code and rerun the app; you are now presented with a listing of stamps laid out as described in the user controls.
One little gotcha when using dynamically loaded templates is that, if you need to do a FindControl in ItemDataBound to get an instance of a control in the template you need to make the call on Controls(0) of the e.Item--as opposed to doing it directly on the e.Item. Here's an example of that, first, I'll modify the footer control to add a Label control:
<%@ Control %>
<tr bgcolor="DarkMagenta">
<td colspan="2">
<asp:Label ID="recordCountLabel" Runat="server" />
</td>
</tr>
</table>
Finally, add the code in the hosting page (DynamicTemplates.aspx) to find the Label and alter the Text property:
Private Sub stampsRepeater_ItemDataBound(ByVal sender As Object, _
ByVal e As System.Web.UI.WebControls.RepeaterItemEventArgs) _
Handles stampsRepeater.ItemDataBound
If e.Item.ItemType = ListItemType.Footer Then
Dim recs As Integer = CType(stampsRepeater.DataSource, ArrayList).Count
Dim lbl As Label = CType(e.Item.Controls(0).FindControl("recordCountLabel"), Label)
lbl.Text = recs.ToString() & " stamps are shown here."
End If
End Sub
Dynamically loading controls in this manner allows you to create templates that are distinct in layout and appearance and load them based on settings known only at runtime. This technique can allow your repeater controls to take on a "skinnable" behaviour based on user settings, for example, imagine if we had created several distinct sets of templates and given them themed names such as:
StampsHeader_Forest.ascx, StampsHeader_Ocean.ascx, StampsHeader_Lunar.ascx
StampsItem_Forest.ascx, StampsItem_Ocean.ascx, StampsItem_Lunar.ascx
StampsFooter_Forest.ascx, StampsFooter_Ocean.ascx, StampsFooter_Lunar.ascx
These controls could then be loaded based on a user's stored preference to provide a completely customized look and feel to your pages, as in the concluding example below.
Dim cookie As HttpCookie = Request.Cookies("ThemeName")
Dim themeName As String = "Forest"
If Not cookie Is Nothing Then
themeName = Request.Cookies("ThemeName").Value
Else
Response.Cookies("ThemeName").Value = "Forest"
End If
stampsRepeater.HeaderTemplate = _
LoadTemplate(Request.ApplicationPath & _
"/UserControls/StampsHeader_" & themeName & ".ascx")
stampsRepeater.ItemTemplate = _
LoadTemplate(Request.ApplicationPath & _
"/UserControls/StampsItem_" & themeName & ".ascx")
stampsRepeater.FooterTemplate = _
LoadTemplate(Request.ApplicationPath & _
"/UserControls/StampsFooter_" & themeName & ".ascx")