Binding Templated Columns

Creating Templates Dynamically

Typically, the layout code of templated columns is defined at design time, but you might face situations in which using predefined templates is not the optimal solution and you need a dynamic template. For example, if you know in advance that a lot of changes must be applied at run time via events such as ItemCreated and ItemDataBound, there is no reason to define a static template, forcing the control to support a double effort: processing the template first and the changes next. Also, when users can change among different views of the same data, a dynamic template is preferable. Whatever your reason for adding templated columns dynamically, you face the problem of how to create a template programmatically. In such a situation, using an external ASCX file can help.

Loading Templates from Files

A template for a column property is a class that implements the ITemplate interface. An instance of such an object can be created using the LoadTemplate method of the Page class. LoadTemplate takes only one argument: the name of the text file that describes the template. The file must have an ASCX extension ASCX is the typical extension of user control files, formerly known as pagelets. You create a file-based template column using the following code:

TemplateColumn tc = new TemplateColumn(); tc.ItemTemplate = Page.LoadTemplate("template.ascx");

The template file can be written in any .NET language and not necessarily in the language of the page. The Page.LoadTemplate method can be used to load the layout code for any template property of the column, including Edit ItemTemplate and HeaderTemplate.

note

There is no way to load a column template from an external file at design time. The <ItemTemplate> ASP.NET tag does not support an src attribute or an attribute similar to src.

The ASCX user control you use to populate the templates of a column defines the HTML and the ASP.NET controls you want to employ. The following code shows the ASCX file that concatenates TitleOfCourtesy, LastName, and FirstName:

<%@ Language="C#" %> <%# DataBinder.Eval(((DataGridItem) Container).DataItem, "TitleOfCourtesy") + " " + "<b>" + DataBinder.Eval(((DataGridItem) Container).DataItem, "LastName") + "</b>, " + DataBinder.Eval(((DataGridItem) Container).DataItem, "FirstName") %>

The user control must indicate its language, even if the language is the same one being used in the hosting page. The following code shows the same user control written in Visual Basic .NET. You can use this code interchangeably with the previous code.

<%@ Language="VB" %> <%# DataBinder.Eval( _ CType(Container, DataGridItem).DataItem, "TitleOfCourtesy") + _ "<b>" + _ DataBinder.Eval( _ CType(Container, DataGridItem).DataItem, "LastName") + _ "</b>, " + _ DataBinder.Eval( _ CType(Container, DataGridItem).DataItem, "FirstName") %>

Managing Multiple Views for a Column

The ability to dynamically load templates from disk can be exploited to build an interesting application that lets users change the view of a templated column. Figure 3-8 shows what I mean.

Figure 3-8

This application uses dynamically loaded templates to allow users to change the column view.

The DataGrid control in Figure 3-8 shows a footer that has been dynamically modified to span all columns. The footer contains a drop-down list with the available views for the templated column. The user selects the desired view and then clicks the Apply link button to enable it.

Setting Up the Footer

You can place the controls for selecting the view mode anywhere on the page. A good place is in the footer of the templated column. In this case, you can use the <FooterTemplate> tag:

<FooterTemplate> <b>View:</b> <asp:dropdownlist runat="server" /> <asp:linkbutton runat="server" Text="Apply" CommandName="ApplyView" /> <FooterTemplate>

By default, the DataGrid control s footer is disabled, and you turn it on by setting the ShowFooter attribute to true. Usually the footer is a blank row added at the bottom of the grid s current page and needs a different structure from the rest of the rows. Let s say you want the footer to span all the columns in the grid to become a table row with a single cell. Once again you have to resort to the ItemCreated event to accomplish this kind of structural manipulation of the grid items. The following code shows the strategy for creating the footer shown in Figure 3-9.

