Using ITemplate in DataList, GridViews, Repeaters and DataGrids
I want to thank TRINUG, my local user group, for letting be the main presenter last night. I talked about how to take the data controls like GridView, Datalist and Repeater to another level. I sort of did a progression of creating and controlling custom layouts in the data controls. This started with a review of some basic items, like auto generated columns, bound columns templates and the use of custom templates.
I wanted to take a few moments to post some information about using custom defined templates and my way of 'cheating' on this. First I guess I will start by explaining what we will be doing. In any of the data controls you set your row layout in a Template, it may be an Itemtemplate, AlternatingItemTemplate, Headertemplate, Pagertemplate or FootTemplate. You can do it the hard way and think you are doing it easy, but I want to show you how to do it the easy way and your peers will think you are a genius.
The big secret is in learning how to utilize the ITemplate interface. This interface can be implemented in your classes to define the layout and population of any templated ASP.NET server control, like the Data Controls.
ITemplate has one public method, InstantiateIn. This method has one parameter that is passed to it, a reference to the control the template belongs to, like a DataListItem. The DataListItem is actually a control for the row in the DataList that is being loaded. It would be a DataGridItem in a dataGrid and so on.
What I do with this event is to drop a Panel on the Container. You could use several other controls, like a placeholder
Public Class MyCustomTemplate
Implements ITemplate
Dim WithEvents p As Panel
Public Sub InstantiateIn(ByVal container As System.Web.UI.Control) Implements System.Web.UI.ITemplate.InstantiateIn
p = New Panel
container.Controls.Add(p)
End Sub
End Class
First, I define a panel at the class level, p. This is done so I can access the events of the panel anywhere in the class I need to. So I added a method in my CustomTemplate class to handle the databind event of the panel. In this method I grab a reference to the row's DataListItem and create a Literal. I then get a reference to the dataitem so I can bind my data. Then I get a predefined template to render the data the way I want. This is where I get fancy and make things get easy. I don't like mannually building my layout in the code behind, rather I like to merge a layout with the right data. So I create a text file, say mydatatemplate.htm and load it at run-time. I really like to cache it the first time it is loaded, but you can do what you want. So Let's Look at one:
<table border='0' cellpadding='0' cellspacing='0' width='580' align='left'>
<tr>
<td width='10'>
</td>
<td width='250' valign='top'>
<table border='0' cellpadding='0' cellspacing='0' width='250' align='left'>
<tr>
<td align='left'>
##CourseName##
</td>
</tr>
<tr>
<td align='left'>
##Address##
</td>
</tr>
<tr>
<td align='left'>
##City## ##State## ##Zip##
</td>
</tr>
</table>
</td>
<td width='280' valign='top'>
<table border='0' cellpadding='0' cellspacing='0' width='250' align='left'>
<tr>
<td align='left'>
##PhoneNumber##
</td>
</tr>
<tr>
<td align='left'>
##NoHoles## Holes
</td>
</tr>
<tr>
<td align='left'>
##CourseType##
</td>
</tr>
</table>
</td>
</tr>
<tr><td colspan=3><br /></td></tr>
</table>
The row template is loaded in a basic file read operation (below). For production I would put this in the application cache so I would not have to reload it over and over, but I want to keep it simple here. Oh and I would not hard code the path, but again this is for a demo.
Public ReadOnly Property ctFrame() As String
Get
Dim FILE_NAME As String = 'c:\inetpub\wwwroot\datademo\mydatatemplate.htm'
Dim output As String = ''
If Not File.Exists(FILE_NAME) Then
Return output
End If
Using sr As StreamReader = File.OpenText(FILE_NAME)
output = sr.ReadToEnd
sr.Close()
End Using
Return output
End Get
End Property
This is one for a set of data that defines a Golf Course, but you can make it whatever you want. I deliniate the fields to perform the merge with a double # on each side. I mentioned an Info Class to hold the data that defines my data object, like a golf course. I generate these from CodeSmith templates I have created for my taste, but you could of course do it by hand if you wanted. So I generate a method called Merge on the Info class that does a Replace on each one of the fields in the template for me. To be a little more advanced, I could expand this to different merge fields to indicate hyperlinks, images, etc.
Public Function Merge(ByVal sMessage As String) As String
sMessage = sMessage.Replace('##CourseId##', CourseId)
sMessage = sMessage.Replace('##CourseName##', CourseName)
sMessage = sMessage.Replace('##Address##', Address)
sMessage = sMessage.Replace('##City##', City)
sMessage = sMessage.Replace('##State##', State)
sMessage = sMessage.Replace('##Zip##', Zip)
sMessage = sMessage.Replace('##PhoneNumber##', PhoneNumber)
sMessage = sMessage.Replace('##NoHoles##', NoHoles)
sMessage = sMessage.Replace('##CourseType##', CourseType)
sMessage = sMessage.Replace('##GreensFees##', GreensFees)
sMessage = sMessage.Replace('##AdvTeeOpt##', AdvTeeOpt)
Return sMessage
End Function
So when we put all this together we capture the DataBind event of the Panel to drop the row's layout with merged data in it. In the event handler I declare a literal, get the DatListItem (the control representing the row), the template (ctFrame) and the CourseInfo (the DataItem). I learned it is good to check to make sure these things are actually instantiated and to wrap it in a Try Catch. So basically you just run the merge on the layout and set the literal's text to the merged layout. Then I add the Literal to the controls on the panel and we are done.
Public Sub BindData(ByVal sender As Object, ByVal e As EventArgs) Handles p.DataBinding
Dim l As New Literal
Dim container As DataListItem = CType(p.NamingContainer, DataListItem)
Dim sFrame As String = ctFrame
Dim ci As CourseInfo = CType(container.DataItem, CourseInfo)
Try
If IsNothing(ci) = False Then
l.Text = ci.Merge(ctFrame)
p.Controls.Add(l)
End If
Catch ex As Exception
End Try
End Sub
This is a very easy way to help expedite your data control layouts to make it easier to change the layout.