Creating Templated Controls


A template enables you to customize the layout of a control. Furthermore, a template can contain expressions that are not evaluated until runtime.

The ASP.NET 2.0 Framework supports two types of templates. First, you can create a one-way databinding template. You use a one-way databinding template to display data items. In a one-way databinding template, you use the Eval() expression to display the value of a data item.

Second, you have the option of creating a two-way databinding template. A two-way databinding template can be used not only to display data items, but to update data items. You can use the Bind() expression in a two-way databinding template to both display a data item and extract the value of a data item.

Typically, you use templates with a databound control. For example, the GridView, Repeater, DataList, FormView, and DetailsView controls all support an ItemTemplate that enables you to format the data items that these controls display. However, you can use a template even when you are not displaying a set of data items. For example, the Login control supports a LayoutTemplate that enables you to customize the appearance of the Login form.

This part of this chapter concentrates on creating non-databound controls that support templates. In the next part of this chapter, you learn how to use templates with databound controls.

Implementing the ITemplate Interface

You create a one-way databinding template by adding a property to a control that returns an object that implements the ITemplate interface. The ITemplate interface includes one method:

  • InstantiateIn Instantiates the contents of a template in a particular control.

You are not required to implement the InstantiateIn() method yourself. The ASP.NET Framework creates the method for you automatically. You call the InstantiateIn method in your control to add the contents of a template to your control.

For example, the control in Listing 33.1 represents an article. The Article control includes a template named ItemTemplate. The ItemTemplate is used to lay out the elements of the article: the title, author, and contents.

Listing 33.1. Article.vb

Imports System Imports System.Web Imports System.Web.UI Imports System.Web.UI.WebControls Namespace myControls     Public Class Article         Inherits CompositeControl         Private _title As String         Private _author As String         Private _contents As String         Private _itemTemplate As ITemplate         Public Property Title() As String             Get                 Return _title             End Get             Set(ByVal Value As String)                 _title = value             End Set         End Property         Public Property Author() As String             Get                 Return _author             End Get             Set(ByVal Value As String)                 _author = value             End Set         End Property         Public Property Contents() As String             Get                 Return _contents             End Get             Set(ByVal Value As String)                 _contents = value             End Set         End Property         <TemplateContainer(GetType(Article))> _         <PersistenceMode(PersistenceMode.InnerProperty)> _         Public Property ItemTemplate() As ITemplate             Get                 Return _itemTemplate             End Get             Set(ByVal Value As ITemplate)                 _itemTemplate = Value             End Set         End Property         Protected Overrides Sub CreateChildControls()             _itemTemplate.InstantiateIn(Me)         End Sub     End Class End Namespace 

Notice that the Article control contains a property named ItemTemplate that returns an object that implements the ITemplate interface. Notice that this property is decorated with two attributes: a TemplateContainer and a PersistenceMode attribute.

The TemplateContainer attribute is used to specify the type of control that will contain the template. In the case of the Article control, the template will be contained in the Article control itself. Therefore, the Article control's type is passed to the TemplateContainer attribute.

The PersistenceMode attribute indicates how a property is persisted in an ASP.NET page. The possible values are Attribute, EncodedInnerDefaultProperty, InnerDefaultProperty, and InnerProperty. We want to declare the ItemTemplate like this:

<custom:Article   runat="server">   <ItemTemplate>    ... template contents ...   </ItemTemplate> </custom:Article> 


Because we want to declare the ItemTemplate inside the Article control, the PersistenceMode attribute needs to be set to the value InnerProperty.

The Article control overrides the base WebControl class's CreateChildControls() method. The ItemTemplate is added as a child control to the Article control. Any controls contained in the template become child controls of the current control.

The page in Listing 33.2 illustrates how you can use the Article control and its ItemTemplate.

Listing 33.2. ShowArticle.aspx

<%@ Page Language="VB" %> <%@ Register TagPrefix="custom" Namespace="myControls" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <script runat="server">     Sub Page_Load()         Article1.Title = "Creating Templated Databound Controls"         Article1.Author = "Stephen Walther"         Article1.Contents = "Blah, blah, blah, blah..."         Article1.DataBind()     End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head  runat="server">     <title>Show Article</title> </head> <body>     <form  runat="server">     <div>     <custom:Article                  Runat="server">         <ItemTemplate>         <h1><%# Container.Title %></h1>         <em>By <%# Container.Author %></em>         <br /><br />         <%# Container.Contents %>         </ItemTemplate>     </custom:Article>     </div>     </form> </body> </html> 

