不用sqldatasource时gridview的排序
GridView 是 ASP.NET 2.0 中提供的原来 DataGrid 的后继控件。提供了比 DataGrid 更为强大的功能。
GridView 现在有两种数据绑定模型。一种是向后(backward)兼容 1.x 的 DataSource 语法。一种是新的 DataSourceID 语法。
前者通过在代码中直接将数据源赋值给 DataSource 属性,并调用 DataBind 方法实现数据的绑定。
后者可以在“Design”视图中,放置 DataSource 控件(SqlDataSource, AccessDataSource, ObjectDataSource, XmlDataSource等),然后将 DataSource 控件的 ID 在“Properties”面板中设置给 GridView 控件的 DataSourceID 属性。
关于第二种绑定语法,QuickStart 中的示例挺多。演示了自动的分页、排序、(通过脚本回调式的)无刷新分页、排序等等。应该很全面了。
我尝试用一个 GridView 控件列出 ASP.NET 2.0 成员资格(Membership)中的所有角色。简单的使用 System.Web.Security.Roles.GetAllRoles() 方法即可得到全部的角色列表。
Roles.GetAllRoles 方法返回的是一个 string 数组,直接按照第一种绑定语法,写上两行代码:this.GridView1.DataSource = Roles.GetAllRoles(); this.GridView1.DataBind(); 效果便出来了。
只有一列,列头是一个“Item”字样,很不好,我需要自定义的列头(比如我想写一个“Role”的列头会更好一些)。这个“需求”可以简单的用自定义 GridView 的 Columns 来实现:我添加了一个 TemplateColumn,指定列头为“Role”,模版中写的代码是 <%# Container.DataItem %>还是用 1.x 中的绑定语法。(其实 2.0 中新推出的绑定语法是 Eval 和 Bind 语法,不过我这个地方没办法使用。不信?你试试看,呵呵)另外,要顺便关闭 AutoGenerateColumns 属性。
OK,截至目前,上面的需求都顺利完成了,下面我尝试给它加入排序功能。
对于 GridView 的 Column 来说,如果其 SortExpression 没有设置,呈现后的列头不会出现排序用的 LinkButton。于是我就为刚才添加的那个 TemplateColumn 设置了 SortExpression = "role"。现在再到浏览器里去看,LinkButton 就出来了。
点一下试一试?那个再熟悉不过的错误页面出来了,“The GridView 'GridView1' fired event Sorting which wasn't handled.”查了查文档,原来,GridView 内置了对于第二种绑定模型的自动分页、排序功能。对于通过 DataSource 的第一种绑定模型,GridView 只提供 UI,不提供实现。看来还需要我们手工去实现。
文档还有刚才那个错误消息都告诉我们,实现排序是通过 Sorting 事件。这个事件处理程序,我是这么写的:
protected void GridView1_Sorting(object sender, GridViewSortEventArgs e) { if (e.SortExpression == "role") { string[] roles = this.GridView1.DataSource as string[]; if (roles == null) { roles = Roles.GetAllRoles(); } if (e.SortDirection == SortDirection.Ascending) { Array.Sort<string>(roles); } else { Array.Sort<string>(roles, new Comparison<string>( delegate(string a, string b) { return -a.CompareTo(b); } )); } this.GridView1.DataSource = roles; this.GridView1.DataBind(); } else { e.Cancel = true; } }
效果如何呢?点一下试验一下,第一下还好用,正序排列没问题;再点一次问题就出来了:依然是正序,并没有按照预想那样变成逆序。
我在?Visual Web Developer?2005 Express?Edition 中启动 Debug,设置了断点,发现每次进入这个事件处理程序时,e.SortDirection 都等于 Ascending。看来问题出在这里啊。
尝试了其他一些方法,都没有解决这个问题,我决定使用 Reflector 来看看究竟。有这么两段代码:
private void HandleSort(string sortExpression) { if (this.AllowSorting) { SortDirection direction1 = SortDirection.Ascending; if ((this.SortExpressionInternal == sortExpression) && (this.SortDirectionInternal == SortDirection.Ascending)) { direction1 = SortDirection.Descending; } this.HandleSort(sortExpression, direction1); } }
Sorting 事件触发前,实际上是有这样的一个判断:如果此次排序列的 SortExpression 和前次的排序列的 SortExpression (GridView 的 SortExpression 属性值和它等价,即上面的 SortExpressionInternal 字段)相同,并且前次排序方向为正序,那这次排序方向就改为 Descending。其他情况为 Ascending。这段代码和我们的预期逻辑是一致的,但为什么效果不对?接着看:
private void HandleSort(string sortExpression, SortDirection sortDirection) { bool flag1 = base.IsBoundUsingDataSourceID; GridViewSortEventArgs args1 = new GridViewSortEventArgs(sortExpression, sortDirection); this.OnSorting(args1); if (!args1.Cancel) { if (flag1) { ....... this.SortExpressionInternal = args1.SortExpression; this.SortDirectionInternal = args1.SortDirection; this._pageIndex = 0; } this.OnSorted(EventArgs.Empty); base.RequiresDataBinding = true; } }
看到了吧,if (flag1) { ... } 当 flag1 也就是 base.IsBoundUsingDataSourceID 为真时,才会执行那两行改变 SortExpressionInternal 和 SortDirectionInternal 的代码。换句话说,如果使用的是第一种绑定模型,这两个 private 的字段是不会被修改的了;那么无论点多少次,在前面那个方法中,都不会进入 direction1 = SortDirection.Descending; 这一行代码的。
如果是这样,能不能我们手动设置这两个要紧的字段值呢?很遗憾,GridView 的 SortExpression 和 SortDirection 两个属性是只读的,不能直接设置;SortExpressionInternal 和 SortDirectionInternal 两个字段是 private 的,即使从 GridView 派生子类都无法访问这两个字段。
找了一圈,似乎陷入绝境了,恐怕只能等微软来解决了。
(BTW,哪位知道 .NET Framework 2.0 的 Bug 报告网址?)
====================================================
我用了留言里的一种方法解决了问题:
public SortDirection GridViewSortDirection
{
get
{
if (ViewState["sortDirection"] == null)
ViewState["sortDirection"] = SortDirection.Ascending;
return (SortDirection)ViewState["sortDirection"];
}
set { ViewState["sortDirection"] = value; }
}
然后在sorting里面改变GridViewSortDirection的值就可以了。