Developing Custom Controls - Part 1
page 6 of 8
by G. Mohyuddin
Feedback
Average Rating: 
Views (Total / Last 10 Days): 35766/ 69

Building an Extended Custom Control

Extending an existing server control is an interesting and a simple job most of the time. I have chosen TextBox control for this purpose. Let us first analyze our need of building an extended TextBox control as we do not want to extend it just for the sake of an example! This analysis can be divided into two parts finding deficiencies to make up and suggesting enhancements that are useful. I emphasize on the analysis so that we can extend it meaningfully and purposefully making it a really useful member of our control tool kit. With inclusion of these properties in our control you will also be able to learn how a custom control can emit HTML and JavaScript as per required. 

There is a deficiency that really becomes problematic for developers; when the TextMode of the TextBox is set to Multiline, the MaxLength property stops working. We will discuss why it cannot support this.

While focusing on enhancements there are several which can be very useful, for example providing a support for entering cased input uppercase or lowercase, numeric, and alphanumeric. A textbox can also be self validating, these enhancements can add a good worth to it. So after a little analysis, we are able to figure out what we want to overcome and what we want to add in our new control.

Our new Textbox, ExtendedTextBox, will have the following features.

1.    It supports a cased input by providing TextCaseMode property.

2.    It supports an input mode for numeric and alphanumeric input by providing InputMode property.

3.    It supports MaxLength property when TextMode is set to MultiLine also.

4.    It provides built in required field validation by providing IsRequired property.

Now getting specific to the implementation, the following are the steps to accomplish all this.

5.    First, create a new project of Web Control Library. Please note that VB and Visual C# in Standard Edition do not support the Web Control Library template. It is only available in the professional edition.

6.    Give the class some suitable name, in our case ExtendedTextBox, and extend it from its base i.e. TextBox as shown below.

Listing 3