When you open the page in Listing 33.2, the contents of the ItemTemplate are displayed (see Figure 33.1).

Figure 33.1. Using a template to display an article.


In the Page_Load() method, the Title, Author, and Contents properties of the article are set. Notice that these properties are used within databinding expressions within the Article control's ItemTemplate. For example, the value of the Title property is displayed with the following databinding expression:

<%# Container.Title %> 


The Container keyword refers to the current binding container. In this case, the binding container is the Article control itself. Therefore, you can refer to any property of the Article control by using the Container keyword.

Notice that the Article control's DataBind() method is called at the end of the Page_Load() method. Don't forget to call this method when you include databinding expressions in a template. If you don't call this method, then the databinding expressions are never evaluated and displayed.

Creating a Default Template

The previous section discussed the ITemplate interface's InstantiateIn() method. Normally, you don't implement the InstantiateIn() method; you let the ASP.NET Framework do it for you. However, if you want to supply a control with a default template, then you need to implement this method.

The modified Article control in Listing 33.3 includes a default template for the ItemTemplate. The default template is used when an ItemTemplate is not supplied.

Listing 33.3. ArticleWithDefault.vb

[View full width]

Imports System Imports System.Web Imports System.Web.UI Imports System.Web.UI.WebControls Namespace myControls     Public Class ArticleWithDefault         Inherits CompositeControl         Private _title As String         Private _author As String         Private _contents As String         Private _itemTemplate As ITemplate         Public Property Title() As String             Get                 Return _title             End Get             Set(ByVal Value As String)                 _title = value             End Set         End Property         Public Property Author() As String             Get                 Return _author             End Get             Set(ByVal Value As String)                 _author = value             End Set         End Property         Public Property Contents() As String             Get                 Return _contents             End Get             Set(ByVal Value As String)                 _contents = value             End Set         End Property         <TemplateContainer(GetType(ArticleWithDefault))> _         <PersistenceMode(PersistenceMode.InnerProperty)> _         Public Property ItemTemplate() As ITemplate             Get                 Return _itemTemplate             End Get             Set(ByVal Value As ITemplate)                 _itemTemplate = Value             End Set         End Property         Protected Overrides Sub CreateChildControls()             If _itemTemplate Is Nothing Then                 _itemTemplate = New ArticleDefaultTemplate()             End If             _itemTemplate.InstantiateIn(Me)         End Sub     End Class     Public Class ArticleDefaultTemplate         Implements ITemplate         Public Sub InstantiateIn(ByVal container As Control) Implements ITemplate. InstantiateIn             Dim lblTitle As New Label()             AddHandler lblTitle.DataBinding, AddressOf lblTitle_DataBinding             Dim lblAuthor As New Label()             AddHandler lblAuthor.DataBinding, AddressOf lblAuthor_DataBinding             Dim lblContents As New Label()             AddHandler lblContents.DataBinding, AddressOf lblContents_DataBinding             container.Controls.Add(lblTitle)             container.Controls.Add(New LiteralControl("<br />"))             container.Controls.Add(lblAuthor)             container.Controls.Add(New LiteralControl("<br />"))             container.Controls.Add(lblContents)         End Sub         Private Sub lblTitle_DataBinding(ByVal sender As Object, ByVal e As EventArgs)             Dim lblTitle As Label = CType(sender, Label)             Dim container As ArticleWithDefault = CType(lblTitle.NamingContainer,  ArticleWithDefault)             lblTitle.Text = container.Title         End Sub         Private Sub lblAuthor_DataBinding(ByVal sender As Object, ByVal e As EventArgs)             Dim lblAuthor As Label = CType(sender, Label)             Dim container As ArticleWithDefault = CType(lblAuthor.NamingContainer,  ArticleWithDefault)             lblAuthor.Text = container.Author         End Sub         Private Sub lblContents_DataBinding(ByVal sender As Object, ByVal e As EventArgs)             Dim lblContents As Label = CType(sender, Label)             Dim container As ArticleWithDefault = CType(lblContents.NamingContainer,  ArticleWithDefault)             lblContents.Text = container.Contents         End Sub     End Class End Namespace 

