Click here to Skip to main content
Click here to Skip to main content

Enhanced Textbox Control

, 3 Apr 2010 CPOL
Rate this:
Please Sign up or sign in to vote.
An extended control which doesn't require attaching a validation control, but just needs specifying the input type.

Introduction

In application development, there is always the need for validating input controls. To validate input controls, ASP.NET has a set of validation controls. To apply validation on input controls, you require to attach a validation control with the input control.

Here, I will discuss about an extended control which doesn't require attaching a validation control, but just needs specifying the input type.

Implementation

Custom control implementation

As this is a generic control which can be used by any developer in any ASP.NET application, it inherit the ASP.NET TextBox control to support a basic textbox control and to add extra features for validation; it's basically a custom control, i.e., a custom textbox control.

Properties of the control

All properties use the viewstate to persist values. Another thing to note here is the property appends the ID of the control so that if two textbox controls are dropped on the same page, they do not conflict with each other's property values.

ValidationType

This property allows applying a validation type on the textbox control.

The following validation types are supported by the textbox control:

  • None – allows to enter any type of data.
  • Integer – allows to enter only integer. E.g., 123.
  • Numeric – allows to enter only integer and decimal point values. E.g., 123.12 or 123.
  • Alpha – allows to enter alphabets. E.g., A-Z or a- z.
  • AlphaNumeric – allows to enter alpha and numeric data. E.g., A-Z or a-z or 0-9.
  • Custom – allows to enter data based on patterns specified by the user.
  • Required – allows to enter any kind of data but you are required to enter data in the field.
  • RequiredAndInteger – allows to enter integer and must enter data.
  • RequiredAndNumeric – allows to enter numeric and must enter data.
  • RequiredAndAlpha – allows entering alphabets and must enter data.
  • RequiredAndAlphaNumeric – allows to enter alphanumerics and must enter data.
  • RequiredCustom – allow to enter data based on pattern and must enter data.

AssociatedLabelText

This property allows to set the label that should be displayed with an error message when invalidate data is entered.

public string AssociatedLableText
{
    get
    {
        if (ViewState["LableText" + this.ID.ToString()] == null)
            return "Textbox";

        return (string)(ViewState["LableText" + this.ID.ToString()].ToString());
    }
    set
    {
        ViewState["LableText" + this.ID.ToString()] = value;
    }
}

CustomPattern

This property allows to set a custom Regular Expression to validate the entered data. This property is used when the user wants to validate complex data like email.

public string CustomPattern
{
    get
    {
        if (ViewState["CustomPattern" + this.ID.ToString()] == null)
            return string.Empty;
        
        return (string)(ViewState["CustomPattern" + this.ID.ToString()].ToString());
    }
    set
    {
        ViewState["CustomPattern" + this.ID.ToString()] = value;
    }
}

ReadOnly

This property causes the textbox control to be displayed as a label control so that its does not allow editing data.

public string ReadOnly
 {
    get
    {
        if (ViewState["ReadOnly" + this.ID.ToString()] == null)
            return string.Empty;
        return (string)(ViewState["ReadOnly" + this.ID.ToString()].ToString());
    }
    set
    {
        ViewState["ReadOnly" + this.ID.ToString()] = value;
    }
 }

LabelCSSClass

This property allows to set the CSS class when the textbox has the ReadOnly property set to true.

public string LabelCSSClass
{
    get
    {
        if (ViewState["LabelCSSClass" + this.ID.ToString()] == null)
            return string.Empty;
        return (string)(ViewState["LabelCSSClass" + this.ID.ToString()].ToString());
    }
    set
    {
        ViewState["LabelCSSClass" + this.ID.ToString()] = value;
    }
}

ShowWaterMark

This property allows to set the watermark in textbox when property set to true.

public bool ShowWaterMark
{
	get
	{
		if (ViewState["ShowWaterMark" + this.ID.ToString()] == null)
			return false;
		return Convert.ToBoolean(ViewState["ShowWaterMark" + 
		                         this.ID.ToString()].ToString());
	}
	set
	{
		ViewState["ShowWaterMark" + this.ID.ToString()] = value;
	}
}

WaterMarkText

This property allows to set watermark in textbox when ShowWaterMark propety is set to true.

