girdview分组,统计,排序的解决方案

本文出处:http://www.cnblogs.com/rexying/archive/2007/09/29/910560.html

girdview分组,统计,排序的解决方案


导言:
GridView控件与DataGrid相比有了很大的改进,但仍不够完美,比如进行分组(group)和统计(summary).为了实现统计功能,我们可以在RowDataBound事件里进行编码;而分组则要复杂一些。如果同时实现这2种功能就更棘手了,基于这个难题我们可以考虑使用GridViewHelper类,就像其名称一样,它的用处在于构建分组和统计.

使用GridViewHelper

下面我们将看一些GridViewHelper的示例.首先我们展示groups 和 summaries创建的方格.该示例的数据来自Northwind数据库,做了些许修改:


                                                                                                   图1


要为ItemTotal列创建统计功能,我只需要2行代码:

protected void Page_Load(object sender, EventArgs e)
{
    GridViewHelper helper = new GridViewHelper(this.GridView1);
    helper.RegisterSummary("ItemTotal", SummaryOperation.Sum);
}

首先,我们创建GridViewHelper,然后对指定的列注册summary功能,那么就可以执行summary操作了,结果如下图所示.


                                                                                                  图2:


在该示例里,最下面新添加了一行以显示统计结果.不过,我们也可以在页脚行显示统计结果,而用不着新添加一行.不同的是,新添加一行时,只生成一个单元格来显示结果,而在页脚行显示时,所有的单元格都会显示出来.

现在我们来创建分组,代码如下:
protected void Page_Load(object sender, EventArgs e)
{
    GridViewHelper helper = new GridViewHelper(this.GridView1);
    helper.RegisterGroup("ShipRegion", true, true);
    helper.ApplyGroupSort();
}

其中,RegisterGroup方法的第一个参数定义了分组依据,也就是按哪个列来进行分组.当然也可以创建交叉分组(composite group),也就是按照几个列的组合进行分组.第二个参数指定了是否自动分组,就本例而言,为每个组的标头新创建一行.第三个参数指定是否把作为分组依据的那个列强制隐藏.而ApplyGroupSort方法将分组依据的那个列作为排序标准(sort expression),就本例而言,自然就是ShipRegion了.这么做是很有必要的,因为有可能数据从数据库检索来时已经进行了某种排序.如下图,ShipRegion列已经被隐藏了:


                                                                                                   图3

让我们来看一些更有趣的事情,向各个分组添加统计功能.如下:

protected void Page_Load(object sender, EventArgs e)
{
    GridViewHelper helper = new GridViewHelper(this.GridView1);
    helper.RegisterGroup("ShipRegion", true, true);
    helper.RegisterSummary("ItemTotal", SummaryOperation.Sum, "ShipRegion");
    helper.ApplyGroupSort();
}

这次,RegisterSummary方法多了一个参数。该参数指定了创建统计功能的组的名称。组的名称自动由作为分组依据的那些列的名字生成.如果分组依据只有一个列,那么组的名称就是那一列的名称;如果分组依据有多个列,那么组名由这些列名按顺序串联起来,用加号("+")连接,如"ShipRegion+ShipName".下图为进行了分组且对每组添加统计功能的情况:


                                                                                                图4


我们还可以创建"等级组",如下所示:

protected void Page_Load(object sender, EventArgs e)
{
    GridViewHelper helper = new GridViewHelper(this.GridView1);
    helper.RegisterGroup("ShipRegion", true, true);
    helper.RegisterGroup("ShipName", true, true);
    helper.ApplyGroupSort();
}


结果如下:


                                                                              图5

GridViewHelper有一些事件可以很容易的实现视觉或功能上的调整,如下:

                                                                                        图6:

GroupStart:当新的分组开始时发生,意思就是说,当在作为分组依据的列里发现新的值时.

GroupEnd:某个组的最后一行结束时发生.

GroupHeader:当自动的为某个组添加一个标头时发生.如果不是自动的分组的话将不会触发该事件.

GroupSummary:当为某个组生成统计功能时发生。如果不是自动分组的话将不会触发该事件,不过分组类型是suppression group(我们将在后面介绍)的话,另当别论.

GeneralSummary:当算出最终累计数时发生.如果最终累计数是自动生成的,那么在添加统计行,且数字填充到行之后才触发该事件.


通过寥寥几行代码我们就可以改善界面,如下:

