Pranay Rana: April 2010

Friday, April 9, 2010

Enhanced Textbox Control


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 withonKeyPress 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 withonKeyPress 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 thepattern 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 CustomPatternproperty.
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 thegroupid 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';
          }
          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 theEnableClient 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 toenter 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.

ASP.NET Extended Grid Control

Introduction
In this article, I am going to discuss about my EnhanceGrid which has the following features:
  1. Custom paging to fetch only the required number of records
  2. Icon in the header of the column to display sorting direction
  3. Dropdown in the pager to adjust the number of records in a page
  4. Properties of the extended grid
  5. How to use the custom grid control
Custom paging to fetch only the required number of records
Following is the Stored Procedure I use to provide custom paging feature in my custom grid. The comments explain the fields and give detailed information about the Stored Procedure:
CREATE PROCEDURE [dbo].[GetRequestedRecordByPage]
 @FromList nvarchar(200)              -- Table Name 
,@SortingCol nvarchar(200)         -- Sorting column Name
,@SelectList nvarchar(200) = '*'         -- Select columns list
,@WhereClause nvarchar(200) = ''        -- Where clause i.e condition
,@PageNum int = 1                           -- Requested page number
,@PageSize int = 5                 -- No of record in page
,@TotalNoOfRecord int output         -- Total no of selected records
AS
Begin
  SET NOCOUNT ON
  DECLARE @Query nvarchar(max)         -- query going to be execute

  IF rtrim(ltrim(@WhereClause)) <> ''
  BEGIN
      SET @Query ='SELECT   @TotalNoOfRecord = COUNT(*)
                      FROM     ' + @FromList + '
        WHERE    ' + @WhereClause
  END
  ELSE
  BEGIN
      SET @Query ='SELECT   @TotalNoOfRecord = COUNT(*)
                      FROM     ' + @FromList
  END

    /* Count no. of record */
       EXEC sp_executeSQL
        @Query,
        @params = N'@TotalNoOfRecord INT OUTPUT',
         = @TotalNoOfRecord OUTPUT

DECLARE @lbound int, @ubound int




/* Calculating upper and lower bound */
        SET @lbound = ((@PageNum - 1) * @PageSize)
        SET @ubound = @lbound + @PageSize + 1


