[原创]ExtAspNet秘密花园(十八) — 表格之事件处理
事件通常是由用户触发的,比如按钮的点击事件、下拉列表的选择项改变事件。不过有些事件并非用户触发的,而是在程序执行的某个特定阶段触发的,比如将要介绍的表格的预绑定事件、行预绑定事件以及行绑定事件,本章将会详细描述这些和表格相关的事件。
有哪些事件参数类型
每个事件处理函数都会接受一个事件参数,默认的是EventArgs,不过Grid为大部分事件自定义了事件参数类型,先来看下源代码中的定义:
用户触发的事件参数类型:
- GridCommandEventArgs:表格行命令事件参数,对应RowCommand事件。
- RowIndex:行索引
- ColumnIndex:列索引
- CommandName:命令名称
- CommandArgument:命令参数
- GridPageEventArgs:表格分页事件参数,对应PageIndexChange事件。
- NewPageIndex:新页面的索引
- GridSortEventArgs:表格排序事件参数,对应Sort事件。
- SortField:排序字段
- SortDirection:排序方向
- ColumnIndex:列索引
- GridRowClickEventArgs:表格行点击事件参数,对应RowClick和RowDoubleClick两个事件。
- RowIndex:行索引
非用户触发的事件参数类型:
- GridPreRowEventArgs:表格行预绑定事件参数,对应PreRowDataBound事件。
- GridRowEventArgs:表格行绑定事件参数,对应RowDataBound事件。
- 还有一个PreDataBound事件,使用的是默认EventArgs事件参数。
分页和排序事件
这两个事件在前面章节已经提到过,就不再赘述。
行单击和行双击事件
这个两个事件也相对简单,其事件参数只有一个属性RowIndex,表示当前点击的是哪一行。
这个事件在实际项目中也经常用到,特别是主从表联动时,单击主表的某一行后更新从表的数据,下面通过一个例子来说明。
首先来看下示例的最终显示效果:
用户单击左侧的某一个班级时,通过更新右侧的班级信息和班级的学生列表。
目前我们只关心左侧主表的实现代码(完整的示例请查看官方示例),先看下ASPX标签结构:
1: <ext:Grid ID="Grid2" ShowBorder="true" ShowHeader="true" Title="表格(班级)" runat="server"
2: DataKeyNames="Id,Name" EnableMultiSelect="false" EnableRowClick="true" OnRowClick="Grid2_RowClick">
3: <Columns>
4: <ext:TemplateField Width="60px">
5: <ItemTemplate>
6: <asp:Label ID="Label2" runat="server" Text='<%# Container.DataItemIndex + 1 %>'></asp:Label>
7: </ItemTemplate>
8: </ext:TemplateField>
9: <ext:BoundField ExpandUnusedSpace="true" DataField="Name" DataFormatString="{0}"
10: HeaderText="姓名" />
11: </Columns>
12: </ext:Grid>
这里有如下几个关键点:
- 设置表格的EnableMultiSelect属性为false,让表格只能单选;
- 设置表格的EnableRowClick属性为true;
- 设置行点击事件处理函数OnRowClick。
再来看下后台的行点击处理代码:
1: protected void Grid2_RowClick(object sender, ExtAspNet.GridRowClickEventArgs e)
2: {
3: BindGrid1();
4: }
5:
6: private void BindGrid1()
7: {
8: if (Grid2.SelectedRowIndex < 0)
9: {
10: return;
11: }
12:
13: int classId = Convert.ToInt32(Grid2.DataKeys[Grid2.SelectedRowIndex][0]);
14:
15: // 根据classId查询数据库,绑定从表和更新班级描述...
16: }
注意,如何启用行双击事件一定要启用EnableRowDoubleClick属性。
行内命令事件
能够触发行内命令事件的列只有CheckBoxField和LinkButtonField两种类型,由于CheckBoxField列会有专门的篇幅讲解,这里只关心LinkButtonField。
由于LinkButtonField不是一个单独的控件,而是包含在表格控件中,所以事件的定义必须在表格上,那么LinkButtonField就需要一个标示来表明当前触发的是表格中的哪个LinkButtonField,这个标示其实就是CommandName和CommandArgument。
下面通过一个例子来说明如何使用LinkButtonField,先看下ASPX标签结构:
1: <ext:Grid ID="Grid1" Title="表格" PageSize="3" ShowBorder="true" ShowHeader="true"
2: AutoHeight="true" runat="server" EnableCheckBoxSelect="True" DataKeyNames="Id,Name"
3: Width="800px" OnRowCommand="Grid1_RowCommand" EnableRowNumber="True">
4: <Columns>
5: <ext:LinkButtonField HeaderText=" " Width="60px" CommandName="Action1" Text="按钮 1" />
6: <ext:LinkButtonField HeaderText=" " Width="60px" ConfirmText="你确定要这么做么?" ConfirmTarget="Top"
7: CommandName="Action2" Text="按钮 2" />
8: </Columns>
9: </ext:Grid>
来看下后台代码:
1: protected void Grid1_RowCommand(object sender, ExtAspNet.GridCommandEventArgs e)
2: {
3: if (e.CommandName == "Action1" || e.CommandName == "Action2")
4: {
5: object[] keys = Grid1.DataKeys[e.RowIndex];
6: labResult.Text = String.Format("你点击了第 {0} 行,第 {1} 列,行命令是 {2}", e.RowIndex + 1, e.ColumnIndex + 1, e.CommandName) +
7: "<br />" +
8: String.Format("当前行数据 - 编号:{0},姓名:{1}", keys[0], keys[1]);
9: }
10: }
来看下用户点击第二个“按钮 1”的界面效果:
这里还有一个小技巧,通过设置ConfirmText、ConfirmTarget、ConfirmIcon、ConfirmTitle来定义回发之前的确认对话框,显示效果如图:
预绑定事件
预绑定事件一般用来修改列的属性,这样每次调用DataBind函数进行数据绑定时都会触发预绑定事件。
在《AppBox - 基于ExtAspNet的企业通用管理框架》中,此事件用来检查相应的权限并设置表格中列的属性,代码片段如下:
1: protected void Grid1_PreDataBound(object sender, EventArgs e)
2: {
3: // 数据绑定之前,进行权限检查
4: if (!CheckPowerEdit())
5: {
6: ExtAspNet.WindowField btn = Grid1.FindColumn(columnId) as ExtAspNet.WindowField;
7: btn.Enabled = false;
8: btn.ToolTip = CHECK_POWER_FAIL_ACTION_MESSAGE;
9: }
10: }
这里首先判断是否具有编辑权限,如果没有编辑权限,就设置弹出窗体列的Enabled属性为false,显示效果如下:
行预绑定事件
行预绑定事件是在每一行进行数据绑定之前触发的事件,一般用于根据原始数据修改列的属性(比如启用禁用)。
要想熟练地使用行预绑定事件,首先要能理解如下几个关键点:
- 行预绑定事件发生在每一行进行数据绑定之前;
- 列属性是全局的,这就意味着在一次行预绑定事件中对列属性的修改会影响以后的每一次的行绑定;
- 列属性不能在客户端和服务器之间持久化,所以每次对表格进行数据绑定时都要重复对列属性的修改。
先来看一个使用行预绑定事件的示例,示例的显示界面如下:
在行预绑定事件中做了如下两件事情:
- 修改“所学专业”列的DataTextFormatString,在专业后面增加年份;
- 根据一定的规则,启用/禁用“是否在校”列、按钮列以及删除按钮列。
首先来看下ASPX标签结构定义:
1: <ext:Grid ID="Grid1" Title="表格" PageSize="3" ShowBorder="true" ShowHeader="true"
2: Width="800px" AutoHeight="true" OnPreRowDataBound="Grid1_PreRowDataBound" runat="server"
3: EnableCheckBoxSelect="True" DataKeyNames="Id,Name" OnRowCommand="Grid1_RowCommand"
4: EnableRowNumber="True">
5: <Columns>
6: <ext:BoundField Width="100px" DataField="Name" DataFormatString="{0}" HeaderText="姓名" />
7: <ext:TemplateField Width="60px" HeaderText="性别">
8: <ItemTemplate>
9: <asp:Label ID="Label2" runat="server" Text='<%# GetGender(Eval("Gender")) %>'></asp:Label>
10: </ItemTemplate>
11: </ext:TemplateField>
12: <ext:BoundField Width="60px" DataField="EntranceYear" HeaderText="入学年份" />
13: <ext:CheckBoxField ColumnID="cbxAtSchool" TextAlign="Center" Width="60px" RenderAsStaticField="false" DataField="AtSchool"
14: HeaderText="是否在校" />
15: <ext:HyperLinkField HeaderText="所学专业" DataToolTipField="Major" DataTextField="Major"
16: DataTextFormatString="{0}" DataNavigateUrlFields="Major" DataNavigateUrlFormatString="http://gsa.ustc.edu.cn/search?q={0}"
17: DataNavigateUrlFieldsEncode="true" Target="_blank" ExpandUnusedSpace="True" />
18: <ext:ImageField Width="60px" DataImageUrlField="Group" DataImageUrlFormatString="~/images/16/{0}.png"
19: HeaderText="分组"></ext:ImageField>
20: <ext:LinkButtonField TextAlign="Center" ConfirmText="你确定要这么做么?" ConfirmTarget="Top"
21: ColumnID="lbfAction1" Width="50px" CommandName="Action1" Text="按钮" />
22: <ext:LinkButtonField TextAlign="Center" ConfirmText="你确定要这么做么?" Icon="Delete" ConfirmTarget="Top"
23: ColumnID="lbfAction2" HeaderText=" " Width="50px" CommandName="Action2" />
24: </Columns>
25: </ext:Grid>
再来看下PreRowDataBound事件处理函数:
1: protected void Grid1_PreRowDataBound(object sender, ExtAspNet.GridPreRowEventArgs e)
2: {
3: LinkButtonField lbfAction1 = Grid1.FindColumn("lbfAction1") as LinkButtonField;
4: LinkButtonField lbfAction2 = Grid1.FindColumn("lbfAction2") as LinkButtonField;
5: CheckBoxField cbxAtSchool = Grid1.FindColumn("cbxAtSchool") as CheckBoxField;
6:
7: if (e.RowIndex < 5)
8: {
9: cbxAtSchool.Enabled = true;
10: lbfAction1.Enabled = true;
11: lbfAction2.Enabled = true;
12: }
13: else
14: {
15: cbxAtSchool.Enabled = false;
16: lbfAction1.Enabled = false;
17: lbfAction2.Enabled = false;
18: }
19:
20: // 如果绑定到 DataTable,那么这里的 DataItem 就是 DataRowView
21: HyperLinkField linkField = Grid1.Columns[4] as HyperLinkField;
22: DataRowView row = e.DataItem as DataRowView;
23: if (row != null)
24: {
25: linkField.DataTextFormatString = "{0} (" + row["EntranceYear"].ToString() + ")";
26: }
27: }
由于当前正处于数据绑定的前夕,所以可以通过e.DataItem获得当前将要进行绑定的原始数据:
- 如果是将表格绑定到DataTable、DataView、DataSet,则这里的DataItem就是DataRowItem;
- 如果是将表格绑定到IEnumerable的数据,则这里的DataItem就是具体的类类型了。
注意:由于GridRow也有DataItem属性,很多人就误认为任何时候都可以使用此属性了,这是错误的。
从本质上讲,DataItem是临时变量,只有在本次HTTP请求中,并且在数据绑定后才存在,其他情况下都为null。
甚至在PreRowDataBound事件内部,行实例的DataItem也是null,可以在上面函数中增加如下代码:
1: GridRow rowInstance = Grid1.Rows[e.RowIndex];
2: if (rowInstance.DataItem == null)
3: {
4: // 会走到这里...
5: }
行绑定事件
行绑定事件是在行的数据绑定结束后触发的,此时可以修改行的渲染结果(也就是行单元格渲染后的HTML代码),下面通过一个示例说明:
可以特别比较本例和上例的区别,本例中在RowDataBound事件中修改渲染后的“所学专业”列,直接看后台代码:
1: protected void Grid1_RowDataBound(object sender, ExtAspNet.GridRowEventArgs e)
2: {
3: // e.DataItem -> System.Data.DataRowView 或者自定义类
4: // e.RowIndex -> 当前行序号(从 0 开始)
5: // e.Values -> 当前行每一列渲染后的 HTML 片段
6:
7: DataRowView row = e.DataItem as DataRowView;
8: if (row != null)
9: {
10: e.Values[4] = String.Format("{0} ({1})", e.Values[4], row["EntranceYear"]);
11: }
12: }
想知道Values到底是什么值,调试一下就能看到:
可以看出,Values属性保存的是渲染后的HTML片段,并且Values属性在HTTP请求之间是持久化的,这也是为什么Ajax回发过程中不需要重新绑定表格的原因。
注意:虽然Values属性是持久化的,但是不可以作为原始数据使用,因为Values的本质是渲染后的HTML片段。推荐的方式是定义DataKeyNames属性,并使用持久化属性DataKeys。
小结
本章我们不仅学习了用户触发的事件类型,包括分页排序事件、行单击双击事件以及行命令事件;同时还学习了非用户触发的事件类型,包括预绑定事件、行预绑定事件以及行绑定事件,这三个事件能够用来单独控制每一行中每个单元格的显示内容,同样非常重要。
下一篇文章我们会介绍CheckBoxField,前面已经提到CheckBoxField同样能够触发行命令事件,这是否意味着可以直接编辑CheckBoxField呢?下一篇文章会详细讲解。