Working with Control Property Collections


When you build more complex controls, you often need to represent a collection of items. For example, the standard ASP.NET DropDownList control contains one or more ListItem controls that represent individual options in the DropDownList. The GridView control can contain one or more DataBoundField controls that represent particular columns to display.

In this section, we build several controls that represent a collection of items. We build multiple content rotator controls that randomly display HTML content, as well as a server-side tab control that renders a tabbed view of content.

Using the ParseChildren Attribute

When building a control that contains a collection of child controls, you need to be aware of an attribute named the ParseChildren attribute. This attribute determines how the content contained in a control is parsed.

When the ParseChildren attribute has the value TRue, then content contained in the control is parsed as properties of the containing control. If the control contains child controls, then the child controls are parsed as properties of the containing control. (The attribute really should have been named the ParseChildrenAsProperties attribute.)

When the ParseChildren attribute has the value False, then no attempt is made to parse a control's child controls as properties. The content contained in the control is left alone.

The default value of the ParseChildren attribute is False. However, the WebControl class overrides this default value and sets the ParseChildren attribute to the value to true. Therefore, you should assume that ParseChildren is False when used with a control that inherits directly from the System.Web.UI.Control class, but assume that ParseChildren is true when used with a control that inherits from the System.Web.UI.WebControls.WebControl class.

Imagine, for example, that you need to create a content rotator control that randomly displays content in a page. There are two ways of creating this control, depending on whether ParseChildren has the value true or False.

The control in Listing 31.24 illustrates how you can create a content rotator control when ParseChildren has the value False.

Listing 31.24. ContentRotator.vb

Imports System Imports System.Web.UI Imports System.Web.UI.WebControls Namespace myControls     <ParseChildren(False)> _     Public Class ContentRotator         Inherits WebControl         Protected Overrides Sub AddParsedSubObject(ByVal obj As Object)             If TypeOf obj Is Content Then                 MyBase.AddParsedSubObject(obj)             End If         End Sub         Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)             Dim rnd As Random = New Random()             Dim index As Integer = rnd.Next(Me.Controls.Count)             Me.Controls(index).RenderControl(writer)         End Sub     End Class     Public Class Content         Inherits Control     End Class End Namespace 

The file in Listing 31.24 actually contains two controls: a ContentRotator control and a Content control. The ContentRotator control randomly selects a single Content control from its child controls and renders the Content control to the browser. This all happens in the control's RenderContents() method.

Notice that the ParseChildren attribute has the value False in Listing 31.24. If you neglected to add this attribute, then the Content controls would be parsed as properties of the ContentRotator control and you would get an exception.

Note

The AddParsedSubObject() method is discussed in the next section.


The page in Listing 31.25 illustrates how you can use the ContentRotator and Content controls (see Figure 31.11).

Listing 31.25. ShowContentRotator.aspx

[View full width]

<%@ 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"> <html xmlns="http://www.w3.org/1999/xhtml" > <head  runat="server">     <title>Show ContentRotator</title> </head> <body>     <form  runat="server">     <div>     <custom:ContentRotator                  Runat="server">         <custom:Content                          Runat="server">             First Content Item         </custom:Content>         <custom:Content                          Runat="server">             Second Content Item             <asp:Calendar                                  Runat="server" />         </custom:Content>         <custom:Content                          Runat="server">             Third Content Item         </custom:Content>     </custom:ContentRotator>     </div>     </form> </body> </html> 

Figure 31.11. Randomly displaying content with the ContentRotator control.


If ParseChildren is not set to the value False, then you need to add a property to your control that corresponds to the child controls contained in the control. For example, the control in Listing 31.26 includes an Items property that represents the Item controls contained in the control.

Listing 31.26. ItemRotator.vb

Imports System Imports System.Collections Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.ComponentModel Namespace myControls     <ParseChildren(True, "Items")> _     Public Class ItemRotator         Inherits CompositeControl         Private _items As ArrayList = New ArrayList()         <Browsable(False)> _         Public ReadOnly Property Items() As ArrayList             Get                 Return _items             End Get         End Property         Protected Overrides Sub CreateChildControls()             Dim rnd As Random = New Random()             Dim index As Integer = rnd.Next(_items.Count)             Dim item As Control = CType(_items(index), Control)             Me.Controls.Add(item)         End Sub     End Class     Public Class Item         Inherits Control     End Class End Namespace 

In Listing 31.26, the second value passed to the ParseChildren attribute is the name of a control property. The contents of the ItemRotator are parsed as items of the collection represented by the specified property.

Unlike the ContentRotator control, the controls contained in the ItemRotator control are not automatically parsed into child controls. After the CreateChildControls() method executes, the ItemRotator control contains only one child control (the randomly selected Item control).

The page in Listing 31.27 illustrates how you can use the ItemRotator control to randomly display page content.

Listing 31.27. ShowItemRotator.aspx

[View full width]

<%@ Page Language="VB" Trace="true" %> <%@ 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"> <html xmlns="http://www.w3.org/1999/xhtml" > <head  runat="server">     <title>Show ItemRotator</title> </head> <body>     <form  runat="server">     <div>     <custom:ItemRotator                  Runat="server">         <custom:item  runat="server">             First Item         </custom:item>         <custom:item  runat="server">             Second Item             <asp:Calendar                                  Runat="server" />         </custom:item>         <custom:item  runat="server">             Third Item         </custom:item>     </custom:ItemRotator>     </div>     </form> </body> </html> 

There is no requirement that the contents of a control must be parsed as controls. When building a control that represents a collection of items, you can also represent the items as objects. For example, the ImageRotator control in Listing 31.28 contains ImageItem objects. The ImageItem class does not represent a control.

Listing 31.28. ImageRotator.vb

Imports System Imports System.Collections Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.ComponentModel Namespace myControls     <ParseChildren(True, "ImageItems")> _     Public Class ImageRotator         Inherits WebControl         Private _imageItems As ArrayList = New ArrayList()         Public ReadOnly Property ImageItems() As ArrayList             Get                 Return _imageItems             End Get         End Property         Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)             Dim rnd As Random = New Random()             Dim img As ImageItem = CType(_imageItems(rnd.Next(_imageItems.Count)), ImageItem)             writer.AddAttribute(HtmlTextWriterAttribute.Src, img.ImageUrl)             writer.AddAttribute(HtmlTextWriterAttribute.Alt, img.AlternateText)             writer.RenderBeginTag(HtmlTextWriterTag.Img)             writer.RenderEndTag()         End Sub     End Class     Public Class ImageItem         Private _imageUrl As String         Private _alternateText As String         Public Property ImageUrl() As String             Get                 Return _imageUrl             End Get             Set(ByVal Value As String)                 _imageUrl = value             End Set         End Property         Public Property AlternateText() As String             Get                 Return _alternateText             End Get             Set(ByVal Value As String)                 _alternateText = value             End Set         End Property     End Class End Namespace 

Notice that the ImageItem class is just a class. It does not derive from the base Control class. Because the ImageItem class does nothing more than represent a couple of properties, there is no reason to make it a full-blown control.

The page in Listing 31.29 illustrates how you can use the ImageRotator control to display different images randomly.

Listing 31.29. ShowImageRotator.aspx

[View full width]

<%@ Page Language="VB" Trace="true" %> <%@ 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"> <html xmlns="http://www.w3.org/1999/xhtml" > <head  runat="server">     <title>Show ImageRotator</title> </head> <body>     <form  runat="server">     <div>     <custom:ImageRotator                  Runat="server">         <custom:ImageItem ImageUrl="Image1.gif" AlternateText="Image 1" />         <custom:ImageItem ImageUrl="Image2.gif" AlternateText="Image 2" />         <custom:ImageItem ImageUrl="Image3.gif" AlternateText="Image 3" />     </custom:ImageRotator>     </div>     </form> </body> </html> 

The page in Listing 31.29 has tracing enabled. If you look in the Control Tree section, you'll see that the ImageRotator control does not contain any child controls (see Figure 31.12).

Figure 31.12. The ShowImageRotator.aspx page control tree.


Using the AddParsedSubObject() Method

When the ParseChildren attribute has the value false, the contents of a control are automatically added to the control's collection of child controls (represented by the Controls property). It is important to understand that all content contained in the control, even carriage returns and spaces, are added to the controls collection.

Any content contained in a control that does not represent a server-side control is parsed into a Literal control. In some cases, you might want to allow only a certain type of control to be added to the Controls collection.

The AddParsedSubObject() method is called as each control is added to the Controls collection. By overriding the AddParsedSubObject() method, you can block certain types of controlssuch as Literal controlsfrom being added to the Controls collection.

For example, the ContentRotator control in Listing 31.20 overrides the base AddParsedSubObject() method and prevents anything that is not a Content control from being added to the ContentRotator Controls collection. If you removed the AddParsedSubObject() method from this control, then all of the carriage returns and spaces between the Content controls would be added to the Controls collection as Literal controls.

Using a ControlBuilder

The AddParsedSubObject() method enables you to specify which parsed controls get added to a Controls collection. Sometimes, you must take even more control over the parsing of a control.

When the ASP.NET Framework parses a page, the Framework uses a special type of class called a ControlBuilder class. You can modify the way in which the content of a control is parsed by associating a custom ControlBuilder with a control.

Here's a list of the most useful methods supported by the ControlBuilder class:

  • AllowWhiteSpaceLiterals() Enables you to trim white space from the contents of a control.

  • AppendLiteralString() Enables you trim all literal content from the contents of a control.

  • GetChildControlType() Enables you to specify how a particular tag gets parsed into a control.

The GetChildControlType() method is the most useful method. It enables you to map tags to controls. You can use the GetChildControlType() method to map any tag to any control.

For example, the file in Listing 31.30 contains a ServerTabs control that renders multiple tabs (see Figure 31.13). Each tab is represented by a Tab control.

Figure 31.13. Using the ServerTabs control.


Listing 31.30. ServerTabs.vb

[View full width]

Imports System Imports System.Collections Imports System.Web.UI Imports System.Web.UI.WebControls Namespace myControls     <ControlBuilder(GetType(ServerTabsBuilder))> _     <ParseChildren(False)> _     Public Class ServerTabs         Inherits WebControl         Implements IPostBackEventHandler         Public Property SelectedTabIndex() As Integer             Get                 If ViewState("SelectedTabIndex") Is Nothing Then                     Return 0                 Else                     Return CType(ViewState("SelectedTabIndex"), Integer)                 End If             End Get             Set(ByVal Value As Integer)                 ViewState("SelectedTabIndex") = Value             End Set         End Property         Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)             Dim i As Integer             For i = 0 To Me.Controls.Count - 1 Step i + 1                 Dim tab As ServerTab = CType(Me.Controls(i), ServerTab)                 Dim eRef As String = Page.ClientScript.GetPostBackClientHyperlink(Me, i .ToString())                 If SelectedTabIndex = i Then                     writer.AddAttribute(HtmlTextWriterAttribute.Class, "tab selectedTab")                 Else                     writer.AddAttribute(HtmlTextWriterAttribute.Class, "tab")                 End If                 writer.RenderBeginTag(HtmlTextWriterTag.Div)                 writer.AddAttribute(HtmlTextWriterAttribute.Href, eRef)                 writer.RenderBeginTag(HtmlTextWriterTag.A)                 writer.Write(tab.Text)                 writer.RenderEndTag() ' A                 writer.RenderEndTag() ' Tab Div             Next             writer.Write("<br style='clear:both' />")             writer.AddAttribute(HtmlTextWriterAttribute.Class, "tabContents")             writer.RenderBeginTag(HtmlTextWriterTag.Div)             Me.Controls(SelectedTabIndex).RenderControl(writer)             writer.RenderEndTag() ' Tab Contents DIV         End Sub         Protected Overrides Sub AddParsedSubObject(ByVal obj As Object)             If TypeOf obj Is ServerTab Then                 MyBase.AddParsedSubObject(obj)             End If         End Sub         Protected Overrides ReadOnly Property TagKey() As HtmlTextWriterTag             Get                 Return HtmlTextWriterTag.Div             End Get         End Property         Public Sub RaisePostBackEvent(ByVal eventArgument As String) Implements  IPostBackEventHandler.RaisePostBackEvent             SelectedTabIndex = Int32.Parse(eventArgument)         End Sub     End Class     Public Class ServerTabsBuilder         Inherits ControlBuilder         Public Overrides Function GetChildControlType(ByVal tagName As String, ByVal  attribs As IDictionary) As Type             If String.Compare(tagName, "tab", True) = 0 Then                 Return GetType(ServerTab)             Else                 Return Nothing             End If         End Function     End Class     Public Class ServerTab         Inherits Control         Private _Text As String         Public Property Text() As String             Get                 Return _Text             End Get             Set(ByVal Value As String)                 _Text = value             End Set         End Property     End Class End Namespace 

Notice that the ServerTabs class is decorated with a ControlBuilder attribute. This attribute associates the ServerTabs control with a ControlBuilder class named ServerTabsBuilder.

The ServerTabsBuilder class overrides the base ControlBuilder GetChildControlType() method. The overridden method maps the <tab> tag to the Tab control. Because of this mapping, you do not need to use a prefix or use the runat="server" attribute when declaring a tab within the ServerTabs control.

The page in Listing 31.31 illustrates how you can use the ServerTabs control.

Listing 31.31. ShowServerTabs.aspx

[View full width]

<%@ 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"> <html xmlns="http://www.w3.org/1999/xhtml" > <head  runat="server">     <style type="text/css">         html         {             background-color:silver;         }         .tab         {             float:left;             position:relative;             top:1px;             background-color:#eeeeee;             border:solid 1px black;             padding:0px 15px;             margin-left:5px;         }         .tab a         {             text-decoration:none;         }         .selectedTab         {             background-color:white;             border-bottom:solid 1px white;         }         .tabContents         {             border:solid 1px black;             background-color:white;             padding:10px;             height:200px;         }     </style>     <title>Show ServerTabs</title> </head> <body>     <form  runat="server">     <div>     <custom:ServerTabs                  Runat="Server">         <tab Text="First Tab">           Contents of the first tab         </tab>         <tab Text="Second Tab">           Contents of the second tab         </tab>         <tab Text="Third Tab">           Contents of the third tab         </tab>     </custom:ServerTabs>     </div>     </form> </body> </html> 

The ControlBuilder enables you to declare instances of the Tab control by using the <tab> tag instead of using a <custom:Tab runat="server"> tab.




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