The control in Listing 33.3 is very similar to the control created in the previous section. However, notice that the CreateChildControls() method has been modified. The new version of the CreateChildControls() method tests whether there is an ItemTemplate. If there is no ItemTemplate, an instance of the ArticleDefaultTemplate class is created.

The ArticleDefaultTemplate class, which is also included in Listing 33.3, implements the ITemplate interface. In particular, the class implements the InstantiateIn() method. The instantiateIn() method creates all the controls that will appear in the template.

In Listing 33.3, three Label controls are created that correspond to the Title, Author, and Contents properties. Notice that the DataBinding event is handled for all three of these Label controls. When the DataBind() method is called, the DataBinding event is raised for each child control in the Article control. At that time, the values of the Title, Author, and Contents properties are assigned to the Text properties of the Label controls.

The page in Listing 33.4 illustrates how you can use the modified Article control.

Listing 33.4. ShowArticleWithDefault.aspx

<%@ Page Language="VB" %> <%@ Register TagPrefix="custom" Namespace="myControls" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <script runat="server">     Sub Page_Load()         ArticleWithDefault1.Title = "Creating Templated Databound Controls"         ArticleWithDefault1.Author = "Stephen Walther"         ArticleWithDefault1.Contents = "Blah, blah, blah, blah..."         ArticleWithDefault1.DataBind()     End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head  runat="server">     <title>Show Article with Default Template</title> </head> <body>     <form  runat="server">     <div>     <custom:ArticleWithDefault                  Runat="server" />     </div>     </form> </body> </html> 

The ArticleWithDefault control in Listing 33.4 does not include an ItemTemplate. When the page is displayed in a browser, the contents of the ItemTemplate are supplied by the ArticleDefaultTemplate class (see Figure 33.2).

Figure 33.2. Displaying an article with a default template.


Supporting Simplified Databinding

The databinding expressions used in the previous two sections might seem a little odd. For example, we used the following databinding expression to refer to the Title property:

<%# Container.Title %> 


When you use a databinding expression with one of the standard ASP.NET controls, such as the GridView control, you typically use a databinding expression that looks like this:

<%# Eval("Title") %> 


Why the difference? The standard ASP.NET controls support a simplified databinding syntax. If you want to support this simplified syntax in your custom controls, then you must implement the IDataItemContainer interface.

The IDataItemContainer includes the following three properties, which you are required to implement:

  • DataItem Returns the value of the data item.

  • DataItemIndex Returns the index of the data item from its data source.

  • DisplayIndex Returns the index of the data item as it is displayed in a control.

Typically, you implement the IDataItemContainer when creating a databound control. For example, you wrap up each record retrieved from a database table in an object that implements the IDataItemContainer interface. That way, you can use a simplified databinding expression to refer to the value of a particular database record column.

In this section, we create a non-databound control that supports the simplified databinding syntax. The control is named the Product control, and it is included in Listing 33.5.

Listing 33.5. Product.vb

[View full width]

Imports System Imports System.Web Imports System.Web.UI Imports System.Web.UI.WebControls Namespace myControls     Public Class Product         Inherits CompositeControl         Private _itemTemplate As ITemplate         Private _item As ProductItem         Public Property Name() As String             Get                 EnsureChildControls()                 Return _item.Name             End Get             Set(ByVal Value As String)                 EnsureChildControls()                 _item.Name = Value             End Set         End Property         Public Property Price() As Decimal             Get                 EnsureChildControls()                 Return _item.Price             End Get             Set(ByVal Value As Decimal)                 EnsureChildControls()                 _item.Price = Value             End Set         End Property         <TemplateContainer(GetType(ProductItem))> _         <PersistenceMode(PersistenceMode.InnerProperty)> _         Public Property ItemTemplate() As ITemplate             Get                 Return _itemTemplate             End Get             Set(ByVal Value As ITemplate)                 _itemTemplate = Value             End Set         End Property         Protected Overrides Sub CreateChildControls()             _item = New ProductItem()             _itemTemplate.InstantiateIn(_item)             Controls.Add(_item)         End Sub     End Class     Public Class ProductItem         Inherits WebControl         Implements IDataItemContainer         Private _name As String         Private _price As Decimal         Public Property Name() As String             Get                 Return _name             End Get             Set(ByVal Value As String)                 _name = Value             End Set         End Property         Public Property Price() As Decimal             Get                 Return _price             End Get             Set(ByVal Value As Decimal)                 _price = Value             End Set         End Property         Public ReadOnly Property DataItem() As Object Implements IDataItemContainer.DataItem             Get                 Return Me             End Get         End Property         Public ReadOnly Property DataItemIndex() As Integer Implements IDataItemContainer .DataItemIndex             Get                 Return 0             End Get         End Property         Public ReadOnly Property DisplayIndex() As Integer Implements IDataItemContainer .DisplayIndex             Get                 Return 0             End Get         End Property     End Class End Namespace 