/* Get list of record(s) */
        SELECT @Query =  ''
        SELECT @Query =  'SELECT  *
                          FROM    (
SELECT  ROW_NUMBER() OVER(ORDER BY ' + @SortingCol  + ') AS rownumber,' +@SelectList  +  
                                        ' FROM    ' + @FromList
       
        IF rtrim(ltrim(@WhereClause)) <> ''
        BEGIN
            SELECT @Query = @Query + ' WHERE   ' + @WhereClause
        END

            SELECT @Query = @Query + '     ) AS tbl
WHERE rownumber > ' + CONVERT(varchar(9), @lbound) +
      ' AND rownumber < ' + CONVERT(varchar(9), @ubound)

       EXEC (@Query)                
End
As you can see, the above procedure is generic so that it can be used anywhere. I have therefore included it as part of my EnhanceGrid, as it is applicable in any project developed with ASP.NET.
Icon in the header of the column to display sorting direction
To shows the sorting icon in the header text, I have overridden the following events of the standard Grid control which is part of the .NET Framework.
  • OnRowCreated: This event gets fired when rows are created. Here, if the RowType is Header, then I add a sorting icon by getting the index of the item that gets clicked, and then I call the SortingIcon method to add the icon in the header row.
protected override void OnRowCreated(GridViewRowEventArgs e)
{
    if (e.Row.RowType == DataControlRowType.Header)
    {
        /* Get sorting column index */
        int index = GetSortColumnIndex();
        if (index != -1)
        {
            sortingIcon(index, e.Row);
        }
    }
}
  • OnSorting: This event gets called when the user clicks on the header column to sort records of the grid control. As you can seen from the code below, it stores the column sort expression in lblSortExp and stores the sort direction in lblSortDirection and reverses the currently stored value.
protected override void OnSorting(GridViewSortEventArgs e)
{
    try
    {
        lblSortExp = e.SortExpression;

        switch (lblSortDirection)
        {
            case "asc":
            {
                lblSortDirection = "desc";
                break;
            }
            case "desc":
            case "":
            case null:
            {
                lblSortDirection = "asc";
                break;
            }
        }
        BindGrid();
    }
    catch (Exception ex) { }
}
  • sortingIcon: This function is used to add sorting icons in the header of the column. In this function, I have created a Label which is added dynamically with the header text in the clicked header column.
private void sortingIcon(int index, GridViewRow row)
{
    System.Web.UI.WebControls.Label lblSorting =
                  new System.Web.UI.WebControls.Label();
    if (lblSortDirection == "desc")
    {
        lblSorting.Text = "<span style=\"font-family:" +
                          "Marlett; font-weight:bold\">6</span>";
    }
    else
    {
        lblSorting.Text = "<span style=\"font-family:Marlett;" +
                          " font-weight:bold\">5</span>";
    }
    row.Cells[index].Controls.Add(lblSorting);
}
  • GetSortColumnIndex: This function is used to get the index of the column which is clicked by the user for sorting. In this function, I compare the sorting expression of the clicked column with each column and get the index of the clicked column. This is needed because I don’t know the index of the clicked column.
private int GetSortColumnIndex()
{
    foreach (DataControlField field in this.Columns)
    {
        if (field.SortExpression.ToString() == lblSortExp)
        {
            return this.Columns.IndexOf(field);
        }
    }
    return -1;
}
Dropdown in the pager to adjust the number of records per page
For the number of records per page, I have overridden the same method that I overrode to show the sorting icon, but here the condition is changed. I check for the pager type row, as you can see below. In the code below, I have created a dropdown control which contains the per page record number and the attached selected change.
protected override void OnRowCreated(GridViewRowEventArgs e)
{
    try
    {
        if (e.Row.RowType == DataControlRowType.Pager)
        {
            DropDownList ddl ddlNoPages = new DropDownList();
            //adds variants of pager size
            ddlNoPages.Items.Add("10");
            ddlNoPages.Items.Add("50");
            ddlNoPages.Items.Add("100");
            ddlNoPages.AutoPostBack = true;
            //selects item due to the GridView current page size
            ListItem li = ddlNoPages.Items.FindByText(this.PageSize.ToString());
                    
            if (li != null)
                ddlNoPages.SelectedIndex = ddlNoPages.Items.IndexOf(li);
          
            ddlNoPages.SelectedIndexChanged +=
              new EventHandler(ddlNoPages _SelectedIndexChanged);
         
            //adds dropdownlist in the additional cell to the pager table
            Table pagerTable = e.Row.Cells[0].Controls[0] as Table;
            TableCell cell = new TableCell();
            cell.Style["padding-left"] = "50px";
            cell.Style["text-align"] = "right";
            cell.Controls.Add(new LiteralControl("Page Size:"));

            cell.Controls.Add(ddlNoPages);
            //e.Row.Cells[0].Controls.Add(cell);
            pagerTable.Rows[0].Cells.Add(cell);
       }
   }
   catch (Exception ex)
   {
   }
}
The following event gets fired when the combobox index gets changed. The code is very simple to understand, so I think there is no need to explain it in detail.
void ddlNoPages_SelectedIndexChanged(object sender, EventArgs e)
{
    if (PageSize > int.Parse(((DropDownList)sender).SelectedValue))
        IsPagesizeChanged = true;
    else
        IsPagesizeChanged = false;
    PageIndex = 0;
    //changes page size
    PageSize = int.Parse(((DropDownList)sender).SelectedValue);
    //binds data source
    BindGrid();
}
Note: There are still some errors in this method in which I am working, and I will provide a full solution for this.
After the above gets done, you require to override the following method of the grid for the custom paging Store Procedure to work. In this method, you have to set the values for the paging datasource properties.
protected override void InitializePager(GridViewRow row,
          int columnSpan, PagedDataSource pagedDataSource)
{
   try
   {
     #region code for standard paging
     //To set custome paging
     pagedDataSource.AllowCustomPaging = true;
     //To set total no of records retived
     pagedDataSource.VirtualCount = Convert.ToInt32(lbltotal);
     //To set current page index
     pagedDataSource.CurrentPageIndex = lblpageIndex;
     #endregion code for standard paging
              
     base.InitializePager(row, columnSpan, pagedDataSource);
    }
    catch (Exception ex) { }
}
You can also override the above method to provide custom paging of a different kind as per you client requirements.
Properties of the extended grid
Note that, most of the properties get their value form the ViewState and are set in the ViewState. It is by using the ViewState mechanism that the grid retains property values during postback events.
Another thing to note here is that the control ID is attached with each ViewState property because if you drop the same grid control two or more times on one page, it works property without interfering with other grid operations.
  • lbltotal: This property of the grid is used to store the total number of records retrieved by the Stored Procedure; it is used to adjust the paging accordingly.
public int lbltotal
{
    get
    {
        if (null != ViewState["lbltotal" + ControlID])
                 return (int)ViewState["lbltotal" + ControlID];
        else
                 return 0;
    }
    set
    {
          ViewState["lbltotal" + ControlID] = value;
    }
}
  • lblpageIndex: Stores the current page index.
public int lblpageIndex

{
   get
   {
        if (null != ViewState["lblpageIndex" + ControlID])
                return (int)ViewState["lblpageIndex" + ControlID];
        else
                 return 0;
   }
   set
   {
          ViewState["lblpageIndex" + ControlID] = value;
   }
}
  • lblSortDirection: Stores the sorting direction of the column.
public string lblSortDirection
{
  get
  {
     if (null != ViewState["lblSortDirection" + ControlID])
        return (string)ViewState["lblSortDirection" + ControlID];
     else
        return string.Empty;
  }
  set
  {
     ViewState["lblSortDirection" + ControlID] = value;
  }
}
  • lblSortExp: Stores the sorting expression, i.e., column sorting expression.
public string lblSortExp
{
    get
    {
        if (null != ViewState["lblSortExp" + ControlID])
            return (string)ViewState["lblSortExp" + ControlID];
        else
            return string.Empty;
    }
    set
    {
        ViewState["lblSortExp" + ControlID] = value;
    }
}
  • FromClause: Stores the From clause of the query which is passed to the Stored Procedure to retrieve records.
public string FromClause
{
    get
    {
       if (null != ViewState["fromClause" + ControlID])
         return (string)ViewState["fromClause" + ControlID];
       else
          return string.Empty;
    }
    set
    {
         ViewState["fromClause" + ControlID] = value;
    }
}
  • WhereClause: Stores the Where clause of the query which is passed as the where condition of the query to the Stored Procedure.
public string WhereClause
{
    get
    {
        if (null != ViewState["whereClause" + ControlID])
            return (string)ViewState["whereClause" + ControlID];
         else
             return string.Empty;
     }
     set
     {
         ViewState["whereClause" + ControlID] = value;
     }
}
  • SelectList: Stores the select list column name which is going to be passed to the Stored Procedure.
public string SelectList
{
   get
   {
       if (null != ViewState["selectList" + ControlID])
           return (string)ViewState["selectList" + ControlID];
       else
           return string.Empty;
   }
   set
   {
       ViewState["selectList" + ControlID] = value;
   }
}
  • ControlID: Stores the ID of the control.
private string _controlId;

public string ControlID
{
   get { return _controlId; }
   set { _controlId = value; }
}
  • DefaultSortExp: Stores the default sort expression which is used by the grid for sorting purposes till the first sorting event occurs.
private string _DefaultSortExp;
public string DefaultSortExp
{
     set{ _DefaultSortExp = value;}
     get{ return _DefaultSortExp;}
}
Other important things
Following are the properties which allow to attach your own events when using the grid on a page.
public event GridViewRowEventHandler onRowCreate
{
    add
    {
        base.RowCreated += value;
    }
    remove
    {
        base.RowCreated -= value;
    }
}

public event GridViewSortEventHandler onSort
{

    add
    {
         base.Sorting += value;
    }
    remove
    {
         base.Sorting -= value;
    }
}
How to use the custom grid control
Following is the code to register the grid control on your page:
<%@ Register TagPrefix="cc"  Namespace="AppEngine.ComponentControls" Assembly="__code" %>
Here is the code to use the grid control on your ASPX page:
<cc:MyGridView  runat="server" ID="grdEmployee"
      AutoGenerateColumns="False" AllowPaging="true"
      AllowSorting="true" DefaultSortExp="FIRSTNAME"
      EnableSortingAndPagingCallbacks = "false">
  <Columns>
    <asp:BoundField DataField="FIRSTNAME"
        HeaderText="FIRSTNAME" SortExpression="FIRSTNAME" />
    <asp:BoundField DataField="LASTNAME"
        HeaderText="LASTNAME" SortExpression="LASTNAME" />
    <asp:BoundField DataField="LOGINNAME"
        HeaderText="LOGINNAME" SortExpression="LOGINNAME" />
    <asp:BoundField DataField="EMAIL"
        HeaderText="EMAIL" SortExpression="EMAIL" />
  </Columns>
  <PagerSettings Mode="NumericFirstLast" Position ="TopAndBottom"
             PageButtonCount="5"  />
  <PagerStyle BackColor="Pink" />
</cc:MyGridView>
The following code is part of your aspx.cs file. As you can see in the code below, I specified SelectList, which is a list of columns; FromClausecontains the table name, which is Employee here; WhereClause has the filter condition.
protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        grdEmployee.SelectList = "[FIRSTNAME],[LASTNAME],[LOGINNAME],[EMAIL]";
        grdEmployee.FromClause = "[EMPLOYEE]";
        grdEmployee.WhereClause = string.Empty;
        //grdEmployee.WhereClause = "[FIRSTNAME] like '%a'";
        grdEmployee.BindGrid();
    }
}
Search with Grid
Following is an example of searching data in the EnhanceGrid control:
For searching purposes, I have added the following controls on the page where I drop the grid control which helps to search a record:
  • ddlColumn: Which contains the name of the column which is going to be displayed by the EnhanceGrid control. The point to note here is the value field of the the list item which has the names of the columns of the database table.
  • txtValue: The control which allows the user to enter a value to search for a particular column.
