Click here to Skip to main content
15,867,141 members
Articles / Web Development / XHTML

Paging, Selecting, Deleting, Editing and Sorting in the ASP.NET GridView Control with Keyboard Shortcuts

Rate me:
Please Sign up or sign in to vote.
4.89/5 (32 votes)
5 Oct 2010CPOL8 min read 157.2K   6.3K   137   20
An ASP.NET 2.0 AJAX Extender to enhance the ASP.NET GridView to page, select, delete, edit and sort rows with keyboard shortcuts.
GridViewKeyboardExtender_src

Introduction

The ASP.NET 2.0 Framework comes with the GridView, a feature-rich ASP.NET server control to display, page through, and edit data on a webpage. Paging, selecting, sorting and editing of the GridView is achieved by declaratively adding properties to the markup of the GridView. This adds link buttons to the data rows of the GridView. You have to click on one of the link buttons to delete or edit a row. One feature I missed is the ability to use these features via keyboard shortcuts.

To achieve that, I developed an ASP.NET 2.0 AJAX Extensions Extender Control for use with ASP.NET 2.0 GridView on ASP.NET 2.0 AJAX enabled web pages.

What You Can Do with the Extender and How to Use It

You can use this extender control in existing ASP.NET 2.0 AJAX Extension application web pages which contain the ASP.NET 2.0 GridView server control.

In the current version, the extender control must be placed at the same level in the control tree as the GridView it belongs to.

In the current release, the extender provides the following navigation and editing features, and the following default keyboard shortcut scheme:

Feature Feature Description Default Shortcut Key
Paging If paging is enabled for the GridView, you have shortcuts to navigate to the next, previous, first, and last pages. You can navigate to first and last page by keyboard even if the link buttons for the first and last page aren't currently visible.
  • Next page: Right arrow key
  • Previous page: Left arrow key
  • First Page: ALT + Pos1
  • Last Page: ALT + End
Selecting If selecting is enabled and a row is preselected, you have shortcuts to navigate to the next and previous row.
  • Select next row: Down key
  • Select previous row: Up key
Edit If editing is enabled and a row is preselected, you have shortcuts to all editing features.
  • Switch selected row to edit mode: ALT + m key
  • Cancel editing: ALT + q key
  • Update changes: ALT + u key
Delete If deleting is enabled and a row is preselected, you have shortcuts to delete that row.
  • Delete row: Del key
Sort The GridView keyboard Extender supports sorting of columns now. To activate sorting on the GridView, you need to do the following:
  • Set property AutoGenerateColumns='False' of the GridView.
  • Add columns to GridView Columns collection manually. The columns must be of type BoundField or a derived column type.
  • Set property AllowSorting='true' of the GridView.
  • Set property AllowSorting='true' (default) of the Extender.
  • GridView column must have a value for property SortExpression.
The extender adds support for sorting on each column satisfying the prerequisities up to ten columns.
  • ALT+1 -ALT+0 - Repeat pressing the shortcut to toggle between ascending and descending sort order of the column.
    ALT+1: 1. sortable column, ALT+0: 10. sortable column.

You don't need to display the corresponding link buttons in order to select, delete, sort and edit a data row by keyboard shortcuts.

This default keyboard scheme can easily be changed by changing the default key values in the extender class.

To configure the keyboard scheme for a GridView, you can set the corresponding public key values XXXKey to a value of the Keys enumeration.

For paging, the extender works with both the numeric and the next/previous pager scheme. It works for all four existing GridView.PagerSettings.Mode values. If the grid doesn't support paging at all, nothing happens when pressing the keyboard shortcuts.

Background

The solution is based on an ASP.NET 2.0 AJAX Extender WebControl. This makes it easy to change the keyboard scheme on server-side and minimize work in the client-side JavaScript.

The extender control works by simulating the corresponding postback commands of the GridView's link buttons. So, the keyboard shortcuts are compatible to the default client/server based GridView navigating and editing scheme, and the ViewState of the GridView remains in sync.

The article will not cover the general steps in developing Extenders and Behaviour controls, but will describe the solution specific details. For a comprehensive documentation on how to develop an ASP.NET 2.0 AJAX Extensions Extender Control, read the documentation on AJAX.ASP.NET, and particularly the article about developing an Extender control.