The file in Listing 33.5 actually contains two classes: the Product and the ProductItem class. The Product control includes an ItemTemplate property. Notice that the TemplateContainer attribute that decorates this property associates the ProductItem class with the ItemTemplate.

In the CreateChildControls() method, the ItemTemplate is instantiated into the ProductItem class. The ProductItem class, in turn, is added to the controls collection of the Product class.

The ProductItem class implements the IDataItemContainer interface. Implementing the DataItemIndex and DisplayIndex properties is a little silly because there is only one data item. However, you are required to implement all the properties of an interface.

The page in Listing 33.6 illustrates how you can use the Product control with the simplified databinding syntax.

Listing 33.6. ShowProduct.aspx

<%@ Page Language="VB" %> <%@ Register TagPrefix="custom" Namespace="myControls" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <script runat="server">     Sub Page_Load()         Product1.Name = "Laptop Computer"         Product1.Price = 1254.12         Product1.DataBind()     End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head  runat="server">     <title>Show Product</title> </head> <body>     <form  runat="server">     <div>     <custom:Product                  Runat="Server">         <ItemTemplate>         Name: <%# Eval("Name") %>         <br />         Price: <%# Eval("Price", "{0:c}") %>         </ItemTemplate>     </custom:Product>     </div>     </form> </body> </html> 

Notice that the Eval() method is used in the Product control's ItemTemplate. For example, the expression Eval("Name") is used to display the product name. If you prefer, you can still use the Container.Name syntax. However, the Eval() syntax is more familiar to ASP.NET developers.

Supporting Two-Way Databinding

Two-way databinding is a new feature of the ASP.NET 2.0 Framework. Two-way databinding enables you to extract values from a template. You can use a two-way databinding expression not only to display the value of a data item, but to update the value of a data item.

You create a template that supports two-way databinding expressions by creating a property that returns an object that implements the IBindableTemplate interface. This interface inherits from the ITemplate interface. It has the following two methods:

  • InstantiateIn Instantiates the contents of a template in a particular control.

  • ExTRactValues Returns a collection of databinding expression values from a template.

For example, the ProductForm control in Listing 33.7 represents a form for editing an existing product. The control includes a property named EditItemTemplate that represents a two-way databinding template.

Listing 33.7. ProductForm.vb

[View full width]

Imports System Imports System.Web Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.ComponentModel Imports System.Collections.Specialized Namespace myControls     Public Class ProductForm         Inherits CompositeControl         Public Event ProductUpdated As EventHandler         Private _editItemTemplate As IBindableTemplate         Private _item As ProductFormItem         Private _results As IOrderedDictionary         Public ReadOnly Property Results() As IOrderedDictionary             Get                 Return _results             End Get         End Property         Public Property Name() As String             Get                 EnsureChildControls()                 Return _item.Name             End Get             Set(ByVal Value As String)                 EnsureChildControls()                 _item.Name = Value             End Set         End Property         Public Property Price() As Decimal             Get                 EnsureChildControls()                 Return _item.Price             End Get             Set(ByVal Value As Decimal)                 EnsureChildControls()                 _item.Price = Value             End Set         End Property         <TemplateContainer(GetType(ProductFormItem), BindingDirection.TwoWay)> _         <PersistenceMode(PersistenceMode.InnerProperty)> _         Public Property EditItemTemplate() As IBindableTemplate             Get                 Return _editItemTemplate             End Get             Set(ByVal Value As IBindableTemplate)                 _editItemTemplate = Value             End Set         End Property         Protected Overrides Sub CreateChildControls()             _item = New ProductFormItem()             _editItemTemplate.InstantiateIn(_item)             Controls.Add(_item)         End Sub         Protected Overrides Function OnBubbleEvent(ByVal source As Object, ByVal args As  EventArgs) As Boolean             _results = _editItemTemplate.ExtractValues(_item)             RaiseEvent ProductUpdated(Me, EventArgs.Empty)             Return True         End Function     End Class     Public Class ProductFormItem         Inherits WebControl         Implements IDataItemContainer         Private _name As String         Private _price As Decimal         Public Property Name() As String             Get                 Return _name             End Get             Set(ByVal Value As String)                 _name = Value             End Set         End Property         Public Property Price() As Decimal             Get                 Return _price             End Get             Set(ByVal Value As Decimal)                 _price = Value             End Set         End Property         Public ReadOnly Property DataItem() As Object Implements IDataItemContainer.DataItem             Get                 Return Me             End Get         End Property         Public ReadOnly Property DataItemIndex() As Integer Implements IDataItemContainer .DataItemIndex             Get                 Return 0             End Get         End Property         Public ReadOnly Property DisplayIndex() As Integer Implements IDataItemContainer .DisplayIndex             Get                 Return 0             End Get         End Property     End Class End Namespace 

