More Complicated Custom Control

Our simple CenteredLabel custom control example was a good starting place for learning about custom controls. However, it doesn't even begin to touch on what you can achieve using custom controls. In the next example, we'll create a more complicated custom control to handle a more common scenario.

Whenever you're laying out a Web Form, you'll almost certainly want to lay out text boxes with labels that describe the required entry. A great number of variables are inherent within this situation. Should the text box be to the right of the label or below the label on the next line? Does the text box have default text? What about the style of the label and the text box?

Just as with the CenteredLabel custom control, the first and most important decision here is to determine what class to inherit from. Because C# and Visual Basic .NET offer only single inheritance, you'll have to select a single class. In this example, we have two possible alternatives: the label or the text box. Presented with such a choice, we should look at exactly what it is we're trying to create. Is it a label with a text box, or a text box with a label? Pretty clearly, what we want is a text box with a label. Looking at it another way, this control is a text box, and it has a label. This example is greatly oversimplified, but you'll still have to decide which single class to inherit from, no matter how complicated your control.

Note You do have another alternative for creating a custom control such as this label/text box example: composite controls. We'll discuss composite controls in the next section. This more complicated custom control is named LabelTextBox. Listing 6-6 shows LabelTextBox.vb, which declares and implements the LabelTextBox class. Listing 6-6 LabelTextBox custom control in Visual Basic .NET used to create a label and a text box and a text box

Imports System.ComponentModel Imports System.Web.UI

Public Enum LabelLocation As Long LabelLeft LabelAbove End Enum

Public Class LabelTextBox

Inherits System.Web.UI.WebControls.TextBox

Dim _labelText As String

Dim _labelStyle As String

Dim labelLocation As LabelLocation

Property [LabelText]() As String Get

Return _labelText End Get

Set(ByVal Value As String)

_labelText = Value End Set End Property

Property [LabelStyle]() As String Get

Return _labelStyle End Get

Set(ByVal Value As String)

_labelStyle = Value End Set End Property

Property [LabelLocation]() As LabelLocation Get

Return _labelLocation End Get

Set(ByVal Value As LabelLocation)

_labelLocation = Value End Set End Property

