65.9K
CodeProject 正在变化。 阅读更多。
Home

在带有键盘快捷键的 ASP.NET GridView 控件中进行分页、选择、删除、编辑和排序

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (29投票s)

2008年7月4日

CPOL

8分钟阅读

viewsIcon

161858

downloadIcon

6358

一个 ASP.NET 2.0 AJAX 扩展器,用于增强 ASP.NET GridView 以通过键盘快捷键进行分页、选择、删除、编辑和排序行。

GridViewKeyboardExtender_src

引言

ASP.NET 2.0 框架附带了 GridView,这是一个功能丰富的 ASP.NET 服务器控件,用于在网页上显示、分页和编辑数据。通过声明性地向 GridView 的标记添加属性,可以实现 GridView 的分页、选择、排序和编辑。这会向 GridView 的数据行添加链接按钮。您必须单击其中一个链接按钮才能删除或编辑行。我缺少的一个功能是能够通过键盘快捷键使用这些功能。

为了实现这一点,我开发了一个 ASP.NET 2.0 AJAX 扩展器控件,用于在支持 ASP.NET 2.0 AJAX 的网页上与 ASP.NET 2.0 GridView 一起使用。

您可以使用扩展器执行的操作以及如何使用它

您可以在包含 ASP.NET 2.0 GridView 服务器控件的现有 ASP.NET 2.0 AJAX 扩展应用程序网页中使用此扩展器控件。

在当前版本中,扩展器控件必须与它所属的 GridView 在控件树中处于同一级别。

在当前版本中,扩展器提供以下导航和编辑功能,以及以下默认键盘快捷键方案

功能 功能描述 默认快捷键
分页 如果为 GridView 启用了分页,则您可以使用快捷键导航到下一页、上一页、第一页和最后一页。即使第一页和最后一页的链接按钮当前不可见,您也可以通过键盘导航到第一页和最后一页。
  • 下一页:向右箭头键
  • 上一页:向左箭头键
  • 第一页:ALT + Pos1
  • 最后一页:ALT + End
选择 如果启用了选择并且预选了一行,则您可以使用快捷键导航到下一行和上一行。
  • 选择下一行:向下箭头键
  • 选择上一行:向上箭头键
未使用。 如果启用了编辑并且预选了一行,则您可以使用所有编辑功能的快捷键。
  • 将所选行切换到编辑模式:ALT + m 键
  • 取消编辑:ALT + q 键
  • 更新更改:ALT + u 键
删除 如果启用了删除并且预选了一行,则您可以使用快捷键删除该行。
  • 删除行:Del 键
Sort GridView 键盘扩展器现在支持列排序。要激活 GridView 上的排序,您需要执行以下操作
  • GridView 的属性 AutoGenerateColumns='False' 设置为 False。
  • 手动向 GridView Columns 集合添加列。这些列必须是 BoundField 类型或派生列类型。
  • GridView 的属性 AllowSorting='true' 设置为 True。
  • Extender 的属性 AllowSorting='true' 设置为 True(默认)。
  • GridView 列必须具有 SortExpression 属性的值。
扩展器最多支持十列满足前提条件的列的排序。
  • ALT+1 -ALT+0 - 重复按快捷键可在列的升序和降序排序顺序之间切换。
    ALT+1:第 1 个可排序列,ALT+0:第 10 个可排序列。

您无需显示相应的链接按钮即可通过键盘快捷键选择、删除、排序和编辑数据行。

通过更改扩展器类中的默认键值,可以轻松更改此默认键盘方案。

要配置 GridView 的键盘方案,您可以将相应的公共键值 XXXKey 设置为 Keys 枚举的值。

对于分页,扩展器适用于数字和下一页/上一页分页器方案。它适用于所有四种现有 GridView.PagerSettings.Mode 值。如果网格根本不支持分页,则在按下键盘快捷键时不会发生任何事情。

背景

该解决方案基于 ASP.NET 2.0 AJAX 扩展器 WebControl。这使得在服务器端更改键盘方案并最大限度地减少客户端 JavaScript 中的工作变得容易。

扩展器控件通过模拟 GridView 链接按钮的相应回发命令来工作。因此,键盘快捷键与默认的基于客户端/服务器的 GridView 导航和编辑方案兼容,并且 GridViewViewState 保持同步。

本文将不涉及开发扩展器和行为控件的一般步骤,但将描述解决方案的具体细节。有关如何开发 ASP.NET 2.0 AJAX 扩展器控件的全面文档,请阅读 AJAX.ASP.NET 上的文档,特别是关于开发扩展器控件的文章。

简而言之,要开发一个扩展 ASP.NET 服务器控件客户端行为的扩展器控件,您必须为服务器端创建一个 ASP.NET 3.5 AJAX 扩展器控件,并为客户端处理创建一个派生自“Sys.UI.Behavior”类的相应 JavaScript 类。您需要将 JavaScript 类注册到服务器端扩展器类中,以便在运行时发出脚本。

