Creating Your Own Controls, Part III: Custom Controls...
Custom controls extend the functionality of a standard .NET framework control. This can be very useful when customizing functionality in your applications, and leads to code reuse.
Custom controls extend the functionality of a standard .NET framework control. This can be very useful when customizing functionality in your applications, and leads to code reuse. Please read the "Creating your own controls II" document to understand more about composite controls in detail.
For this article, a custom list control will be created. This list control renders a simple ordered or unordered list (determined at runtime), based on an enumerated value. The possible values for the enumeration are: circle, disc, square, numeric, lower roman, upper roman, lower alpha, and upper alpha, which the first three values are for unordered lists. To enter values in this list (items property), you would use the same editor as a CheckBoxList or DropDownList control. This control will inherit from the System.Web.UI.WebControls.ListControl class:
| Public Class HTMLListControl Inherits ListControl |
Next, the values provided above are enumerated in a type called ListType, which will then be exposed through a property. Attributes are also specified which handle the control of the functionality in the Visual Studio.NET designer. These attributes add functionality to the properties in the properties window, such as the default value, which will bold the non-default values, and the description, which will show the description in the bottom of the window:
|
Private m_objListType As ListType <DefaultValue(GetType(ListType), "ListType.Disc"), Description("The type of list to render"), _ TypeConverter(GetType(EnumConverter))> _ Public Property ListType() As ListType Get Return m_objListType End Get Set(ByVal Value As ListType) m_objListType = Value End Set End Property |
Next, the render method writes the ordered or unordered list, which is determined by the property above. Also, the enumerated value must be converted to a string to render this type of list item in the browser correctly. The Enum object has several useful shared methods that can convert the enumeration into text, and vice versa. This is useful because the type of list must be specified in the <ul> or <ol> tag's type property. Lastly, each item is written to the list via the <li> tag.
|
Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter) Dim strTag As String Select Case (m_objListType) Case ListType.Circle, ListType.Disc, ListType.Square strTag = "ul" Case Else strTag = "ol" End Select writer.Write("<" & strTag & " type='" & [Enum].GetName(GetType(ListType), m_objListType) & "'>") Dim objItem As ListItem For Each objItem In Me.Items writer.Write("<li>" & objItem.Text & "</li>") Next writer.Write("</" & strTag & ">") End Sub |
To illustrate some additional functionality, the ToString function is shadowed, which will return the entire set of ListItems in a comma-separated value format.
|
Public Shadows Sub ToString() Dim objItem As ListItem Dim strText As String = "" For Each objItem In Me.Items strText = objItem.Text & "," & objItem.Value & ControlChars.CrLf Next End Sub |
Because of the simplicity of this control, a second control example illustrates an additional approach to developing custom controls. This TextBox control parses the data entered to verify that it is a valid social security number. Based on certain parameters provided, if the social security number is valid or invalid, a custom event is raised from the TextChanged event.
This control will not take advantage of the Render method, because no additional rendering functionality is needed. It also illustrates the use of creating a new event to "replace" an existing one. Please note that in the new control, the original event can still be used.
To start, the control is declared with some attributes defined. They define some functionality about the control, such as the DefaultProperty and DefaultEvent for the control. You can see the DefaultEvent property working when you double-click on the control. Visual Studio .NET will create this event declaration in your code. The ToolboxData attribute defines the look of the control declaration in your ASP.NET HTML. If you try to define the name of the control as something other than the class name, you will have an error when you drag it onto your form.
|
<DefaultProperty("Text"), DefaultEvent("NewText"), ToolboxData("<{0}:SocialSecurityTextBox runat=server></{0}:SocialSecurityTextBox>")> _ Public Class SocialSecurityTextBox Inherits TextBox End Class |
The NewText event is the new event for handling text changes within the textbox. It will override the TextChanged event below. A custom event argument passes along validation information regarding the SSN passed in. The declaration of the event is below:
| Public Event NewText(ByVal sender As Object, ByVal e As SSNTextEventArgs) |
Two custom properties alter the validation of the SSN. The first parameter AcceptChars allows character values to be entered in the SSN (surprisingly enough, I happen to know of someone who has a letter in their social security number). The second parameter EvenMiddleNumber enforces the middle two digits of the SSN. These numbers, when added together, must be even, which is a SSN validation rule. However, there are instances of people having an odd-number calculation. Both of these parameters alter the validation scheme, which these are displayed below:
|
Private m_blnAcceptChars As Boolean = False <DefaultValue(False), Description("Accept chars in SSN"), TypeConverter(GetType(BooleanConverter))> _ Public Property AcceptChars() As Boolean Get Return m_blnAcceptChars End Get Set(ByVal Value As Boolean) m_blnAcceptChars = Value End Set End Property Private m_blnEven As Boolean <DefaultValue(True), Description("Is middle number even always?"), TypeConverter(GetType(BooleanConverter))> _ Public Property EvenMiddleNumber() As Boolean Get Return m_blnEven End Get Set(ByVal Value As Boolean) m_blnEven = Value End Set End Property |
When the textbox loses focus, it will trigger the TextChanged event. This can occur at the base-class level. Our new event (NewText) will be the event raised within the page. To validate the SSN properly, several comparisons must be made. The first is to verify that the SSN is formed properly, in the format of XXX-XX-XXXX. Regular expression objects perform this validation, which can validate them pretty easily. If you are unfamiliar with regular expressions, they aren’t as confusing as they seem. For this expression, let's view it in pieces:
[0-9A-Z]{3,}- – The first three characters must be a 0 through 9 or A through Z, followed by a "-"
[0-9A-Z]{2,}- – The next two characters must be a 0 through 9 or A through Z, followed by a "-"
[0-9A-Z]{4,} – The last four characters must be a 0 through 9 or A through Z
The expression checks both numbers and letters, in case that the AcceptChars property is set to true. If the pattern doesn't match, then the new event is raised. The SSNTextEventArgs constructor receives two parameters: the SSN value provided in the textbox, and a Boolean value stating whether the SSN is valid (referred below as IsValid). These parameters are provided in the constructor, so the event argument object can be created on the fly.
All of the validation checks are looking for an invalid SSN. If it is valid, the method raises the NewText event with the IsValid parameter set to true at the very end.
|
Private Sub SocialSecurityTextBox_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.TextChanged Dim objBox As TextBox = CType(sender, TextBox) 'Make sure the SSN is formatted correctly, in the format of XXX-XX-XXXX; 'regular expression matching is used for this Dim objMatch As Match = Regex.Match(objBox.Text, "[0-9A-Z]{3,}-[0-9A-Z]{2,}-[0-9A-Z]{4,}") If (Not objMatch.Success) Then RaiseEvent NewText(Me, New SSNTextEventArgs(objBox.Text, False)) Exit Sub End If |
The next validation check is for character data in the SSN. If the AcceptChars property is set to true, no more validation is needed, because the above regular expression check already parsed out all of the undesired data (such as @, #, $, and other characters). However, if it is set to false, then the entire number must be checked for numeric data. To do this, the framework uses the IsNumeric function. If the data is numeric (minus the "-" characters), then the number is valid at this point. Otherwise, similar to above, the custom NewText event is raised with the IsValid parameter set to false.
|
If (Not m_blnAcceptChars) Then 'Evaluate the SSN without the dashes If (Not IsNumeric(objBox.Text.Replace("-", ""))) Then RaiseEvent NewText(Me, New SSNTextEventArgs(objBox.Text, False)) Exit Sub End If End If |
The last check occurs if the EvenMiddleNumber property is set to true. If true, the middle two numbers are stripped from the SSN string. Then, these numbers are stripped apart again, and converted to an integer. If the modular value of these two parameters (when added together) does not amount to zero, then the SSN is not valid.
|
If (m_blnEven) Then Dim strMiddle As String = objBox.Text.Substring(4, 2) If (IsNumeric(strMiddle)) Then 'Convert the numbers so they may be calculated Dim intLeft As Integer = CType(strMiddle.Substring(0, 1), Integer) Dim intRight As Integer = CType(strMiddle.Substring(1), Integer) 'If the numbers in the middle are even, when calculated, they 'should have a zero remainder If ((intLeft + intRight) Mod 2 <> 0) Then RaiseEvent NewText(Me, New SSNTextEventArgs(objBox.Text, False)) Exit Sub End If End If End If |
As long as none of these validation errors have occurred, then the last statement raises the event with the IsValid property set to true.
|
RaiseEvent NewText(Me, New SSNTextEventArgs(objBox.Text, True)) End Sub |
These are two simple implementations of custom controls in .NET; however, this is only scratching the surface. If you are writing a lot of the same code for a control in your ASP.NET pages, consider custom controls.
You may download the code here.