[Description("Extended TextBox v1.0 (Developed by Mahr G. Mohyuddin)")]   
[DefaultProperty("Text")]                       
[ToolboxData("<{0}:ExtendedTextBox runat=server></{0}:ExtendedTextBox1>")]
public class ExtendedTextBox : System.Web.UI.WebControls.TextBox
  {
...

You can see the class has been adorned with some attributes, Description, DefaultProperty and ToolboxData. Description attribute keeps the description of the control that is displayed on bringing the ouse over it in the tool box. DefaultProperty is used to bring the given property to focus when the property window of a control is displayed. ToolBoxData is a mandatory attribute that is used to define the look of control declaration in aspx markup when it is dropped on the page. It ensures that the control is given the class name; otherwise it will flag an error. You can also add more attributes like DefaultEvent, which add declaration of the specified event in the code behind when a control is double clicked. ToolBoxItem specifies whether the control should be visible or not; when it is set to True the control is visible and vice versa. ToolBoxBiitmap sepecifies the bitmap image that should be used as the icon for the control and finally ToolBoxItemFilter attribute specifies the filter Visual Studio 2005 uses to determine whether the control should be enabled or disabled for a specific designer.

Now, add properties that we need to expose to let the world use features we are going to provide. This includes TextCaseMode, InputMode, IsRequired, and ErrorMessage. Definition of all properties is similar. For instance, TextCaseMode sets the case of the text to be entered. It can be Uppercase, Lowercase or NotSet. We will need an enumeration for this purpose. The following code snippet shows the definition of TextCaseMode property.

Listing 4

[Description("Sets the case of the text entered")][Category("Behavior")
  ][DefaultValue(TextCaseMode.NotSet)]
public TextCaseMode TextCaseMode
{
  get
  {
    object o = ViewState["TextCaseMode"];
    return (o != null ? (TextCaseMode)o: TextCaseMode.NotSet);
  }
  set
  {
    ViewState["TextCaseMode"= value;
  }
}

The property has been adorned with three attributes: Description, Category and DefaultValue. Use of Description is quite evident. Category attribute takes the name of the category in the property pane of the control in Visual Studio IDE. All properties of a control are organized in Accessibly, Appearance, Behavior, Data, Layout and Misc categories. We want the new property TextCaseMode to be displayed in Behavior category.

Another important attribute is DefaultValue which sets a default value,; the specified one, is shown when the no value is set by user. In our case we want the text case mode not to be set by default. Therefore, NotSet has been provided as the default value.

This property is of type TextCaseMode enum that has been defined. A property that is of type enum is shown in the form of Dropdownlist in the control property pane. 

Figure 1

The new properties added to the control have been encircled. You can see that the name with description (provided in Description attribute) of the selected property appears on the bottom of the pane.

A value of property is set and retrieved in/from ViewState having name of property of the property as key, since ASP.NET controls save their state through ViewState. Similarly, a custom control should also save its state. In other words, the value of properties exposed by a custom control should be retained on postbacks. It can be possible only if we set the value of a property in ViewState and get from it as shown above. The following code snippet shows the definition of TextCaseMode and InputMode enums.

Listing 5

public enum TextCaseMode
{
  UpperCase, LowerCase, NotSet
} public enum InputMode
{
  Alphanumeric, Numeric, NotSet
}

Similarly, define other properties InputMode, IsRequired and ErrorMessage. In short, InputMode property is used to set the mode the text being entered that can either be Numeric, Alphanumeric or NotSet. The enum used has been defined in Listing 3.  IsRequired property when set to true makes input in the textbox mandatory and stops all postback until some value is entered. It shows the error, in tooltip, given in ErrorMessage property when the mouse comes over it.  The following code snippet shows the definition of the rest of properties.

Listing 6

[Description("Sets input mode")][Category("Behavior")][DefaultValue
  (InputMode.NotSet)]
 
public InputMode InputMode
{
  get
  {
    object o = ViewState["InputMode"];
 
    return o != null ? (InputMode)o: InputMode.NotSet;
  }
  set
  {
    ViewState["InputMode"= value;
  }
}
 
[Description("Mandates if the text is required")][Category("Behavior")
  ][DefaultValue(false)]
public bool IsRequired
{
  get
  {
    object o = ViewState["IsRequired"];
 
    return o == null ? false : true;
  }
  set
  {
    ViewState["IsRequired"= value;
  }
}
 
[Description("Displays messages if textbox input is required")][Category(
  "Behavior")][DefaultValue("")]
public string ErrorMessage
{
  get
  {
    object o = ViewState["ErrorMessage"];
 
    return o != null ? (string)o: "";
  }
  set
  {
    ViewState["ErrorMessage"= value;
  }
}

Since all the features of the control should work on client side we have client side script events to handle this. We have to write JavaScript code that will be emitted by the control as per demanded by the user who specify them through properties.

For this purpose we have to override PreRender event of the control so that our new control will be able to add the JavaScript logic before completing the rendering of the control.

We will add this code in the overridden PreRender event of the control as the following exhibit shows.

Listing 7

protected override void OnPreRender(EventArgs e)
{
  base.OnPreRender(e);
 
  string alphaNumeric = "function alphaNumeric(textBox){ " +
    "if(/[^0-9A-Za-z]/.test(textBox.value)){ " +
    "textBox.value=textBox.value.replace(/([^0-9A-Za-z])/g,''); " +
    "textBox.title = ''; " + "} " +
 
  " }";
 
  string numeric = "function numeric (textBox){ " +
    "if(/[^0-9a-z]/.test(textBox.value)){ " +
    "textBox.value= textBox.value.replace(/([^0-9])/g,''); " +
    "textBox.title = ''; " + "} " +
 
  " }";
  ...

The code snippet shows that we have called PreRender event of the base class i.e. TextBox and then declared and initialized two string members alphaNumeric and numeric that define javascript methods for respective functionality. I will explain one of them as an example and all upcoming JavasSript code will be quite similar. Please note that we have not registered them yet, that will be done later. We have defined alphaNumeric method that takes the textbox object as parameter and tests it on a regular expression (regExp) object by calling test method. If it is true then call the replace method on the result, that replaces any undesired input char with empty string; it uses again the same regExp object with a global search qualifier g to search the input. You may have noticed that before closing the body of the methods, we have cleared the title property of the textbox which shows an error message as the tooltip when IsRequired is set to True. So on any input we should clear the tooltip.

Likewise, define other javascript methods for UppCase,LoweCase, MaxLength and IsRequired features and set the respective string variables so that later could be registered on demand.

Listing 8

string upperCase = "function upperCase(textBox){ " +
"if(/[^0-9A-Z]/.test(textBox.value)){ "+     
"textBox.value=textBox.value.toUpperCase().replace(/([^0-9A-Z])/g,''); "+
"textBox.title = ''; "+ 
     "} " +
    "}";
 
string lowerCase = "function lowerCase(textBox){ " +
"if(/[^0-9A-Z]/.test(textBox.value)){ "+     
"textBox.value=textBox.value.toLowerCase().replace(/([^0-9A-Z])/g,''); "+
"textBox.title = ''; "+ 
     "} "+
    "}";
…

UpperCase and lowerCase methods have similar logic, the only change is the input is upper/lower cased by calling string class’s method toUpperCase() and toLowerCase(). Now it is time to register them on the basis of the property set by the user. The user may form any combination of the properties TextCaseMode (UpperCase,Lowercase,NotSet), InputMode (Numeic,AlphaNUmeric,NotSet). For this purpose we have to define another method which will call these methods depending upon the properties’ values. The following code snippet shows this.

Listing 9

… 
string cleintScript = "function extendedTextBox_PropertyChange(xTextBox){";
// method body starts
if (this.InputMode == InputMode.Numeric)
{
  Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "numeric",
    numeric, true);
 
  cleintScript += "numeric(xTextBox); ";
}
 
if (this.InputMode == InputMode.Alphanumeric)
{
  Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "alphaNumeric",
    alphaNumeric, true);
 
  cleintScript += "alphaNumeric(xTextBox); ";
}
 
if (this.TextCaseMode == TextCaseMode.UpperCase)
{
  Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "upperCase",
    upperCase, true);
 
  cleintScript += "upperCase(xTextBox); ";
}
 
if (this.TextCaseMode == TextCaseMode.LowerCase)
{
  Page.ClientScript.RegisterClientScriptBlock(this.GetType(), "lowerCase",
    lowerCase, true);
 
  cleintScript += "lowerCase(xTextBox); ";
}
 
cleintScript += " }"// end of method body

We have defined a string variable “clientScript” initialized with the method name; the forthcoming code registers script for a particular method and appends its script, i.e being added in the body of the main work horse method extendedTextBox_PropertyChange. At the end, append the string that closes the body of the method. Now we have to register extendedTextBox_PropertyChange method. The following code snippet not only registers it, but also adds it in the attributes collection of the control for onpropertychange event. We can also use onkeypress event, but using this will notice little elapse in uppercasing and lowercasing the input character. 

Listing 10

…
Page.ClientScript.RegisterClientScriptBlock(
     this.GetType(),
     "cleintScript",
     cleintScript, true);
this.Attributes.Add("onpropertychange", "extendedTextBox_ PropertyChange (this);");
…

Next, we have defined checkMaxLength method that makes MaxLength property of the textbox working when its TextMode is set to MultiLine. In TextBox control this problem arises when TextBox is MultiLine because in this case it is rendered into “TextArea” html control (<textarea id="ExtendedTextBox1" />) instead of  “input control with type text” (<input name="ExtendedTextBox1" type="text" id="ExtendedTextBox1). It is important to know that TextArea control does not support MaxLengh property. Therefore, when we set TextMode property to MultiLine, the MaxLength property stops working. To resolve this problem we have to write some client side logic and make part of the control as shown in the following listing.

Listing 11

string checkMaxLength = "function checkMaxLength(textBox) {" +
       " if(textBox) { " +
         " return ( textBox.value.length <" + this.MaxLength + "); " +
       " } " +
     "} ";
if (this.TextMode == TextBoxMode.MultiLine)
  {
    Page.ClientScript.RegisterClientScriptBlock(
         this.GetType(),
         "checkLength",
          checkMaxLength, true);
     this.Attributes.Add("onkeypress", "return checkMaxLength(this);");
  }

It checks and returns true if the length of input is less than or equal to the value of MaxLength property and false if vice versa. Then we have to register it as usual and add in the attributes collection of the control for onkeypress event.

Now, there is another very important feature added in the new textbox control. It mandates the input if specified using the IsRequired property. The implementation of this is simple, we have to handle form’s client side onSubmit event as shown in following code.

Listing 12

if (this.IsRequired == true)
{
         this.Page.Form.Attributes.Add("onSubmit""if(" + this.ClientID +  
 
".value==''){"+ 
        this.ClientID + ".title = '" + this.ErrorMessage + "';"+
        "return false;} "+ 
      "else{ " + this.ClientID + ".title = ''; "+
       " return true;}");
}
} // end of PreRender

Instead of making a separate method, the handler for onSubmit has been added directly in the attributes collection of the control. It checks if IsRequired property is set to True then sets the title property (used for tooltip) of control with the error message given by the user in ErrorMessage property and return false, otherwise clears the title property and return true i.e. allows postbacks.

When IsRequired property is set to True, it performs the required field validation, stops all postabacks and shows the error message in tooltip when the mouse is brought over the control. The Following figure shows this.

Figure 2


View Entire Article

User Comments

Title: Comparing UC vs CC is awesome   
Name: satya
Date: 2010-03-22 11:16:17 PM
Comment:
Differences b/w User controls, Custom controls are explained excellently.
Title: Error Rendering Control   
Name: Luiz Ricardo
Date: 2009-03-27 9:17:41 AM
Comment:
I have "Error rendering control" when I change TextCaseMode or any other enum property at design time.
Did I forget anything?
Title: Good   
Name: Raju
Date: 2009-03-23 6:29:02 AM
Comment:
At design time,if I change the property it will show
error rendering control
Title: Looks promising   
Name: JP
Date: 2008-12-10 5:56:38 AM
Comment:
reusable in true sense!
Title: Testing   
Name: Tester
Date: 2008-12-10 5:40:56 AM
Comment:
Really good
Title: Good show !   
Name: Ananth
Date: 2008-06-09 3:56:36 PM
Comment:
Nice and Neat !!
Title: Really Intresting   
Name: Uzair Aziz
Date: 2008-03-14 8:58:48 AM
Comment:
It is really helpful and easy to understand for me.now i can develop other custom controls.

Tnank you so much.....
Title: thanks   
Name: ngocthom
Date: 2008-01-09 10:58:04 PM
Comment:
thanks so much for your supply, it is very useful






Community Advice: ASP | SQL | XML | Regular Expressions | Windows


©Copyright 1998-2024 ASPAlliance.com  |  Page Processed at 2024-04-24 2:56:23 AM  AspAlliance Recent Articles RSS Feed
About ASPAlliance | Newsgroups | Advertise | Authors | Email Lists | Feedback | Link To Us | Privacy | Search