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






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

引言
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 启用了分页,则您可以使用快捷键导航到下一页、上一页、第一页和最后一页。即使第一页和最后一页的链接按钮当前不可见,您也可以通过键盘导航到第一页和最后一页。 |
|
选择 | 如果启用了选择并且预选了一行,则您可以使用快捷键导航到下一行和上一行。 |
|
未使用。 | 如果启用了编辑并且预选了一行,则您可以使用所有编辑功能的快捷键。 |
|
删除 | 如果启用了删除并且预选了一行,则您可以使用快捷键删除该行。 |
|
Sort | GridView 键盘扩展器现在支持列排序。要激活 GridView 上的排序,您需要执行以下操作
|
|
您无需显示相应的链接按钮即可通过键盘快捷键选择、删除、排序和编辑数据行。
通过更改扩展器类中的默认键值,可以轻松更改此默认键盘方案。
要配置 GridView
的键盘方案,您可以将相应的公共键值 XXXKey
设置为 Keys
枚举的值。
对于分页,扩展器适用于数字和下一页/上一页分页器方案。它适用于所有四种现有 GridView.PagerSettings.Mode
值。如果网格根本不支持分页,则在按下键盘快捷键时不会发生任何事情。
背景
该解决方案基于 ASP.NET 2.0 AJAX 扩展器 WebControl。这使得在服务器端更改键盘方案并最大限度地减少客户端 JavaScript 中的工作变得容易。
扩展器控件通过模拟 GridView
链接按钮的相应回发命令来工作。因此,键盘快捷键与默认的基于客户端/服务器的 GridView
导航和编辑方案兼容,并且 GridView
的 ViewState
保持同步。
本文将不涉及开发扩展器和行为控件的一般步骤,但将描述解决方案的具体细节。有关如何开发 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
控件,扩展器控件向其添加客户端行为。TargetControlID
是 System.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 键
- 添加对排序的支持