服务器端代码

在以下部分中,我将描述构建 Extender 控件的重要服务器端步骤。在以下代码片段中,您将看到服务器端 Extender 类的定义。

[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
    {
    ...

扩展器只有与 GridView 控件结合使用才有意义。我们可以通过向类定义添加 TargetControlType 属性,将扩展器控件的使用限制为目标类型 GridView。当扩展器控件的 TargetControlID 属性所针对的控件不是 GridView 类型时,将抛出 System.ArgumentNullException

如前所述,扩展器通过触发 GridView 的回发来工作。因此,每个动作都需要三个属性:页面上 GridView 控件的唯一 ID 值、旨在触发回发的键盘键值,以及 GridView 上相应动作的命令参数。

以下代码片段显示了用于处理删除事件的服务器端属性。所有其他事件都具有类似的属性。

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; }
}

...

属性 DeleteCmdArgument 保存相应 GridView 事件的命令参数值。实际值取决于 GridView 的当前状态,并在稍后确定。

public 属性 DeleteKey 用于键值,因此我们可以声明性地配置它,例如在用户控件中。为了将此键值附加到客户端上的相应键值,我们使用一个属性,该属性返回所选公共键枚举值的 ASCII 键值的 string 表示形式。

键值在文件 KeyCodeEnums.cs 中定义和枚举。

我们需要客户端上 GridView 的唯一 ID 以在其上触发回发。

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

扩展器所属的 GridView 对象是通过获取对具有扩展器控件的 TargetControlID 的 ID 值的控件的引用来确定的。扩展器属性 TargetControlID 是扩展器关联的控件的 ID。

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;
    }
}

OnPreRender 覆盖中,我们设置 GridView 回发的命令参数。命令参数的当前值取决于 GridView 的当前状态,例如,所选页面或行的索引。

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);
}

在回发期间,出于安全目的,ASP.NET 运行时会检查控件 ID 和命令参数是否是允许触发回发事件的有效组合。为此,我们需要注册 GridView 的控件 ID 和 GridView 可以回发的命令参数。这是通过调用 ClientScriptManager 类的 RegisterForEventValidation 方法来实现的。此步骤必须在我们扩展器控件的 Render 方法的覆盖中完成。注册 ID 和命令参数的组合后,扩展器就可以回发这些 GridView 的命令了。

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());
        ...

为了连接扩展器的服务器端部分和客户端部分,您必须重写扩展器类的 GetScriptDescriptors 方法。在那里,您将服务器属性与客户端 Behavior 类上的属性进行映射。

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 };
}

在扩展器类的 GetScriptReferences 方法覆盖中,我们注册扩展器的客户端脚本。

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

    return new ScriptReference[] { reference };
}

现在,我们已经完成了扩展器控件的服务器端部分。以下部分描述了客户端的重要细节。

客户端代码

作为第一个重要步骤,我们在行为类的初始化方法中注册一个 KeyDown 事件处理程序。

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);
},

我们在这里做的另一件事是禁用任何与我们的键盘快捷键冲突的浏览器键盘快捷键。在这种情况下,我们禁用了 Firefox 浏览器的“添加到收藏夹”(ALT+D),它与我们在 GridView 中编辑行时的保存更改快捷键冲突。我们必须释放事件处理程序以避免内存泄漏。我们为此使用了 dispose 函数。

下一个有趣的事情是处理程序本身。

 // 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;
        }
    }
    ...

在处理程序中,您可以编辑代码以根据您的需要调整扩展器在按下键(组合)时的行为。

在客户端的 KeyDown 处理程序中,我们检查按下的键(组合)是否是我们服务器端定义的 GridView 动作的键盘快捷键之一。如果是这种情况,我们调用 __doPostBack 函数,其中包含 GridView 的 ID 和在服务器端触发相应命令的命令参数。__doPostBack 函数由 ASP.NET 框架发出到页面的 HTML 标记中。其目的是为了响应用户操作或其他需要服务器端处理的客户端事件而回发(提交 HTML 表单)。

Using the Code

要在您的项目中使用扩展器,请解压缩代码并将其添加到您的解决方案中。

现在,您就可以在现有或即将到来的项目中使用扩展器了。如果您经常使用它,可以将其添加到 ASP.NET 工具箱中。

以下 ASP.NET 标记显示了 GridView 和扩展器控件如何共存。一个重要的属性是 TargetControlID。这是控件的 ID,在我们的示例中是 GridView 控件,扩展器控件向其添加客户端行为。TargetControlIDSystem.Web.UI.Extender 类的一个属性。

<%@ 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>

历史

  • 2008 年 7 月 3 日,版本 1.0。
  • 2010 年 7 月 20 日,版本 2.0
    • 使用 ALT 键而不是 CTRL 键
    • 添加对排序的支持
© . All rights reserved.