Protected Overrides Sub Render(ByVal output As

System.Web.UI.HtmlTextWriter) If _labelStyle Is DBNull.Value Then output.Write("<Span Style= )

output.Write(_labelStyle)

output.Write([_labelText]) output.Write("</Span>") Else output.Write([_labelText]) End If

If _labelLocation = LabelLocation.LabelAbove Then output.RenderBeginTag(HtmlTextWriterTag.Br) output.RenderEndTag() End If

MyBase.Render(output) End Sub

End Class

End Class

As you can see in Listing 6-6, after the Imports are listed, an enumeration is declared. This enumeration is used to allow the user of the class or component it creates to describe the label position. The enumeration is declared so that the default will be LabelLeft, meaning that the label will be on the same line as the text box, to its left. After the class declaration, the class declares that it inherits from System.Web.UIWebControls.TextBox. The three data elements that act as the storage data items for the three properties of the class are declared next. Following this, the LabelText property is declared, as follows:

Property [LabelText]() As String

Return _labelText End Get

Set(ByVal Value As String)

_labelText = Value End Set End Property

The pattern for each property is the same, no matter what the type. In each case, the property is declared. The Get section returns the underlying data element. The property doesn't have a direct data element that it cleanly maps to. In such situations, the property is synthesized, or created from some other information stored in the class. For example, if your class contains a StartDate property and an EndDate property and you need a Duration property, you probably wouldn't want to declare an additional internal data element named _duration but would instead calculate the duration whenever the Duration property was requested.

The Set section allows the property to be set. By tradition, the parameter passed in is named Value, and the type is that of the property itself. In this example, the Set section just assigns the value to the underlying data element, but the Set section can also do something more complex.

The meat of the LabelTextBox class is in the Render method: Protected Overrides Sub Render(ByVal output As _ System.Web.UI.HtmlTextWriter) If _labelStyle Is DBNull.Value Then output.Write("<Span Style= )

output.Write(_labelStyle)

output.Write([_labelText]) output.Write("</Span>") Else output.Write([_labelText]) End If

If _labelLocation = LabelLocation.LabelAbove Then output.RenderBeginTag(HtmlTextWriterTag.Br) output.RenderEndTag() End If

MyBase.Render(output) End Sub

The Render method first checks to see whether _labelStyle has been set. If _labelStyle has been set, a span tag with a Style attribute is written, followed by the _labelText string, followed by the end tag for the span. If the style hasn't been set, _labelText is written directly.

The _labelLocation value is next compared to one of the members of the LabelLocation enumeration, LabelLocation.LabelAbove. If the label location is set so that the label should be above the text box, a <BR> tag is written. Finally, the text box itself is rendered, by calling the Render method of MyBase, a keyword that allows you to access members of the immediate base class. I use MyBase to make it clear that I'm calling the base implementation of Render.

Listing 6-7 shows the same class from Listing 6-6 written using C#. Listing 6-7 C# version of LabelTextBox, named LabelTextBoxCS

using System;

using System.Web.UI;

using System.Web.UI.WebControls;

using System.ComponentModel;

namespace LabelTextBoxCS {

/// Summary description for WebCustomControl1. /// </summary>

public enum LabelLocationCS {

LabelLeft, LabelAbove

public class LabelTextBox : System.Web.UI.WebControls.TextBox {

private string labelText; private string labelStyle; private LabelLocationCS labelLocation;

public string LabelText {

return labelText;

labelText = value;

public string LabelStyle

return labelStyle;

labelStyle = value;

public LabelLocationCS LabelLocation {

return labelLocation;

labelLocation = value;

/// Render control to output parameter specified. /// </summary>

/// <param name="output"> The HTML writer /// to write out to </param>

protected override void Render(HtmlTextWriter output) {

output.Write(labelStyle);

output.Write(labelText);

output.Write(labelText);

if ( labelLocation == LabelLocationCS.LabelAbove ) {

output.RenderBeginTag(HtmlTextWriterTag.Br); output.RenderEndTag();

base.Render(output);

You'll notice some minor differences between the two classes. The first difference is the three-slash (///) comment marker used for XML documentation. Using the XML documentation provided by Visual Studio as a starting point, you can add documentation on classes and class members that can be parsed out to create an XML file. The C# property syntax is also a little different from the Visual Basic .NET syntax, but it's similar enough that you shouldn't have any difficulty following the intent of the code. One characteristic of properties in both Visual Basic .nEt and C# is that the access modifier (in Listings 6-6 and 6-7, Public) applies to both the Get and Set methods. This is an unfortunate limitation, but you can overcome it by providing a property with a Get method with one level of protection and then a separate nonproperty setter method with another level of protection, although this solution isn't ideal. For example, this fragment of a class allows all classes to read ReadOnlyText, but it provides a nonproperty setter method to allow the class to update the internal buffer that is returned as the ReadOnlyText property:

private string _readOnlyText;

public string ReadOnlyText {

return _readOnlyText;

private setReadOnlyText(string Value) {

_readOnlyText=Value;

A more realistic example would be one in which the property isn't simply getting and setting an underlying field but is synthesizing the value in a more complex way.

Note One difference between Visual Basic .NET properties and C#

properties is that in Visual Basic .NET properties, the type of the property is present twice in the declaration of the property: once as the property is declared and once as the type of the Value parameters to the Set method. The C# syntax mentions the type only once, which is somewhat more convenient if during development you're changing the type of the parameter. The Render method in Listing 6-7 is similar to the Visual Basic .NET version in Listing 66, discounting obvious syntax differences between the two languages. One more significant difference between the two Render methods is the use of a different keyword to access the base implementation of the class. C# uses base, whereas Visual Basic .NET uses MyBase.

0 0

Post a comment