Overview of Custom Control Building


You must answer two questions before writing a custom control:

  • What type of control do I want to write?

  • From what class do I inherit?

The two basic types of controls are fully rendered and composite controls. When you build a fully rendered control, you start from scratch. You specify all the HTML content that the control renders to the browser.

When you create a composite control, on the other hand, you build a new control from existing controls. For example, you can create a composite AddressForm control from existing TextBox and RequiredFieldValidator controls. When you create a composite control, you bundle together existing controls as a new control.

The second question that you must address is the choice of the base control for your new control. You can inherit a new control from any existing ASP.NET control. For example, if you want to create a better GridView control, then you can inherit a new control from the GridView control and add additional properties and methods to your custom GridView control.

Typically, when building a basic control, you inherit your new control from one of the following base classes:

  • System.Web.UI.Control

  • System.Web.UI.WebControls.WebControl

  • System.Web.UI.WebControls.CompositeControl

The CompositeControl class inherits from the WebControl class, which inherits from the Control class. Each of these base classes adds additional functionality.

The base class for all controls in the ASP.NET Framework is the System.Web.UI.Control class. Every control, including the TextBox and GridView controls, ultimately derives from this control. This means that all the properties, methods, and events of the System.Web.UI.Control class are shared by all controls in the Framework.

All Web controls inherit from the base System.Web.UI.WebControls.WebControl class. The difference between the Control class and WebControl class is that controls that derive from the WebControl class always have opening and closing tags. Because a WebControl has an opening and closing tag, you also get more formatting options. For example, the WebControl class includes BackColor, Font, and ForeColor properties.

For example, the ASP.NET Literal control inherits from the base Control class, whereas the Label control inherits from the base WebControl class. The Repeater control inherits from the base Control class, whereas the GridView control (ultimately) inherits from the WebControl class.

Finally, the System.Web.UI.WebControls.CompositeControl is new in the ASP.NET 2.0 Framework. You should use this class as the base class for any composite control. The CompositeControl automatically creates a naming container for its child controls. It also includes an overridden Controls property that forces child controls to appear in Design view.

Building Fully Rendered Controls

Let's start by creating a simple fully rendered control. When you create a fully rendered control, you take on the responsibility of specifying all the HTML content that the control renders to the browser.

The file in Listing 31.1 contains a fully rendered control that derives from the base Control class.

Listing 31.1. FullyRenderedControl.vb

Imports System.Web.UI Namespace myControls     Public Class FullyRenderedControl         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         Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)             writer.Write(_Text)         End Sub     End Class End Namespace 

Note

Add the control in Listing 31.1 to your App_Code folder. Any code added to the App_Code folder is compiled dynamically.


The control in Listing 31.1 inherits from the base Control class, overriding the base class Render() method. The control simply displays whatever value that you assign to its Text property. The value of the Text property is written to the browser with the HtmlTextWriter class's Write() method.

The file in Listing 31.2 illustrates how you can use the new control in a page.

Listing 31.2. ShowFullyRenderedControl.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 Fully Rendered Control</title> </head> <body>     <form  runat="server">     <div>     <custom:FullyRenderedControl                  Text="Hello World!"         runat="Server" />     </div>     </form> </body> </html> 

Note

In Listing 31.2, the custom control is registered in the page through use of the <%@ Register %> directive. Alternatively, you can register the control for an entire website by registering the control in the <pages> section of the web configuration file.


If you open the page in Listing 31.2 in a browser and select View Source, you can see the HTML rendered by the control. The control simply renders the string "Hello World!".

Rather than inherit from the base Control class, you can create a fully rendered control by inheriting a new control from the base WebControl class. When inheriting from the WebControl class, you override the RenderContents() method instead of the Render() method.

For example, the control in Listing 31.3 contains a simple fully rendered control that inherits from the WebControl class.

Listing 31.3. FullyRenderedWebControl.vb

Imports System.Web.UI Imports System.Web.UI.WebControls Namespace myControls     Public Class FullyRenderedWebControl         Inherits WebControl         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         Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)             writer.Write(_Text)         End Sub     End Class End Namespace 

The page in Listing 31.4 illustrates how you can use the new control (see Figure 31.1). Notice that the BackColor, BorderStyle, and Font properties are set. Because the control in Listing 31.3 derives from the base WebControl class, you get these properties for free.

Figure 31.1. Displaying a fully rendered WebControl.


Listing 31.4. ShowFullyRenderedWebControl.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 Fully Rendered WebControl</title> </head> <body>     <form  runat="server">     <div>     <custom:FullyRenderedWebControl                  Text="Hello World"         BackColor="Yellow"         BorderStyle="Dashed"         Font-Size="32px"         Runat="Server" />     </div>     </form> </body> </html> 

After opening the page in Listing 31.4, if you select View Source in your browser, you can see the rendered output of the control. It looks like this:

[View full width]

<span style="display:inline-block;background-color :Yellow;border-style:Dashed;font-size:32px;">Hello World</span>


A WebControl, unlike a control, renders an enclosing <span> tag by default.

Understanding the HtmlTextWriter Class

When you create a fully rendered control, you use the HtmlTextWriter class to write the HTML content to the browser. The HtmlTextWriter class was specifically designed to make it easier to render HTML. Here is a partial list of the methods supported by this class:

  • AddAttribute() Adds an HTML attribute to the tag rendered by calling RenderBeginTag().

  • AddStyleAttribute() Adds a CSS attribute to the tag rendered by a call to RenderBeginTag().

  • RenderBeginTag() Renders an opening HTML tag.

  • RenderEndTag() Renders a closing HTML tag.

  • Write() Renders a string to the browser.

  • WriteBreak() Renders a <br /> tag to the browser.

You can call the AddAttribute() or the AddStyleAttribute() method as many times as you please before calling RenderBeginTag(). When you call RenderBeginTag(), all the attributes are added to the opening HTML tag.

The methods of the HtmlTextWriter class can use the following enumerations:

  • HtmlTextWriterTag Contains a list of the most common HTML tags.

  • HtmlTextWriterAttribute Contains a list of the most common HTML attributes.

  • HtmlTextWriterStyle Contains a list of the most Cascading Style Sheet attributes.

When using the methods of the HtmlTextWriter class, you should strive to use these enumerations to represent HTML tags and attributes. If a particular tag or attribute is missing from one of the enumerations, you can pass a string value instead.

For example, the control in Listing 31.5 renders a table of HTML colors by using an HTML table (see Figure 31.2). Notice that the RenderContents() method takes advantage of the methods of the HtmlTextWriter class to render the HTML table.

Figure 31.2. Displaying a table of HTML colors.


Listing 31.5. ColorTable.vb

[View full width]

Imports System Imports System.Web.UI Imports System.Web.UI.WebControls Imports System.Drawing Namespace myControls     Public Class ColorTable         Inherits WebControl         Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)             ' Get list of colors             Dim colors() As KnownColor = CType(System.Enum.GetValues(GetType(KnownColor)),  KnownColor())             ' Render opening table tag             writer.AddAttribute(HtmlTextWriterAttribute.Border, "1")             writer.RenderBeginTag(HtmlTextWriterTag.Table)             ' Render table body             Dim colorName As KnownColor             For Each colorName In colors                 writer.RenderBeginTag(HtmlTextWriterTag.Tr)                 ' Render first column                 writer.RenderBeginTag(HtmlTextWriterTag.Td)                 writer.Write(colorName.ToString())                 writer.RenderEndTag()                 ' Render second column                 writer.AddAttribute(HtmlTextWriterAttribute.Width, "50px")                 writer.AddAttribute(HtmlTextWriterAttribute.Bgcolor, colorName.ToString())                 writer.RenderBeginTag(HtmlTextWriterTag.Td)                 writer.Write("&nbsp;")                 writer.RenderEndTag()                 writer.RenderEndTag()             Next             ' close table             writer.RenderEndTag()         End Sub     End Class End Namespace 

You should notice a number of things about the control in Listing 31.5. First, notice that the AddAttribute() method is called to add the table border attribute. When the RenderBeginTag() method is called, the table border attribute is added to the opening table tag.

Furthermore, notice that you do not specify the tag when calling the RenderEndTag() method. This method automatically closes the last tag opened with the RenderBeginTag() method.

Note

The CD that accompanies this book includes a ShowColorTable.aspx page that you can open in your browser to view the rendered output of the ColorTable control.


The control in Listing 31.6, the DropShadow control, illustrates how you can use the AddStyleAttribute() method of the HtmlTextWriter class to add Cascading Style Sheet attributes to an HTML tag.

Listing 31.6. DropShadow.vb

[View full width]