<div style="width: 100%;">
<div style="float: left; width : 10%;">
<asp:Label runat="server" ID="lblSearch" Text="Select Criteria"></asp:Label>
        </div>
        <div style="float: left; width :10%;">
            <asp:DropDownList runat="server" ID="ddlColumn">
<asp:ListItem Text="First Name" Value="FIRSTNAME"></asp:ListItem>
<asp:ListItem Text="Last Name" Value="LASTNAME"></asp:ListItem>
<asp:ListItem Text="Login ID" Value="LOGINNAME"></asp:ListItem>
<asp:ListItem Text="E-mail ID" Value="EMAIL"></asp:ListItem>
            </asp:DropDownList>
        </div>
  <div style="float: left; width :10%;">
<asp:Label runat="server" ID="lblValue" Text="Value"></asp:Label>
        </div>
        <div style="float: left; width :15%;">
            <asp:TextBox runat="server" ID="txtValue" ></asp:TextBox>
        </div>
        <div>
            <asp:Button runat="server" ID="btnSearch" Text="Search"
                onclick="btnSearch_Click" />
        </div>
</div>
  • btnSearch: Which contains the code for searching a particular data from a database. As you can see in the code below, if there are values present in the txtValue text field, it forms a Where clause, and if not present, then it passes an empty Where clause which searches all the records of the grid control.
protected void btnSearch_Click(object sender, EventArgs e)
{
    if (!string.IsNullOrEmpty(txtValue.Text))
    {
        grdEmployee.WhereClause = ddlColumn.SelectedValue +
                                  " like '%" + txtValue.Text + "%'";
    }
    else
    {
        grdEmployee.WhereClause = string.Empty;
    }
    grdEmployee.SelectList =
      "[FIRSTNAME],[LASTNAME],[LOGINNAME],[EMAIL]";
    grdEmployee.FromClause = "[EMPLOYEE]";
    grdEmployee.BindGrid();