Flylib.com

Books Software

 
 
 

ASP.NET 2.0 Unleashed - page 219


Summary

In this chapter, you learned how you easily can extend the power of the Web Part Framework. The first section explored different types of Web Part Zones that you can create. For example, we created a custom Photo Web Part Zone that automatically displays the photos contained in a folder. We also created a multi-column Web Part Zone that displays Web Parts in a configurable number of repeating columns . Finally, we created a Web Part Zone that supports fancy drop-down menus .

Next, you learned about several methods of extending Catalog Zones. We created a custom Catalog Part that automatically displays all the Web Part controls contained in an application's App_Code folder. We also created a custom Catalog Zone that supports drag-and-drop functionality. Finally, you explored a method for creating a templated Catalog Zone that enables you to customize the appearance of a catalog in any way that you please .

Next, we tackled the subject of Editor Zones. First, we created a simple custom Editor Part that renders a custom form for editing the properties of a Web Part. Next, we created a templated Editor Zone that enables you to easily associate custom editor forms with any Web Part.

Finally, we built a custom Web Part Display Mode and Tool Zone. By taking advantage of a custom Help Display Mode, you can display help messages easily to the users of a Web Part page.



Part IX: Custom Control Building

IN THIS PART


 

CHAPTER 31 Building Custom Controls

 

CHAPTER 32 Integrating JavaScript in Custom Controls

 

CHAPTER 33 Building Templated Databound Custom Controls



Chapter 31. Building Custom Controls

In this Chapter

  • Overview of Custom Control Building

  • View State and Control State

  • Processing Postback Data and Events

  • Working with Control Property Collections

  • Creating a Better Designer Experience

  • Summary

In this chapter, you learn how to extend the ASP.NET Framework by building custom controls. You learn how to create controls in exactly the same way as Microsoft developed the standard ASP.NET controls such as the TextBox and Button controls.



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 id="Head1" runat="server">
    <title>Show Fully Rendered Control</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <custom:FullyRenderedControl
        ID="FullyRenderedControl1"
        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 id="Head1" runat="server">
    <title>Show Fully Rendered WebControl</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <custom:FullyRenderedWebControl
        ID="FullyrenderedWebControl1"
        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]

[View full width]

<span id="FullyrenderedWebControl1" 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

{% if main.adsdop %}{% include 'adsenceinline.tpl' %}{% endif %}

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 id="Head1" runat="server">
    <title>Show RequiredTextBox</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>

    <asp:Label
        ID="lblUserName"
        Text="User Name:"
        AssociatedControlID="txtUserName"
        Runat="server" />

    <custom:RequiredTextBox
        ID="txtUserName"
        Runat="Server" />

    <br />

    <asp:Button
        ID="btnSubmit"
        Text="Submit"
        OnClick="btnSubmit_Click"
        Runat="server" />

    <hr />

    <asp:Label
        id="lblResults"
        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.