public string WaterMarkText
{
	get
	{
		if (ViewState["WaterMarkText" + this.ID.ToString()] == null)
			return string.Empty;
		return (string)(ViewState["WaterMarkText" + 
		                this.ID.ToString()].ToString());
	}
	set
	{
		ViewState["WaterMarkText" + this.ID.ToString()] = value;
	}
}

GroupId

This property is used by the validation function to know with which group it is associated. E.g., if groupId = ‘A’ for the textbox, then the validation event is fired by the button control having groupId = ‘A’ and the textbox control is validated. In this case, it is not going to be validated by the control having groupId = ‘B’.

Note: this property is used by the JavaScript only and it's added to the control directly; it works the same as the group property in .NET, but the difference is this is used by the JavaScript function.

Overridden methods

The Render method of the control cause rendering of the control on the client with the HtmlTextWriter object. By using this method, changing the rendering of the textbox control is possible.

Render

protected override void Render(HtmlTextWriter writer)
{

If the ReadOnly property of the control is true, than only span is going to be rendered in place of the textbox control so it is not displayed as an editable control.

if (this.ReadOnly)
{
   writer.Write(@"<span class=""{0}"">{1}</span>", 
                this.LabelCSSClass, this.Text);
}

If the ReadOnly property of the control is false, than it calls the ApplyValidation method which adds JavaScript and validates the data entered by the client in the textbox control.

else if (!this.ReadOnly)
{
    string strError= ApplyValidation(writer);
    base.Render(writer);

    if (((int)this.DataType) > 4)
    {
        writer.Write(strError);
    }
}

ApplyValidation

private string ApplyValidation(HtmlTextWriter writer)
{

The following line of code defines errorMsg and the script which is going to be bound with the textbox control.

string errorMsg = string.Empty;
string script = string.Empty;

As you see below, if DataType has a value more than 5, it adds the required attribute with the value true, which is used by JavaScript to check whether the value is present or not. It also binds the keyup JavaScript method with the onBlur and onKeyUp events. A span tag is also rendered to display an error message when there is no data present in the textbox control.

if (((int)this.DataType) > 5)
{
    this.Attributes.Add("isRequired", "true");
    this.Attributes.Add("onKeyUp", "keyup('" + this.ClientID + "')");
    this.Attributes.Add("onBlur", "keyup('" + this.ClientID + "')");

    errorMsg = string.Format(@"<span id='spError{2}' style='display:" + 
                             @"none;' >{0}   {1}</span>", 
                             "Enter value in ", this.AssociatedLableText, 
                             this.ClientID);
}

The below line of code adds the ShowWaterMark attribute to the textbox rendered at the client, which is used by the JavaScript function.

this.Attributes.Add("ShowWaterMark", this.ShowWaterMark.ToString());

If the ShowWaterMark value is set to true, than it adds the LabelValue attribute to the textbox control which is displayed as the watermark in the textbox:

if(this.ShowWaterMark)
    this.Attributes.Add("LabelValue", this.WaterMarkText);

If DataType is less than 6, it means it is not required to attach the showWaterMarkText method to the onblur event.

if (((int)this.DataType) < 6)
    this.Attributes.Add("onBlur", "showWaterMarkText('" + this.ClientID + "')");

If the DataType is Integer, requiredAndInteger, numeric, or requireAndNumeric, it binds the IntegerAndDecimal method with onKeyPress so that it is only allowed to enter integer or decimal data in the textbox control.

if (this.DataType == ValidationType.integer||
    this.DataType == ValidationType.requiredAndInteger)
{
    script="IntegerAndDecimal(event,this,'false')";
}

if (this.DataType == ValidationType.numeric ||
    this.DataType == ValidationType.requiredAndNumeric)
{
    script = "IntegerAndDecimal(event,this,'true')";
}

An important thing to note here is the third argument passed to the JavaScript function: when the value passed to the argument is true, it means it allows to enter decimal data, and if it is false, it allows to enter integer data without a decimal point.

If the DataType is alpha, requiredAndalpha, alphaNumeric, or requireAndAlphaNumeric, it binds the AlphaNumericOnly method with onKeyPress so that it allows to enter integer or decimal data only in the textbox control.

if (this.DataType == ValidationType.alpha ||
   this.DataType == ValidationType.requiredAndAlpha)
{
    script = "return AlphaNumericOnly(event,'true')";
}


if (this.DataType == ValidationType.alphaNuemric ||
    this.DataType == ValidationType.requiredAndAlphaNumeric)
{
    script = "return AlphaNumericOnly(event,'false')";
}

An important thing to note here is the second argument passed to the JavaScript function: when the value passed to the argument is true, it allows to enter alpha characters only, and when it is false, it allows to enter alphanumeric characters.

If the DataType is custom or requiredCustom, it binds the checkReg function with the onBlue event. It also adds a span tag with the text box to display an error message when the data entered doesn't match with the Regular Expression assigned to the CustomPattern property. It adds the pattern attribute with the textbox to validate the data.

if (this.DataType == ValidationType.custom ||
    this.DataType == ValidationType.requiredCustom)
{
    errorMsg = errorMsg +
        string.Format(@"<span id='spCuError{2}' style='display:none;' >{0} {1}</span>", 
                      "Invalid data in ", this.AssociatedLableText, this.ClientID);


    this.Attributes.Add("pattern", this.CustomPattern);
    
    if (this.DataType == ValidationType.custom)
        this.Attributes.Add("onBlur", "checkReg(this,'" + this.CustomPattern + "');");
    else
        this.Attributes.Add("onBlur", "keyup('" + this.ClientID + 
             "'); checkReg(this,'" + this.CustomPattern + "');");
}

If the TextMode of the textbox control is set to MultiLine, it binds the CountText method with the onKeyPress event, so that it doesn't allow to enter characters more than a maximum length.

if (this.TextMode == TextBoxMode.MultiLine)
{
    if (this.MaxLength <= 0)
        this.MaxLength = 200;
    script = "return CountText(this,'" + this.MaxLength + "')";
}

As CountText is bound to onKeryPress, it overrides other scripts bind on the control with the keypress event.

The following line of code binds the script with the onKeyPress event.

this.Attributes.Add("onKeypress", script);

This code returns the errorMsg associated with the textbox:

    return errorMsg;
}

JavaScript functions

IntegerAndDecimal

This method allows to enter only integer and decimal data in the textbox control. If the isDecimal value is true, then it allows to enter decimal point values; otherwise, it allows to enter integer values only.

function IntegerAndDecimal(e,obj,isDecimal)
{
    if ([e.keyCode||e.which]==8) //this is to allow backspace
    return true;
    
    if ([e.keyCode||e.which]==46) //this is to allow decimal point
    {
      if(isDecimal=='true')
      {
        var val = obj.value;
        if(val.indexOf(".") > -1)
        {
            e.returnValue = false;
            return false;
        }
        return true;
      }
      else
      {
        e.returnValue = false;
        return false;
      }
    }
    
    if ([e.keyCode||e.which] < 48 || [e.keyCode||e.which] > 57)
    e.preventDefault? e.preventDefault() : e.returnValue = false; 
}

AlphaNumericOnly

This function allows to enter only alphabets or alphanumeric values in the textbox control. It checks if isAlphaonly is true, then allows to enter only alphabets;otherwise, allows to enter alphanumeric values in the textbox control.

function AlphaNumericOnly(e,isAlphaonly)
{
   // copyright 1999 Idocs, Inc. http://www.idocs.com
   var key = [e.keyCode||e.which];
        
   var keychar = String.fromCharCode([e.keyCode||e.which]);
   keychar = keychar.toLowerCase();
     
   if(isAlphaonly=='true')
         checkString="abcdefghijklmnopqrstuvwxyz";
   else 
         checkString="abcdefghijklmnopqrstuvwxyz0123456789";
   
   if ((key==null) || (key==0) || (key==8) || 
         (key==9) || (key==13) || (key==27) )
        return true;
   else if (((checkString).indexOf(keychar) > -1))
        return true;
   else
        return false;
}

keyup

This function shows and hides the error message associated with the textbox control when the textbox Required property is set to true. It also calls the showWaterMarkText method if the ShowWaterMark text attribute value is set to true.

function keyup(cid)
{
    var showWaterMark = $('#' + cid).attr("ShowWaterMark");
    
    if (showWaterMark === "True")
        test = watermark + $('#' + cid).attr("LabelValue");
    else
        test = '';
      if ($('#' + cid).val() === '' || $('#' + cid).val() === test) {
        document.getElementById('spError' + cid).style.display = 'block';
        $('#' + cid).removeClass('normal');
        $('#' + cid).addClass('mandatory');
    }
    else {
        document.getElementById('spError' + cid).style.display = 'none';
        $('#' + cid).removeClass('mandatory');
        $('#' + cid).addClass('normal');
    }

    if (showWaterMark === "True")
        showWaterMarkText(cid);
}

CountText

This function does not allow entering more characters than maxlimit. Note: it's used only for multiline textboxes.

function CountText(field, maxlimit) 
{
   if (field.value.length < maxlimit) // if too long...trim it!
    {
        return true;
    }
    else
      return false;
}

checkReg

This function is to display the error message when data entered is not valid according to the custom pattern specified in the CustomPattern property.

function checkReg(obj,pattern)
{ 
  if(obj.value!=='' && pattern!=='')
  {
    var pattern1= new RegExp(pattern);
    if(obj.value.match(pattern1))
    {         
        document.getElementById('spCuError'+obj.id).style.display='none';
        return true;
    }
    else
    {   
        document.getElementById('spCuError'+obj.id).style.display='block';
        return false;
    }
  } 
}

callOnload

This function sets the style of the textbox to the mandatory CSS class when the Required property is set to true and no data can be entered by the user. It also calls the showWaterMark function to show the watermark text in the textbox.

function callOnload()
{
    $("input[isRequired=true]").each(
        function(n)
        {
            $('#' +this.id).addClass('mandatory');
           
                 if(this.value==='')
                {
                   $('#' +this.id).removeClass('normal');
                   $('#' +this.id).addClass('mandatory');
                }
                else
                {
                   $('#' +this.id).removeClass('mandatory');
                   $('#' +this.id).addClass('normal');
                }
           
        }
     );
    $("textarea[isRequired=true]").each(
        function(n)
        {
            $('#' +this.id).addClass('mandatory');
             if(this.value==='')
                {
                   $('#' +this.id).removeClass('normal');
                   $('#' +this.id).addClass('mandatory');
                }
                else
                {
                   $('#' +this.id).removeClass('mandatory');
                   $('#' +this.id).addClass('normal');
                }
        }
     );
    showWaterMark(); // call function to add watermark to textbox        
}

showWaterMark

This function get called by callOnLoad method this function show watermarktext in textbox control when the ShowWaterMark attribute value is set to true.

var watermark = "";
function showWaterMark() {
    $("input[ShowWaterMark=True]").each(
        function(n) {
            showWaterMarkText(this.id);
            $('#' + this.id).focus(function() {
                test = watermark + $('#' + this.id).attr("LabelValue");
                if (this.value == test) {
                    this.value = "";
                }
            });
        }
     );
     $("textarea[ShowWaterMark=True]").each(
        function(n) {
            showWaterMarkText(this.id);
                $('#' + this.id).focus(function() {
                    test = watermark + $('#' + this.id).attr("LabelValue");
                    if (this.value == test) {
                        this.value = "";
                    }
                });
            }
        );
}

showWaterMarkText

This function is called on onBlur event or form keyUp event. Function show watermarktext if the there is no value in textbox or value is equal to watermarktext, Otherwise it remove the watermark CSS values.

function showWaterMarkText(id) {
    test = watermark + $('#' + id).attr("LabelValue");
    if ($('#' + id).val() === ''
                || $('#' + id).val() == test
                    ) {
        $('#' + id).val(watermark + $('#' + id).attr("LabelValue"));
        $('#' + id).css("color", "#999");
        $('#' + id).css("font-style", "italic");
        if(document.getElementById('spError' + id) !== null)
            document.getElementById('spError' + id).style.display = 'none';    
    }
    else {
        $('#' + id).css("color", "#000");
        $('#' + id).css("font-style", "");
    }
}

validateFormInputs

The function attached with the button click which submits data to the server, but before that, it validates all the textbox controls having the groupid which is passed in as a function parameter.

function validateFormInputs(gropId) {

    var isAllValid = true;
    var searchConditon = "";
    if (gropId !== "" && gropId !== undefined) {
        searchConditon = searchConditon + 
           "input[isRequired=true][GroupId=" + gropId + "]";
    }
    else {
        searchConditon = searchConditon + "input[isRequired=true]";
    }
    $(searchConditon).each(
          function(n) {
              test = watermark + $('#' + this.id).attr("LabelValue");
    		  if (this.value === '' || this.value == test) 
                  isAllValid = false;
                  document.getElementById('spError' + 
                          this.id).style.display = 'block';
                  //alert($('#' +this.id).attr('errprMsg'));
              }
              else {
                  if (this.pattern !== '' && this.pattern !== undefined) {
                      if (!checkReg(this, this.pattern)) {
                            isAllValid = false;
                      }
                  }
                  if (document.getElementById('spError' + this.id) != null)
                        document.getElementById('spError' + 
                                this.id).style.display = 'none';
              }
          }
         );
    searchConditon = "";
    if (gropId !== "" && gropId !== undefined) {
        searchConditon = searchConditon + 
           "textarea[isRequired=true][GroupId=" + gropId + "]";
    }
    else {
        searchConditon = searchConditon + "textarea[isRequired=true]";
    }
    $(searchConditon).each(
          function(n) {
              test = watermark + $('#' + this.id).attr("LabelValue");
              if (this.value === ''|| this.value == test)
                  isAllValid = false;
                  document.getElementById('spError' + 
                            this.id).style.display = 'block';
              }
              else {
                  if (document.getElementById('spError' + this.id) != null)
                      document.getElementById('spError' + 
                                this.id).style.display = 'none';
              }
          }
         );
    return isAllValid;
}