In a few words, to develop an Extender control which extends the client behavior of an ASP.NET server control on the client side, you must create an ASP.NET 3.5 AJAX Extender Control for the server side, and a corresponding JavaScript class, derived from the "Sys.UI.Behavior" class, for client side handling. You need to register the JavaScript class in the server side Extender class so the script is emitted at runtime.

Server-side Code

In the following section, I will describe the important server side steps to build up the Extender control. In the following snippet, you see the definition of the Extender class on the server-side.

C#
[assembly: System.Web.UI.WebResource("AjaxSamples.GridViewKeyBoardPagerBehavior.js",
                                     "application/x-javascript")]

namespace AjaxSamples
{
    [TargetControlType(typeof(System.Web.UI.WebControls.GridView))]
    public class GridViewKeyBoardPagerExtender : System.Web.UI.ExtenderControl
    {
    ...

The extender only makes sense in conjunction with a GridView control. We can limit the use of the extender control to the target type GridView by adding the TargetControlType attribute to the class definition. A System.ArgumentNullException is thrown when the control, targeted by the TargetControlID property of the Extender control, is not of type GridView.

As stated before, the extender works by triggering the postbacks of the GridView. So, we need three properties for each action: the unique ID value of the GridView control on the page, the key value of the keyboard which is intended to fire the postback, and the command argument of the corresponding action on the GridView.

The following code snippet shows the server side properties for handling the delete event. All other events have analogous properties.

C#
private string _delCmdArgument = string.Empty;

[Browsable(false)]
protected string DeleteCmdArgument
{
    get { return _delCmdArgument; }
    set { _delCmdArgument = value; }
}

private Keys _delKey = Keys.Delete;

[Browsable(false)]
protected string DeleteKeyCode
{
    get { return Convert.ToInt32(_delKey).ToString(); }
}

[Browsable(true), DefaultValue(Keys.Delete)]
public Keys DeleteKey
{
    get { return _delKey; }
    set { _delKey = value; }
}

...

The property DeleteCmdArgument holds the value for the command argument of the corresponding GridView event. The actual value depends on the current state of the GridView, and is determined later.

The public property DeleteKey is used for the key value so we can configure it declaratively, e.g., in a User Control. For attaching this key value to a corresponding key value on the client side, we use a property which gives back the string representation of the ASCII key value of the selected public key enumeration value.

The key values are defined and enumeration found in the file KeyCodeEnums.cs.

We need the unique id of the GridView on the client side to fire a postback on it.

C#
[Browsable(false)]
protected string PostBackCtrlID
{
    get { return Grid.UniqueID; }
}

The GridView object the extender belongs to is determined by gaining a reference to the control with the ID value of the TargetControlID of the extender control. The extender property TargetControlID is the ID of that control the extender is associated with.

C#
private GridView _grid;
[Browsable(false)]
protected GridView Grid
{
    get
    {
        if (_grid == null)
        {
            _grid = Parent.FindControl(
                TargetControlID) as System.Web.UI.WebControls.GridView;
            if (_grid == null)
            {
                throw new NullReferenceException(string.Format(
                    "{0} is not of type GridView or the GridView is no initialized.",
                    TargetControlID));
            }
        }

        return _grid;
    }
}

In the OnPreRender override, we set up the command arguments for postback of the GridView. The current values of the command arguments depend on the current state of the GridView, e.g., the index of the selected page or row.

C#
protected override void OnPreRender(EventArgs e)
{
// Grid is in edit mode, so we set corresponding command arguments.
// cancelling or updating editing are the only allowed actions in this state.
// Grid is in edit mode so we set corresponding command values.
if (Grid.EditIndex > -1)
{
    _editCancelCmdArgument = "Cancel$" + Grid.EditIndex.ToString();
    _editUpdateCmdArgument = "Update$" + Grid.EditIndex.ToString();
}
else
{
    // Is a row selected
    if (Grid.SelectedIndex > -1)
    {
        // Deleting this row is possible
        _delCmdArgument = "Delete$" + Grid.SelectedIndex.ToString();
        // Editing// switch row to Editing mode is possible
        _editBeginCmdArgument = "Edit$" + Grid.SelectedIndex.ToString();

        // Selecting is possible if selecting is enabled
        if (Grid.SelectedIndex == 0)
        {
            _prevSelectCmdArgument = String.Empty;
        }
        else
        {
            _prevSelectCmdArgument = 
		"Select$" + Convert.ToString(Grid.SelectedIndex - 1);
        }

        if ((Grid.SelectedIndex == Grid.PageSize - 1) || (
            Grid.SelectedIndex == Grid.Rows.Count - 1))
        {
            _nextSelectCmdArgument = String.Empty;
        }
        else
        {
            _nextSelectCmdArgument = 
		"Select$" + Convert.ToString(Grid.SelectedIndex + 1);
        }
    }

    #region Paging
    // depending of the PagerSettings.Mode value the CommandArgument value of
    // the Pager LinkButtons differ.
    switch (Grid.PagerSettings.Mode)
    {
        case PagerButtons.Numeric:
        case PagerButtons.NumericFirstLast:
            #region Prev/Next Paging Arguments

            // calculate Next/Previous Pager Arguments
            // there is no previous page, the grid shows the first page
            if (Grid.PageIndex == 0)
            {
                _prevPageCmdArgument = string.Empty;
            }
            else
            {
                _prevPageCmdArgument = "Page$" + Convert.ToString(Grid.PageIndex);
            }

            // there is no next page. The grid shows the last page
            if (Grid.PageIndex + 2 > Grid.PageCount)
            {
                _nextPageCmdArgument = string.Empty;
            }
            else
            {
                _nextPageCmdArgument = "Page$" + Convert.ToString(Grid.PageIndex + 2);
            }
            #endregion

            break;
        case PagerButtons.NextPrevious:
        case PagerButtons.NextPreviousFirstLast:
            #region Prev/Next Paging
            // there is no previous page, the grid shows the first page
            if (Grid.PageIndex == 0)            {
                _prevPageCmdArgument = String.Empty;
            }
            else
            {
                _prevPageCmdArgument = "Page$Prev";
            }

            // there is no next page. The grid shows the last page
            if (Grid.PageIndex == Grid.PageCount - 1)
            {
                _nextPageCmdArgument = String.Empty;
            }
            else
            {
                _nextPageCmdArgument = "Page$Next";
            }
            #endregion
            break;
    }

    #region First/Last Paging Settings

    if (Grid.PageIndex == 0)
    {
        _firstPageCmdArgument = String.Empty;
    }
    else
    {
        _firstPageCmdArgument = "Page$First";
    }

    if (Grid.PageIndex == Grid.PageCount - 1)
    {
        _lastPageCmdArgument = String.Empty;
    }
    else
    {
        _lastPageCmdArgument = "Page$Last";
    }

    #endregion
    #endregion    
    
    #region Sorting

    if (_allowSorting
     && Grid.AllowSorting
     && (Grid.Columns != null)
     && (Grid.Columns.Count > 1))
    {
     int sortColumnCount = 0;
     SortColumns = new List<string>();

     for (int i = 0; i < Grid.Columns.Count; i++)
     {
      BoundField field = Grid.Columns[i] as BoundField;

      if (field == null)
      {
       continue;
      }
      if ((String.IsNullOrEmpty(field.SortExpression)) || 
		(String.IsNullOrEmpty(field.DataField)))
      {
       continue;
      }

      if (sortColumnCount == 9)
      {
       {
        SortColumns.Add(field.DataField);
        break;
       }
      }
      else
      {
       SortColumns.Add(field.DataField);
       sortColumnCount++;
      }
     }
     JavaScriptSerializer serializer = new JavaScriptSerializer();
     SortColumnNames = serializer.Serialize(SortColumns);
    }

    #endregion
   }
}

base.OnPreRender(e);
}

During postback, for security purposes, the ASP.NET runtime checks whether the control ID and the command argument is a valid combination that is allowed to fire postback events. For that, we need to register the GridView's control ID and the command arguments the GridView could post back. This is achieved by calls to the method RegisterForEventValidation of the ClientScriptManager class. This step must be done in the override of the Render method of our extender control. After registering the combination of the ID and the command arguments, the extender is allowed to post back these commands of the GridView.

C#
protected override void Render(HtmlTextWriter writer)
{
    ClientScriptManager csm = Page.ClientScript;

    for (int i = 0; i < Grid.PageSize; i++)
    {
        csm.RegisterForEventValidation(Grid.UniqueID, "Select$" + i.ToString());
        csm.RegisterForEventValidation(Grid.UniqueID, "Edit$" + i.ToString());
        csm.RegisterForEventValidation(Grid.UniqueID, "Cancel$" + i.ToString());
        csm.RegisterForEventValidation(Grid.UniqueID, "Delete$" + i.ToString());
        ...

To bridge the server side part of the extender with the client side part, you have to override the method GetScriptDescriptors of the extender class. There you map the server properties with properties on the client side Behavior class.

C#
protected override IEnumerable<ScriptDescriptor> GetScriptDescriptors(
                   Control targetControl)
{
    ScriptBehaviorDescriptor descriptor = new ScriptBehaviorDescriptor(
        "AjaxSamples.GridViewKeyBoardPagerBehavior", targetControl.ClientID);
    descriptor.AddProperty("_firstCmdArgument", this.FirstPageCmdArgument);
    descriptor.AddProperty("_lastCmdArgument", this.LastPageCmdArgument);
    ...
    return new ScriptDescriptor[] { descriptor };
}

In the method GetScriptReferences override of the extender class, we register the client side script of the extender.

C#
protected override IEnumerable<ScriptReference> GetScriptReferences()
{
    ScriptReference reference = new ScriptReference();
    reference.Name = "AjaxSamples.GridViewKeyBoardPagerBehavior.js";
    reference.Assembly = "AjaxSamples";

    return new ScriptReference[] { reference };
}

Now, we have finished the server side part of the extender control. The following part describes the important details on the client-side.

Client-side Code

As the first important step, we register a KeyDown event handler in the initialization method of the behavior class.

JavaScript
AjaxSamples.GridViewKeyBoardPagerBehavior.prototype = {
initialize : function() {
        AjaxSamples.GridViewKeyBoardPagerBehavior.callBaseMethod(this, 'initialize');
        // we catch the keydown event at the document level of the HTML DOM tree
    $addHandler(document, 'keydown', Function.createDelegate(this, this._onKeyDown));
},

dispose : function() {
        AjaxSamples.GridViewKeyBoardPagerBehavior.callBaseMethod(this, 'dispose');
        
        $clearHandlers(document);
},

Another thing we do here is deactivate any keyboard shortcuts of the browser that conflict with one of our keyboard shortcuts. In this case, we deactivate the "Add to Favorites" (ALT+D) of the Firefox browser that conflicts with our Save Changes shortcut when editing a row in the GridView. We have to dispose the event handler to avoid memory leaks. We use the dispose function for that.

The next interesting thing is the handler itself.

JavaScript
 // Event Handler that catches the keyboard event
_onKeyDown : function(keyEvent) {

    var cmdArgument = "";

    if (keyEvent.altKey == true) {
        // user pressed a number (ASCII code 48-57) - Sorting
        if ((keyEvent.keyCode >= 48) || (keyEvent.keyCode <= 57)) { 
            var index = (keyEvent.keyCode == 48) ? 9 : keyEvent.keyCode - 49; 
            if ((index < this.get_sortColumns().length) && (index >= 0)) { 
                cmdArgument = "Sort$"; + this.get_sortColumns()[index]; 
            } 
        } 
        if (keyEvent.keyCode == this._firstKeyCode) {
            cmdArgument = this._firstCmdArgument;
        }

        if (keyEvent.keyCode == this._lastKeyCode) {
            cmdArgument  = this._lastCmdArgument;
        }
        ...
        if (keyEvent.keyCode == this._editUpdateKeyCode) {
            window.onkeypress = function(keyEvent){return false;}
            if (this._editUpdateCmdArgument != "") {
                feedBack = confirm("Save Changes?");
                if (feedBack) {
                    cmdArgument = this._editUpdateCmdArgument;
                }
                else {
                    cmdArgument = this._cancelUpdateCmdArgument;
                }
            }
        }
        if (cmdArgument != "") {
            if (cmdArgument != "") {
                keyEvent.stopPropagation();
                keyEvent.preventDefault();
            __doPostBack(this._postBackCtrlID, cmdArgument);
            return;
        }
    }
    ...

In the handler, you can edit the code to adapt the behavior of the extender when pressing a key (combination) to your needs.

In the KeyDown handler on the client side, we check if the pressed key (combination) is one of the keyboard shortcuts for the GridView actions we defined on the server side. If this is the case, we call the __doPostBack function with the ID of the GridView and the command argument which fires the corresponding command on the server side. The __doPostBack function is emitted by the ASP.NET framework to the HTML markup of the page. The purpose is for posting back (submitting the HTML form) as a response to a user action or another client side event which calls for server side handling.

Using the Code

To use the extender in your project, unzip the code and add it to your solution.

Now, you are ready to use the extender in your existing or upcoming projects. You can add it in the ASP.NET Toolbox if you use it regularly.

The following ASP.NET markup shows how the GridView and the extender control coexist. One important property is the TargetControlID. This is the ID of the control, in our case the GridView control, the extender control adds client behavior to. TargetControlID is a property of the System.Web.UI.Extender class.

ASP.NET
<%@ Register Assembly="AjaxSamples" Namespace="AjaxSamples" TagPrefix="cc1" %>
...
<asp:ScriptManager runat="server" ID="ScripManager1">
</asp:ScriptManager>
<asp:UpdatePanel runat="server" ID="UpdatePanel1" ChildrenAsTriggers="true"
    UpdateMode="Conditional">
    <ContentTemplate>
        <div>
        <asp:AccessDataSource ID="Northwind" runat="server" 
            DataFile="~/App_Data/Nwind.mdb"
            SelectCommand="SELECT [CustomerID], [CompanyName], [ContactName],
            [Address], [City], [Country] FROM [Customers]">
        </asp:AccessDataSource>
        <asp:GridView ID="GridView1"
             runat="server" 
             DataSourceID="Northwind" 
             DataKeyNames="CustomerID" 
             AllowPaging="True"
             AllowSorting="True"
             AutoGenerateColumns="False" >
             <Columns>
               <asp:BoundField DataField="CustomerID" 
		HeaderText="Kunden ID" SortExpression="CustomerId" />
               <asp:BoundField DataField="CompanyName" 
		HeaderText="Unternehmen" SortExpression="CompanyName" />
               <asp:BoundField DataField="ContactTitle" 
		HeaderText="Titel" SortExpression="ContactTitle" />
               <asp:BoundField DataField="ContactName" 
		HeaderText="Ansprechpartner" SortExpression="ContactName" />
               <asp:BoundField DataField="Address" 
		HeaderText="Straße" SortExpression="Address" />
               <asp:BoundField DataField="PostalCode" 
		HeaderText="PLZ" SortExpression="PostalCode" />
               <asp:BoundField DataField="City" HeaderText="Ort" SortExpression="City" />
               <asp:BoundField DataField="Country" 
		HeaderText="Land" SortExpression="Country" />
               <asp:BoundField DataField="Region" 
		HeaderText="Region" SortExpression="Region" />
               <asp:BoundField DataField="Phone" 
		HeaderText="Tel." SortExpression="Phone" />
               <asp:BoundField DataField="Fax" HeaderText="Fax" SortExpression="Fax" />
             </Columns>
        </asp:GridView>
        <cc1:GridViewKeyBoardPagerExtender NextKey="Up" 
		PreviousKey="Down" ID="GridViewKeyBoardPagerExtender1"
				runat="server" TargetControlID="GridView1" />
        </div>
    </ContentTemplate>
</asp:UpdatePanel>

History

  • 3 July 2008, Version 1.0.
  • 20 July 2010, Version 2.0
    • Use of ALT key instead of CTRL
    • Add support for sorting

License

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


Written By
Software Developer Dipl.-Ing. Rolf Cerff Software Development and IT-
Germany Germany
I'am a freelance software developer, located at Freiburg i. Br., Germany. My main domain is designing and developing Web applications based on the ASP.NET technology. Further main interests are. Sustainable and agile software architectures, Domain Driven Design (DDD). I'am a certified SCRUM Master.

Comments and Discussions

 
GeneralMy vote of 5 Pin
eyedia22-Jul-11 4:21
eyedia22-Jul-11 4:21 

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

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