Imports System.Web.UI Imports System.Web.UI.WebControls Namespace myControls     Public Class DropShadow         Inherits WebControl         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         Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)             writer.AddStyleAttribute(HtmlTextWriterStyle.Filter, "dropShadow(color=#AAAAAA ,offX=3,offY=3);width:500px")             writer.RenderBeginTag(HtmlTextWriterTag.Div)             writer.Write(_Text)             writer.RenderEndTag()         End Sub     End Class End Namespace 

The control in Listing 31.6 renders a drop shadow behind whatever text you assign to the control's Text property (see Figure 31.3). The drop shadow is created with the help of an Internet Explorer DropShadow filter.

Figure 31.3. Displaying a drop shadow with the DropShadow control.


Notice that the Filter attribute is added to the <div> tag with a call to the AddStyleAttribute() method. The AddStyleAttribute() method works just like the AddAttribute() method, except that the AddStyleAttribute() method adds a CSS attribute instead of an HTML attribute.

Web Standards Note

Filters are an Internet Explorer extension to the Cascading Style Sheet standard. They don't work with Firefox or Opera. Firefox has its own extensions to Cascading Style Sheets with its -moz style rules.


Specifying the Containing WebControl Tag

By default, a WebControl renders an HTML <span> tag around its contents. You can specify a different tag by overriding the WebControl's TagKey property.

For example, the control in Listing 31.7 renders its contents within an HTML <div> tag.

Listing 31.7. Glow.vb

[View full width]

Imports System.Web.UI Imports System.Web.UI.WebControls Namespace myControls     Public Class Glow         Inherits WebControl         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         Protected Overrides ReadOnly Property TagKey() As HtmlTextWriterTag             Get                 Return HtmlTextWriterTag.Div             End Get         End Property         Protected Overrides Sub AddAttributesToRender(ByVal writer As HtmlTextWriter)             writer.AddStyleAttribute(HtmlTextWriterStyle.Filter, "glow(Color=#ffd700 ,Strength=10)")             MyBase.AddAttributesToRender(writer)         End Sub         Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)             writer.Write(_Text)         End Sub         Public Sub New()             Me.Width = Unit.Parse("500px")         End Sub     End Class End Namespace 

The control in Listing 31.7 displays a glowing effect around any text that you assign to its Text property. The control takes advantage of the Internet Explorer Glow filter to create the glow effect (see Figure 31.4).

Figure 31.4. Displaying glowing text with the Glow control.


Notice that the control overrides the base WebControl's TagKey property. Because the overridden property returns a <div> tag, the WebControl renders a <div> tag.

Note

There are several methods you can use to modify the tag rendered by a WebControl. You can override the TagName property instead of the TagKey property. The TagName property enables you to specify an arbitrary string for the tag. (It doesn't limit you to the HtmlTextWriterTag enumeration.) You also can specify the tag rendered by a WebControl in the WebControl's constructor. Finally, you can override a WebControl's RenderBeginTag() and RenderEndTag() methods and completely customize the opening and closing tags.


Furthermore, you should notice that the control in Listing 31.7 overrides the AddAttributesToRender() method. If you override this method, then you can add HTML or CSS attributes to the opening HTML tag rendered by the control. When overriding this method, be careful to call the base AddAttributesToRender() method or the standard control attributes, such as the control ID, won't be rendered.

Building Composite Controls

If you don't want to start from scratch when building a custom control, you can build a composite control. When you create a composite control, you create a new control from existing controls.

Every ASP.NET control has a Controls property that represents all of its child controls. If you add child controls to a control, then the child controls are automatically rendered when the parent control is rendered.

When you create a composite control, you typically override a control's CreateChildControls() method. This method is called when a control builds its collection of child controls.

For example, the control in Listing 31.8 combines a TextBox control and RequiredFieldValidator control.

Listing 31.8. RequiredTextBox.vb

Imports System Imports System.Web.UI.WebControls Namespace myControls     Public Class RequiredTextBox         Inherits CompositeControl         Private input As TextBox         Private validator As RequiredFieldValidator         Public Property Text() As String             Get                 EnsureChildControls()                 Return input.Text             End Get             Set(ByVal Value As String)                 EnsureChildControls()                 input.Text = value             End Set         End Property         Protected Overrides Sub CreateChildControls()             input = New TextBox()             input.ID = "input"             Me.Controls.Add(input)             validator = New RequiredFieldValidator()             validator.ID = "valInput"             validator.ControlToValidate = input.ID             validator.ErrorMessage = "(Required)"             validator.Display = ValidatorDisplay.Dynamic             Me.Controls.Add(validator)         End Sub     End Class End Namespace 

Notice that the control in Listing 31.8 inherits from the base CompositeControl class. Furthermore, rather than override the base control's RenderContents() method, the control overrides the base control's CreateChildControls() method.

You should notice one other special thing in Listing 31.8. Notice that the EnsureChildControls() method is called in both the Get and Set methods of the Text property. The EnsureChildControls() method forces the CreateChildControls() method to be called. However, it prevents the CreateChildControls() method from being called more than once.

The Text property gets or sets a property of a child control (the TextBox control). If you attempt to use the Text property before the CreateChildControls() method is called, then you receive a null reference exception. The child controls must be created before you can access any of the child control properties.

The page in Listing 31.9 illustrates how you can use the RequiredTextBox control in a page.

Listing 31.9. ShowRequiredTextBox.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"> <script runat="server">     Sub btnSubmit_Click(ByVal sender As Object, ByVal e As EventArgs)         lblResults.Text = txtUserName.Text     End Sub </script> <html xmlns="http://www.w3.org/1999/xhtml" > <head  runat="server">     <title>Show RequiredTextBox</title> </head> <body>     <form  runat="server">     <div>     <asp:Label                  Text="User Name:"         AssociatedControl         Runat="server" />     <custom:RequiredTextBox                  Runat="Server" />     <br />     <asp:Button                  Text="Submit"         OnClick="btnSubmit_Click"         Runat="server" />     <hr />     <asp:Label                  Runat="server" />     </div>     </form> </body> </html> 

The page in Listing 31.9 has tracing enabled. If you look at the control tree for the page, you'll see that the RequiredTextBox control includes both a TextBox and RequiredFieldValidator control as child controls.

Building Hybrid Controls

In practice, you very rarely build pure composite controls. In most cases in which you override a control's CreateChildControls() method, you also override the control's RenderContents() method to specify the layout of the child controls.

For example, the control in Listing 31.10 represents a Login control. In the control's CreateChildControls() method, two TextBox controls are added to the control's collection of child controls.

Listing 31.10. Login.vb

Imports System Imports System.Web.UI Imports System.Web.UI.WebControls Namespace myControls     Public Class Login         Inherits CompositeControl         Private txtUserName As TextBox         Private txtPassword As TextBox         Public Property UserName() As String             Get                 EnsureChildControls()                 Return txtUserName.Text             End Get             Set(ByVal Value As String)                 EnsureChildControls()                 txtUserName.Text = value             End Set         End Property         Public Property Password() As String             Get                 EnsureChildControls()                 Return txtPassword.Text             End Get             Set(ByVal Value As String)                 EnsureChildControls()                 txtPassword.Text = value             End Set         End Property         Protected Overrides Sub CreateChildControls()             txtUserName = New TextBox()             txtUserName.ID = "txtUserName"             Me.Controls.Add(txtUserName)             txtPassword = New TextBox()             txtPassword.ID = "txtPassword"             txtPassword.TextMode = TextBoxMode.Password             Me.Controls.Add(txtPassword)         End Sub         Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)             writer.RenderBeginTag(HtmlTextWriterTag.Tr)             ' Render UserName Label             writer.RenderBeginTag(HtmlTextWriterTag.Td)             writer.AddAttribute(HtmlTextWriterAttribute.For, txtUserName.ClientID)             writer.RenderBeginTag(HtmlTextWriterTag.Label)             writer.Write("User Name:")             writer.RenderEndTag() ' Label             writer.RenderEndTag() ' TD             ' Render UserName TextBox             writer.RenderBeginTag(HtmlTextWriterTag.Td)             txtUserName.RenderControl(writer)             writer.RenderEndTag() ' TD             writer.RenderEndTag()             writer.RenderBeginTag(HtmlTextWriterTag.Tr)             ' Render Password Label             writer.RenderBeginTag(HtmlTextWriterTag.Td)             writer.AddAttribute(HtmlTextWriterAttribute.For, txtPassword.ClientID)             writer.RenderBeginTag(HtmlTextWriterTag.Label)             writer.Write("Password:")             writer.RenderEndTag() ' Label             writer.RenderEndTag() ' TD             ' Render Password TextBox             writer.RenderBeginTag(HtmlTextWriterTag.Td)             txtPassword.RenderControl(writer)             writer.RenderEndTag() ' TD             writer.RenderEndTag() ' TR         End Sub         Protected Overrides ReadOnly Property TagKey() As HtmlTextWriterTag             Get                 Return HtmlTextWriterTag.Table             End Get         End Property     End Class End Namespace 

In Listing 31.10, the RenderContents() method is overridden in order to layout the two TextBox controls. The TextBox controls are rendered within an HTML table (see Figure 31.5). Notice that each TextBox is rendered by calling the RenderControl() method.

Figure 31.5. Performing layout with an HTML table.


The default RenderContents() method simply calls the RenderControl() method for each child control. If you override the RenderContents() method, you have more control over the layout of the control.

The Login control in Listing 31.10 uses an HTML table for layout. From a web standards perspective, using HTML tables for layout is frowned upon. The modified Login control in Listing 31.11 uses <div> tags instead of a <table> tag for layout.

Listing 31.11. LoginStandards.vb

Imports System Imports System.Web.UI Imports System.Web.UI.WebControls Namespace myControls     Public Class LoginStandards         Inherits CompositeControl         Private txtUserName As TextBox         Private txtPassword As TextBox         Public Property UserName() As String             Get                 EnsureChildControls()                 Return txtUserName.Text             End Get             Set(ByVal Value As String)                 EnsureChildControls()                 txtUserName.Text = Value             End Set         End Property         Public Property Password() As String             Get                 EnsureChildControls()                 Return txtPassword.Text             End Get             Set(ByVal Value As String)                 EnsureChildControls()                 txtPassword.Text = Value             End Set         End Property         Protected Overrides Sub CreateChildControls()             txtUserName = New TextBox()             txtUserName.ID = "txtUserName"             Me.Controls.Add(txtUserName)             txtPassword = New TextBox()             txtPassword.ID = "txtPassword"             txtPassword.TextMode = TextBoxMode.Password             Me.Controls.Add(txtPassword)         End Sub         Protected Overrides Sub RenderContents(ByVal writer As HtmlTextWriter)             writer.AddStyleAttribute("float", "left")             writer.RenderBeginTag(HtmlTextWriterTag.Div)             writer.AddStyleAttribute(HtmlTextWriterStyle.Padding, "3px")             writer.RenderBeginTag(HtmlTextWriterTag.Div)             writer.AddAttribute(HtmlTextWriterAttribute.For, txtUserName.ClientID)             writer.RenderBeginTag(HtmlTextWriterTag.Label)             writer.Write("User Name:")             writer.RenderEndTag()             writer.RenderEndTag()             writer.AddStyleAttribute(HtmlTextWriterStyle.Padding, "3px")             writer.RenderBeginTag(HtmlTextWriterTag.Div)             writer.AddAttribute(HtmlTextWriterAttribute.For, txtPassword.ClientID)             writer.RenderBeginTag(HtmlTextWriterTag.Label)             writer.Write("Password:")             writer.RenderEndTag()             writer.RenderEndTag()             writer.RenderEndTag()             writer.AddStyleAttribute("float", "left")             writer.RenderBeginTag(HtmlTextWriterTag.Div)             writer.AddStyleAttribute(HtmlTextWriterStyle.Padding, "3px")             writer.RenderBeginTag(HtmlTextWriterTag.Div)             txtUserName.RenderControl(writer)             writer.RenderEndTag()             writer.AddStyleAttribute(HtmlTextWriterStyle.Padding, "3px")             writer.RenderBeginTag(HtmlTextWriterTag.Div)             txtPassword.RenderControl(writer)             writer.RenderEndTag()             writer.RenderEndTag()             writer.Write("<br style='clear:left' />")         End Sub         Protected Overrides ReadOnly Property TagKey() As HtmlTextWriterTag             Get                 Return HtmlTextWriterTag.Div             End Get         End Property     End Class End Namespace 

The control in Listing 31.11 works quite nicely in all recent browsers (Internet Explorer 6, Firefox 1, Opera 8) without requiring an HTML table for layout (see Figure 31.6).

Figure 31.6. Performing CSS layout.


Note

Microsoft does not have the luxury of using <div> tags for layout. Because Microsoft must support very old browsers that have limited or no support for Cascading Style Sheets (HTML 3.2 browsers), the standard controls must rely on HTML tables for layout.





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