Note: You need to pass the group ID only when you have to validate the controls related to the given group, i.e., if there are two or more groups of the control validated by different buttons.

Advantages

  • SQL injection attack
  • Because the control only allows to enter data specified in a property, it does not allow entering unwanted characters which creates problems when doing database DML operations. Read more about SQL injection attacks here: http://en.wikipedia.org/wiki/SQL_injection.

  • Rendering cost decrease
  • It also decreases the cost of rendering. Because you attach the validation control itself on the page and also emit JavaScript if the EnableClient property is set to true.

  • Multiple type of validation in one textbox control
  • By setting the ValidationType property, we can apply more than one validation method, e.g., required + alpha, required + numeric etc.

  • Watermark
  • If showWaterMark = true, it allows to show a watermark in the textbox control, which can be used to show help text or a text value to enter in the textbox.

  • ReadOnly
  • Using this property, we can show the textbox as a label when the form is in read-only mode, i.e., in non-editable form just for viewing purposes.

How to use

Following is an implementation of the textbox control on a page. To get more information about this, download the code and check it out.

<form id="form1" runat="server">
    <table>
      <tr>
        <td>
         <!--To Allow only ahlpha numeric char. 
                   Data must required to enter by user before submit. -->
            <cc:MyTextBox runat="server" id="MyTextBox2" GroupId="A" 
              AssociatedLableText="FirstName" DataType="requiredAndAlphaNumeric" >
            </cc:MyTextBox>
        </td>
        <td>
           <!--To Allow only ahlpha char. Data must required to
                          enter by user before submit. -->
            <cc:MyTextBox runat="server" id="MyTextBox1" GroupId="A" 
              AssociatedLableText="LastName" DataType="requiredAndAlpha" >
            </cc:MyTextBox>
        </td>
        <td>
            <!--Data must required to enter by user before submit. -->
            <cc:MyTextBox runat="server" id="MyTextBox3" GroupId="B" 
              AssociatedLableText="Remark"  TextMode="MultiLine" 
              DataType="required" ></cc:MyTextBox>
        </td>
        <td>
            <!--Data must match with pattern specified . 
                   Data must required to enter by user before submit. -->
            <cc:MyTextBox runat="server" id="MyTextBox4" GroupId="A"
                AssociatedLableText="Email"   
                DataType="requiredCustom" 
                CustomPattern="([a-zA-Z0-9_.-])+@([a-zA-Z0-9_.-])+
                                \.([a-zA-Z])+([a-zA-Z])+" >
            </cc:MyTextBox>
        </td>
        <td>
            <cc:MyTextBox runat="server" id="MyTextBox5"  AssociatedLableText="Test" 
                 ShowWaterMark="true" WaterMarkText="TestBox" ></cc:MyTextBox>
	    </td>
      </tr>
    </table>
    
    <!-- Validate only textboxes which is having gorupid=A when button is clicked-->
     <cc:MyButton runat="server" ID="mybutton" 
        OnOnCustomCommand="mybutton_OnCustomCommand" CausesValidation="true" 
        OnClientClick="return validateFormInputs('A');"  CommandArgument="" Text="test" />  