if (lit == ListItemType.Footer) { // Remove 1st and 3rd columns in the original schema e.Item.Cells.RemoveAt(0); e.Item.Cells.RemoveAt(1); e.Item.Cells[0].ColumnSpan = 3; // Populate the drop-down list with available views // Each view corresponds to an ASCX file in the current folder DropDownList ddViews = (DropDownList) e.Item.FindControl("ddViews"); ListItem l; l = new ListItem("Ms. Surname, Name", "courtesylastfirst.ascx"); ddViews.Items.Add(l); l = new ListItem("Name Surname - (Ms.)", "firstlastcourtesy.ascx"); ddViews.Items.Add(l); // Select the previously selected element, if any // Need to preserve list state across postback events ddViews.SelectedIndex = Convert.ToInt32(ViewState["ViewIndex"]); }

Figure 3-9

In this grid, you use the ItemCreated event to create a footer that becomes a table row with a single cell.

Applying the View

The view in Figure 3-9 is enabled when the user clicks the Apply link button. This button has been assigned a CommandName property. Any clickable element within the body of the DataGrid control raises an ItemCommand event when the user clicks it. This event is handled in much the same way it is for the DataList control, which I covered in Chapter 1.

When you handle the ItemCommand event, you distinguish among the various elements that might have fired the event by using the CommandName attribute. In the example of Figure 3-9, I defined a link button with a command name of ApplyView. To handle the button click, you need code such as the following:

public void ItemCommand(Object sender, DataGridCommandEventArgs e) { if (e.CommandName == "ApplyView") { DropDownList ddViews = (DropDownList)e.Item.FindControl("ddViews"); String strFile = ddViews.SelectedItem.Value; ViewState["CurrentViewFile"] = strFile; ViewState["ViewIndex"] = ddViews.SelectedIndex.ToString(); UpdateView(); } }

The ItemCommand handler is registered through the OnItemCommand attribute of the <asp:datagrid> tag.

<asp:datagrid runat="server"  OnItemCommand="ItemCommand"  />

You first retrieve the instance of the drop-down list control. The control ddViews is not accessible at the page level because of the implementation of ASP.NET templates. Each template runs in a separate naming container that makes it impossible for the run time processing the ASP.NET page to retrieve the drop-down list control by name within the page scope. So you cannot use ddViews, which is the ID of the drop-down list, as the programmatic identifier of the control in the page code. The ID ddViews makes sense only in the context of the template a sort of child control with its own namespace that contains it. To retrieve a valid instance of the drop-down list control, then, you need to resort to the following code:

DropDownList ddViews = (DropDownList) e.Item.FindControl("ddViews");

Notice that FindControl called from the Page object or the DataGrid object will not work, because FindControl knows how to locate a control only in the current naming container. In the body of ItemCommand, when the command name is ApplyView, e.Item represents the footer. Calling FindControl within the range of the footer turns out to be successful.

In ItemCommand, once you hold a reference to the drop-down list control, you pick up the name of the currently selected view and store it, as well as its index, in the Attributes collection of the grid. The drop-down list s Value property contains the name of the ASCX file that represents the view, whereas its Text property points to a display name. The name of the file and its index need to be persisted across multiple invocations of the same page to guarantee that the control s state can be correctly restored.

Refreshing the View

Once you know the name of the file to load the template from, you are pretty much finished. The only remaining work is letting the DataGrid control know about the new template for one of its columns. In our running example, the templated column is the second column. The next code snippet explains how to change the item template dynamically. The full source code for the ColumnView.aspx application is available on the companion CD.

TemplateColumn tc = (TemplateColumn) grid.Columns[1]; tc.ItemTemplate = Page.LoadTemplate((String)ViewState["CurrentViewFile"]); grid.DataBind();

The final call to DataBind causes the grid to redraw its user interface, taking into account all the changes to the columns.

note

The working directory for LoadTemplate is the root of the current Web application, so you don t need to indicate a fully qualified URL. However, LoadTemplate requires a virtual path and throws an exception if you pass it an absolute file system path with drive and directory information.

Loading Templates from Strings

Unlike many other methods that are expected to read from disk, the Page object s LoadTemplate method does not support streams and writers. If this support were possible, then in-memory strings could have been used to create dynamic templates. If you don t want to, or can t afford to, make your application dependent on external ASCX files, how can you create dynamic templates? Typically, you don t want to deal with ASCX files because having several file names hard coded in the source is too restrictive. Another good reason to avoid disk-based templates is that it results in the template code being just one of the configuration parameters of the application. In this case, in fact, you might have all this information stored in a centralized medium such as a SQL Server table or an XML file.

Is there a way to dynamically create a template from a string? I haven t found any documentation that says it is possible or how to do it, but it may be that the documentation hasn t been written yet. After looking at the programming interface of the involved classes, I haven t been able to discover a way to do this yet. However, nothing really prevents you from creating a temporary file, writing the string that represents the layout, and loading a template from the temporary file. When you create temporary files from within an ASP.NET application, make sure that the file name is really unique for each concurrent session. For this purpose, use the Session ID or create a unique temporary file through the static method, Path.GetTempFileName. Bear in mind that the LoadTemplate method assumes it has been given a virtual path. On the other hand, stream and writer classes require absolute paths and don t know how to cope with virtual paths. As a result, you come up with the following code to create and load a string-based template.

// Create the column object TemplateColumn bc = new TemplateColumn(); // Create the temp file and write the template code String tmp = Session.SessionID + ".ascx"; StreamWriter sw = new StreamWriter(Server.MapPath(tmp)); sw.Write(strLayoutCode); sw.Close(); // Load the template from the temp file and add the column bc.ItemTemplate = Page.LoadTemplate(tmp); grid.Columns.Add(bc); // Delete the temp file File.Delete(Server.MapPath(tmp));

You need to use Server.MapPath to map a URL from a virtual to a physical path. You need to do this only when working with streams and files.

tip

Use the StringBuilder class to build the text of the template instead of concatenating strings with the plus sign (+) operator. The Append method (part of the StringBuilder class) really appends a given string to the internal buffer, whereas the plus sign (+) operator creates a brand new string that is the sum of the two.

Implementing ITemplate

When the ASP.NET run time processes a template, it parses the string, extracting the definition of the various controls that actually make it. These controls are then instantiated and added to the Controls collection of the naming container typically a DataGridItem object. You can implement this pattern yourself by writing a made-to-measure class that inherits from ITemplate. A living instance of this class can then be assigned to any template property such as ItemTemplate.

The ITemplate interface has only one method, which is named InstantiateIn. The method is called to populate the user interface of the container control with instances of child controls in accordance with the expected template. You can certainly come up with a flexible and configurable class that reads from an external source the controls to create and bind to data. More simply, though, you might want to write ad-hoc classes one for each needed template. Figure 3-10 shows just this.

Figure 3-10

Write the template text and generate a new column dynamically.

Structure of ITemplate Classes

The ITemplate class can be defined in the <script> section of an ASP.NET page as well as in separate C# or Visual Basic .NET class files that you link to the project. Another good place for this kind of code is in the code-behind file for the ASP.NET pages that use it. An ITemplate-based class looks like in the following code.

class LastFirstNameTemplate : ITemplate { public void InstantiateIn(Control container) {...} private void BindLastName(Object s, EventArgs e) {...} private void BindFirstName(Object s, EventArgs e) {...} }

In the body of InstantiateIn, you create instances of controls and add them to the specified container. For DataGrid controls, the container is an object of type DataGridItem. It will be DataListItem for a DataList control. In general, a container is any class that implements the INamingContainer interface.

If the control being added to the container s Controls collection has to be bound to a data source column, then you also register your own handler for the DataBinding event. When the event occurs, you retrieve the text out of the data source and refresh the user interface of the control. When defined for a server control, the DataBinding event handler is expected to resolve all data-binding expressions in the server control and in any of its children. Let s consider the layout that we repeatedly encountered earlier:

<%# "<b>" + DataBinder.Eval(Container.DataItem, "lastname") + "</b>, " + DataBinder.Eval(Container.DataItem, "firstname") %>

The following code demonstrates a template class that obtains the same results.

public class LastFirstNameTemplate : ITemplate { public void InstantiateIn(Control container) { container.Controls.Add(new LiteralControl("<b>")); Label lblLName = new Label(); lblLName.DataBinding += new EventHandler(this.BindLastName); container.Controls.Add(lblLName); container.Controls.Add(new LiteralControl("</b>, ")); Label lblFName = new Label(); lblFName.DataBinding += new EventHandler(this.BindFirstName); container.Controls.Add(lblFName); } private void BindLastName(Object sender, EventArgs e) { Label l = (Label) sender; DataGridItem container = (DataGridItem) l.NamingContainer; l.Text = ((DataRowView) container.DataItem)["lastname"].ToString(); } private void BindFirstName(Object sender, EventArgs e) { Label l = (Label) sender; DataGridItem container = (DataGridItem) l.NamingContainer; l.Text = ((DataRowView) container.DataItem)["firstname"].ToString(); } }

The DataBinding Event Handler

A DataBinding event handler accomplishes two tasks. If programmed correctly, it should first take and hold the underlying data item. Second, it has to refresh the user interface of the bound control to reflect data binding.

A reference to the involved control can be obtained through the always declared, but not frequently used, sender parameter. The container that hosts the control is returned by the NamingContainer property of the control itself. At this point, you have all that you need to set up and use another well-known ASP.NET expression: Container.DataItem. The type of the data item depends on the data source associated with the DataGrid. In most real-world scenarios, it will be DataRowView. What remains is to access a particular column on the row and set the control s bound properties.

note

Nearly identical code can be used to dynamically create templates for the DataList and the Repeater controls. The only difference is in the type casting required in the DataBinding event handlers.

The full source for the ITemplateClass.aspx application can be found on the companion CD.



Building Web Solutions with ASP. NET and ADO. NET
Building Web Solutions with ASP.Net and ADO.NET
ISBN: 0735615780
EAN: 2147483647
Year: 2002
Pages: 75
Authors: Dino Esposito

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net