You should notice two special things about the EditItemTemplate property. First, notice that the property returns an object that implements the IBindableTemplate interface. Second, notice that the TemplateContainer attribute that decorates the property includes a BindingDirection parameter. You can assign one of two possible values to BindingDirection: OneWay and TwoWay.

The ProductForm includes an OnBubbleEvent() method. This method is called when a child control of the ProductForm control raises an event. For example, if someone clicks a Button control contained in the EditItemTemplate, the OnBubbleEvent() method is called.

In Listing 33.7, the OnBubbleEvent() method calls the EditItemTemplate's ExtractValues() method. This method is supplied by the ASP.NET Framework because the EditItemTemplate is marked as a two-way databinding template.

The ExTRactValues() method returns an OrderedDictionary collection that contains name/value pairs that correspond to each of the databinding expressions contained in the EditItemTemplate. The ProductForm control exposes this collection of values with its Results property. After the values are extracted, the control raises a ProductUpdated event.

The page in Listing 33.8 illustrates how you can use the ProductForm control to update the properties of a product.

Listing 33.8. ShowProductForm.aspx

<%@ Page Language="VB" %> <%@ Register TagPrefix="custom" Namespace="myControls" %> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <script runat="server">     sub Page_Load()         If Not Page.IsPostBack Then             ProductForm1.Name = "Laptop"             ProductForm1.Price = 433.12             ProductForm1.DataBind()         End If     End Sub     Sub ProductForm1_ProductUpdated(ByVal sender As Object, ByVal e As EventArgs)         lblName.Text = ProductForm1.Results("Name").ToString()         lblPrice.Text = ProductForm1.Results("Price").ToString()     End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head  runat="server">     <title>Show ProductForm</title> </head> <body>     <form  runat="server">     <div>     <custom:ProductForm                  OnProductUpdated="ProductForm1_ProductUpdated"         Runat="server">         <EditItemTemplate>         <asp:Label                          Text="Product Name:"             AssociatedControl             Runat="server" />         <asp:TextBox                          Text='<%# Bind("Name") %>'             Runat="server" />         <br /><br />         <asp:Label                          Text="Product Price:"             AssociatedControl             Runat="server" />         <asp:TextBox                          Text='<%# Bind("Price") %>'             Runat="server" />         <br /><br />         <asp:Button                          Text="Update"             Runat="server" />         </EditItemTemplate>     </custom:ProductForm>     <hr />     New Product Name:     <asp:Label                  Runat="server" />     <br /><br />     New Product Price:     <asp:Label                  Runat="server" />     </div>     </form> </body> </html> 

In the Page_Load() method in Listing 33.8, the ProductForm Name and Price properties are set. Next, the DataBind() is called in order to cause the ProductForm control to evaluate its databinding expressions.

Notice that the ProductForm control's EditItemTemplate includes Bind() expressions instead of Eval() expressions. You use Bind() expressions in a two-way databinding template.

The EditItemTemplate includes a Button control. When you click the Button control, the ProductForm control's OnBubbleEvent() method executes, the values are retrieved from the EditItemTemplate, and the ProductUpdated event is raised.

The page in Listing 33.8 handles the ProductUpdated event and displays the new values with two Label controls (see Figure 33.3).

Figure 33.3. Using a two-way databinding template.





ASP. NET 2.0 Unleashed
ASP.NET 2.0 Unleashed
ISBN: 0672328232
EAN: 2147483647
Year: 2006
Pages: 276

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