protected void Page_Load(object sender, EventArgs e)
{
    GridViewHelper helper = new GridViewHelper(this.GridView1);
    helper.RegisterGroup("ShipRegion", true, true);
    helper.RegisterGroup("ShipName", true, true);
    helper.GroupHeader += new GroupEvent(helper_GroupHeader);
    helper.ApplyGroupSort();
}
    
private void helper_GroupHeader(string groupName, object[] values, GridViewRow row)
{
    if ( groupName == "ShipRegion" )
    {
        row.BackColor = Color.LightGray;
        row.Cells[0].Text = "  " + row.Cells[0].Text;
    }
    else if (groupName == "ShipName")
    {
        row.BackColor = Color.FromArgb(236, 236, 236);
        row.Cells[0].Text = "     " + row.Cells[0].Text;
    }
}


改善后的界面如下:


                                                                                    图7:

更多分组选项

这里还有2个案例。第一个是交叉分组(composite group).第二个案例定义了一个suppress group,其行为与sql GROUP BY字句一样,重复的值都过滤掉,且将summary操作建立在其它列的基础上.

下面我们将看到这些交叉组的代码以及界面:

protected void Page_Load(object sender, EventArgs e)
{
    GridViewHelper helper = new GridViewHelper(this.GridView1);
    string[] cols = new string[2];
    cols[0] = "ShipRegion";
    cols[1] = "ShipName";
    helper.RegisterGroup(cols, true, true);
    helper.ApplyGroupSort();
}


                                                                                             图8

我们可以向该分组添加统计功能.这次,我们将定义一个求平均数的操作,并添加一个显示该操作的label控件:

protected void Page_Load(object sender, EventArgs e)
{
    GridViewHelper helper = new GridViewHelper(this.GridView1);
    string[] cols = new string[2];
    cols[0] = "ShipRegion";
    cols[1] = "ShipName";
    helper.RegisterGroup(cols, true, true);
    helper.RegisterSummary("ItemTotal", SummaryOperation.Avg, "ShipRegion+ShipName");
    helper.GroupSummary += new GroupEvent(helper_GroupSummary);
    helper.ApplyGroupSort();
}

private void helper_GroupSummary(string groupName, object[] values, GridViewRow row)
{
    row.Cells[0].HorizontalAlign = HorizontalAlign.Right;
    row.Cells[0].Text = "Average";
}


                                                                                     图9:

最后这个示例将创建一个suppress group.有一点很重要,如果定义了一个suppress group就不能创建其它的分组.同理,如果已经一个分组,也不能再定义一个suppress group,如果你非要硬来的话将抛出一个异常.

下面我们将看到suppress group的代码以及界面:

protected void Page_Load(object sender, EventArgs e)
{
    GridViewHelper helper = new GridViewHelper(this.GridView1);
    helper.SetSuppressGroup("ShipName");
    helper.RegisterSummary("Quantity", SummaryOperation.Sum, "ShipName");
    helper.RegisterSummary("ItemTotal", SummaryOperation.Sum, "ShipName");
    helper.ApplyGroupSort();
}


                                                                                                     图10

那些没有在summary操作里定义的列,其包含的值没有显示出来.它提示某种信息:

"Column 'column_name' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause."

没有必要将那些无关的列显示出来。为此,我们调用一个方法将它们隐藏起来:

protected void Page_Load(object sender, EventArgs e)
{
    GridViewHelper helper = new GridViewHelper(this.GridView1);
    helper.SetSuppressGroup(rdBtnLstGroup.SelectedValue);
    helper.RegisterSummary("Quantity", SummaryOperation.Sum, "ShipName");
    helper.RegisterSummary("ItemTotal", SummaryOperation.Sum, "ShipName");
    helper.SetInvisibleColumnsWithoutGroupSummary();
    helper.ApplyGroupSort();
}

结果如下:

 

                                                      图11


Summary操作

GridViewHelper有3个内置的统计操作:sum, average 以及 row count.我们可以定制自己的统计操作.为此,我们要为GridViewHelper提供2个方法.第一个方法将会被方格(或组)里的每一行所调用,第二个方法将会被调用来返回结果.下面我们将展示一个用户自定义统计操作的示例.其返回最小值:

private List<int> mQuantities = new List<int>();

protected void Page_Load(object sender, EventArgs e)
{
    GridViewHelper helper = new GridViewHelper(this.GridView1);
    helper.RegisterSummary("Quantity", SaveQuantity, GetMinQuantity);
}

private void SaveQuantity(string column, string group, object value)
{
    mQuantities.Add(Convert.ToInt32(value));
}

private object GetMinQuantity(string column, string group)
{
    int[] qArray = new int[mQuantities.Count];
    mQuantities.CopyTo(qArray);
    Array.Sort(qArray);
    return qArray[0];
}

在上面的代码中,2个方法都接受group 和 column名称.如果统计方法不涉及到group,那么该参数为null.这些方法都被每一行所调用,同时接受当前行的某列的值.最终效果如下:


                                                                                            图12


局限性

在上面的例子我们模拟(simulate)了一个"等级组"。虽然该方格看起来具有"层次感",但执行起来绝不会按层次进行.没有group 或 subgroup,只有sequentially registered groups.不过,如果我们想对一个内镶组添加统计功能时,这倒是个问题.下面,我们将看在这种情况下将会发生什么情况:
protected void Page_Load(object sender, EventArgs e)
{
    GridViewHelper helper = new GridViewHelper(this.GridView1);
    helper.RegisterGroup("ShipRegion", true, true);
    helper.RegisterGroup("ShipName", true, true);
    helper.RegisterSummary("ItemTotal", SummaryOperation.Sum, "ShipName");
    helper.RegisterSummary("ItemTotal", SummaryOperation.Sum);
    helper.GroupSummary += new GroupEvent(helper_Bug);
    helper.ApplyGroupSort();
}


private void helper_Bug(string groupName, object[] values, GridViewRow row)
{
    if (groupName == null) return;

    row.BackColor = Color.Bisque;
    row.Cells[0].HorizontalAlign = HorizontalAlign.Center;
    row.Cells[0].Text = "[ Summary for " + groupName + " " + values[0] + " ]";
}

 

                                                                        图13

如上图所示,summary在外部组(outer group)的标头创建后再创建.原因是,本来事件的发生顺序是:
Group1_Start
Group1_End
Group2_Start
Group2_End

对hierarchical grouping而言,事件发生顺序为:

Group1_Start
Group2_Start
Group2_End
Group1_End

贯彻

GridViewHelper是作为一个独立类(standalone class)而不是继承类来贯彻的,这就使的利用GridViewHelper来处理任何的GridView成为了可能。另外还有4个类:GridViewSummary, GridViewGroup, GridViewSummaryList 以及 GridViewGroupList.这些"list" classes可以利用一个字符串索引来进行访问,如:
helper.GeneralSummaries["ItemTotal"].Value.

当创建GridViewHelper时,将对目标GridView的一个引用进行保存,且RowDataBound事件将绑定到一个方法完成实际的工作:

public GridViewHelper(GridView grd, bool useFooterForGeneralSummaries, SortDirection groupSortDirection)
{
    this.mGrid = grd;
    this.useFooter = useFooterForGeneralSummaries;
    this.groupSortDir = groupSortDirection;
    this.mGeneralSummaries = new GridViewSummaryList();
    this.mGroups = new GridViewGroupList();
    this.mGrid.RowDataBound += new GridViewRowEventHandler(RowDataBoundHandler);
}

GridViewHelper内部使用的一些方法被定义为public,因为如果必要的话可以将这些有用的方法进行用户定制.还有一些其它的操作选择没有在示例里进行演示,不过我们可以通过Visual Studio的智能感知系统来查证.

纪要:

对值类型的过多的的装箱和拆箱操作会对执行性能产生影响.为此,我们可以用泛型来贯彻这些内置的统计操作,但这并没有想象的那么容易,可以参阅文章《Using Generics for Calculations》(http://www.codeproject.com/csharp/genericnumerics.asp)或《Operator Overloading with Generics》(http://www.codeproject.com/csharp/genericoperators.asp).在现实生活中,这种影响不太显著,除非有数百万行,或有数千个用户分组,且同时进行数据统计操作.

在线案例将GridView的EnableViewState设置为false. 这是必要的,因为当其为true,在一个页面回传时,GridView控件将从ViewState进行重新绑定,而不会触发RowDataBound事件.我们可以在ASP.Net 2.0里放心的禁用该ViewState,因为ControlState仍然进行了保存.

posted @ 2010-11-30 14:25  ccczqh  阅读(787)  评论(0编辑  收藏  举报