Login | Register   
RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Dynamic Templates for the Repeater, DataList and DataGrid Controls : Page 3

This article explains why a single template hard-coded at design-time is not always the best option when using DataGrid, DataList and Repeater controls that must generate different outputs according to the logged-in user, a user's choice, or any other condition. It then provides detailed solutions to implement dynamic templates, by writing external user controls, or classes that implement the ITemplate interface.

Custom Template Classes

The template technique seen so far is great if you want to dynamically apply the same template to all the rows of a Repeater, DataList or DataGrid, but there yet one more situation you may face. Say for example that you want to use a red foreground color for employees that have a salary greater than a certain amount, and instead use green for employees that make less that another amount. Or that you want to render with different background colors the male, female, and gender-neutral   employees, according to their title of courtesy. Or again, you may want to hide some information only for employees that explicitly required this (the e-mail or mailing address, for example), but not for everybody.

Some of these row-by-row customizations, such as showing/hiding certain data, can be done by binding the Visible property of a Label to an expression. Others, regarding the visual customization of the item’s layout and style or the data being shown, can be done by handling the control’s ItemCreated and ItemDataBound events. However, if you need the same customized template for more than one control, possibly in different pages, you’ll end up repeating the same template definition and the same code-behind for its customization over and over again, in many places. This is not the best for maintainability and code-reuse of course.

In these cases, you can create a class that implements the ITemplate interface, and that programmatically creates all the controls for the template column or item, and takes care of binding the required data to the proper controls. In practice, it does by code everything you would declare in the ASPX file and in the code-behind. Then, you can create an instance of this class and associate it to the Repeater’s, DataList’s or DataGrid’s templates, as we did before with the templates loaded from file. This is a great option, because in a single class you can have everything is required for your row-by-row custom output, and you achieve a great code reuse because you’ll be able to use the class from everywhere in your project, or in any other project if you compile the class in its own redistributable Class Library assembly. As an example, we’re going to see how to render the employees with different background colors, according to their title of courtesy.

We declare a class that implements ITemplate, add any custom property (I use public fields in the example below, as I don’t have to validate the value, but the concept it’s the same) and a constructor that takes in input the values to initialize these properties:

Class MyCustomTemplate Implements ITemplate Public ForeColor As Color Public FemaleColor As Color Public MaleColor As Color Public NeutralColor As Color Dim WithEvents pan As Panel ' the constructor takes the color for female/male/neutral empl. titles Sub New(ByVal femaleColor As Color, ByVal maleColor As Color, _ ByVal neutralColor As Color, ByVal foreColor As Color) Me.FemaleColor = femaleColor Me.MaleColor = maleColor Me.NeutralColor = neutralColor Me.ForeColor = foreColor End Sub

The ITemplate interface has a single method to implement, InstantiateIn, called when the container’s item is being created, and where we must instantiate the controls that will show the data. In this example we need three labels, for the employee’s first and last names, and the title of courtesy. However, we first create a panels that covers the whole container’s item’s size, and then add the labels inside it. We use a container panel control so that’s easier to set the item’s background color. So, the labels are added to the panel’s Controls collection, and the panel itself is added to the container control’s Controls collection. Here’s the code for this method:

Public Sub InstantiateIn(ByVal container As System.Web.UI.Control) _ Implements System.Web.UI.ITemplate.InstantiateIn ' create a panel with the specified forecolor pan = New Panel() pan.ForeColor = Me.ForeColor ' if the parent control is a WebControl... If TypeOf (container) Is WebControl Then ' get a strongly-typed reference to the containing item Dim webCtl As WebControl = DirectCast(container, WebControl) ' set the panel's size so that it fully cover's its containers pan.Width = webCtl.Width pan.Height = webCtl.Height End If ' add 3 label controls, and two literals for the <b></b> tags pan.Controls.Add(New Label()) ' this is the TitleOfCourtesy label pan.Controls.Add(New LiteralControl("<b>")) pan.Controls.Add(New Label()) ' this is the LastName label pan.Controls.Add(New LiteralControl("</b>, ")) pan.Controls.Add(New Label()) ' this is the FirstName label ' add the Panel control to the DataListItem control container.Controls.Add(pan) End Sub