</form>

Conclusion

This textbox control implementation provides a basic implementation of a custom control with the use of jQuery and JavaScript.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Pranay Rana
Software Developer (Senior) GMind Solusion
India India

Microsoft C# MVP (12-13)

 
Hey, I am Pranay Rana, working as a ITA in MNC. Web development in Asp.Net with C# and MS sql server are the experience tools that I have had for the past 5.5 years now.
 
For me def. of programming is : Programming is something that you do once and that get used by multiple for many years
 

You can visit my blog

StackOverFlow - http://stackoverflow.com/users/314488/pranay
My CV :- http://careers.stackoverflow.com/pranayamr
 
Awards:


Follow on   Twitter   LinkedIn

Comments and Discussions

 
GeneralMy vote of 5 PinmemberRahul Rajat Singh26-Jun-12 5:17 
GeneralMy vote of 5 PinmemberUday P.Singh17-Dec-11 9:06 
GeneralMy vote of 5 Pinmemberjashimu9-May-11 5:06 
GeneralMy vote of 5 Pinmemberwynnminthiri6-Feb-11 19:00 
GeneralRe: My vote of 5 PinmemberPranay Rana7-Feb-11 2:05 
GeneralNice Article PinmemberWonde Tadesse25-Jan-11 14:20 
GeneralRe: Nice Article PinmemberPranay Rana7-Feb-11 2:06 
GeneralMy vote of 5 PinmemberRaviRanjankr24-Dec-10 1:48 
GeneralRe: My vote of 5 PinmemberPranay Rana7-Feb-11 2:06 
GeneralGood work Pinmembertushar22914-Dec-10 20:09 
GeneralRe: Good work PinmemberPranay Rana7-Feb-11 2:07 
GeneralCongrats!!! PinmemberJinal Desai - LIVE26-May-10 1:01 
GeneralRe: Congrats!!! PinmemberPranay Rana7-Feb-11 2:07 
GeneralGood effort Pinmemberthe_cool_one24-Apr-10 18:09 
GeneralRe: Good effort PinmemberPranay Rana25-Apr-10 8:36 
GeneralRe: Good effort PinmemberSandesh M Patil18-Aug-10 2:50 
GeneralRe: Good effort PinmemberPranay Rana18-Aug-10 2:54 
GeneralRe: Good effort PinmemberSandesh M Patil18-Aug-10 4:17 
GeneralRe: Good effort PinmemberPranay Rana18-Aug-10 19:39 
GeneralGood Job PinmemberHemangDVyas24-Apr-10 1:51 
GeneralRe: Good Job PinmemberPranay Rana24-Apr-10 4:47 
GeneralMore room for improvement PinmemberNavnath R. Kale15-Apr-10 21:40 
GeneralRe: More room for improvement PinmemberPranay Rana15-Apr-10 23:16 
Generalgood contribution PinmemberPavan Nagumalli13-Apr-10 19:57 
GeneralRe: good contribution PinmemberPranay Rana13-Apr-10 20:19 
GeneralVery Nice Article PinmemberMember 2821278 (Mohan Prajapati)12-Apr-10 20:59 
GeneralRe: Very Nice Article PinmemberPranay Rana13-Apr-10 20:18 
GeneralGood Work Pranay PinmvpAbhijit Jana11-Apr-10 20:14 
GeneralRe: Good Work Pranay PinmemberPranay Rana11-Apr-10 21:16 
GeneralNice Control Pranay PinmemberKrunal Mevada4-Apr-10 20:53 
GeneralImprovement for your Excellent Control Pinmemberrctaubert30-Mar-10 4:50 
GeneralNice package! PinmemberSandeep Mewara28-Mar-10 8:52 
GeneralRe: Nice package! PinmemberPranay Rana28-Mar-10 19:57 
QuestionWhat about other locales? Pinmembermav.northwind26-Mar-10 20:54 
AnswerRe: What about other locales? PinmemberPranay Rana26-Mar-10 22:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.141030.1 | Last Updated 3 Apr 2010
Article Copyright 2010 by Pranay Rana
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid