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.

Share This Article With Your Friends!

Googles Ads Facebook Pixel Bing Pixel LinkedIn Pixel