At this point we’ve only declared the controls for the template, but haven’t yet specified what they should show. To bind the data to these controls, we must react to their DataBinding events, raised when a row in the Repeater / DataList / DataGrid, and so the inner controls in its templates, are being bound to a data item. If you look again to the above, you’ll see that the Panel control was declared at class level, with the WithEvents keyword, so that’s easy to handle its events.

The next method we have to write is actually an handler for the panel’s DataBinding event, where we get a reference to the container’s data item, extract the data we want (the first and last names, and the title of courtesy), and show it in the three labels created in the InstantiateIn method. Let’s first see the complete code:

' this event fires once for each item in the DataList, ' as it is being bound to the data source Private Sub pan_DataBinding(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles pan.DataBinding Dim title, firstName, lastName As String ' get a reference to the container item, and then to the data item Dim data As Object If TypeOf (pan.NamingContainer) Is DataListItem Then data = DirectCast(pan.NamingContainer, DataListItem).DataItem ElseIf TypeOf (pan.NamingContainer) Is DataGridItem Then data = DirectCast(pan.NamingContainer, DataGridItem).DataItem ElseIf TypeOf (pan.NamingContainer) Is RepeaterItem Then data = DirectCast(pan.NamingContainer, RepeaterItem).DataItem Else Return End If ' cast the data item to the proper type: ' - DataRowView if the datasource is a DataTable/DataView) ' - DbDataRecord if the datasource is a DataReader ' and the retrieve the values we need to show in the template If TypeOf (data) Is DataRowView Then Dim drv As DataRowView = DirectCast(data, DataRowView) title = drv("TitleOfCourtesy").ToString() firstName = drv("FirstName").ToString() lastName = drv("LastName").ToString() Else Dim dbr As DbDataRecord = DirectCast(data, DbDataRecord) title = dbr("TitleOfCourtesy").ToString() firstName = dbr("FirstName").ToString() lastName = dbr("LastName").ToString() End If ' set the pan's BackColor according to the TitleOfCourtesy value Select Case title Case Is = "Ms.", "Mrs.", "Lady" pan.BackColor = FemaleColor Case Is = "Mr." pan.BackColor = MaleColor Case Else pan.BackColor = NeutralColor End Select ' display the values in the Panel child controls. Dim lblTitle As Label = DirectCast(pan.Controls(0), Label) lblTitle.Text = title Dim lblLastName As Label = DirectCast(pan.Controls(2), Label) lblLastName.Text = lastName Dim lblFirstName As Label = DirectCast(pan.Controls(4), Label) lblFirstName.Text = firstName End Sub End Class

First of all, as we earlier did in the template files, we must cast the generic panel’s container to the right type (DataListItem, for example), so that we can access its properties, such as DataItem. However, while the template files were tailor-made for a specific template control, we want to make this class working with either the Repeater, the DataList or DataGrid. With the template files this wasn’t important because in case we wanted to switch from a DataList to a DataGrid we had just to make a little modification to a couple of text files.

In this case however things would be much more difficult, because we’d also have to recompile the codeand this could be even impossible if we don’t have the source code but are referencing the class from a compiled assembly developed by someone else. Therefore, you’d likely want to write a single class that can work with any of these three controls. For this reason, we check the type of the container with the TypeOf function, cast the container to the proper type and get an Object reference from the DataItem property, that represents the data item being bound to this item.

Before being able to read the values we need from the retrieved data item, we must follow a similar process though, because the data item can be of type DbDataRecord or DataRowView, according to whether the container Repeater / DataList or DataGrid’s DataSource is a DataReader or a DataTable/DataView, respectively. In the code above you see that we use again the TypeOf function to know the right type for the cast, do the cast and can finally read the wanted values and save them to local variables.

We’re near the end. At this point, we use the retrieved TitleOfCourtesy value to decide which background color we have to use to render the employee, and use it for the panel’s BackColor property. Finally, we get the references to the inner Label controls we added to the panel, by casting from the general Control returned by the panel’s Controls collection to Label, and then assigning their Text properties with the values read from the data item.

Comment and Contribute






(Maximum characters: 1200). You have 1200 characters left.



Thanks for your registration, follow us on our social networks to keep up-to-date