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

时间:2022-10-17 09:50:18
 

转自:http://hi.baidu.com/sneer_owen/blog/item/ec85e0df164d4651cdbf1a02.html

 

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

使用 GridViewHelper

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

girdview分组,统计,排序的解决方案
                                                                                              图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 操作了,结果如下图所示.

girdview分组,统计,排序的解决方案
                                                                                             图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 列已经被隐藏了:

girdview分组,统计,排序的解决方案
                                                                                             图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".下图为进行了分组且对每组添加统计功能的情况:

girdview分组,统计,排序的解决方案
                                                                                     图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();
}

结果如下:

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

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

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

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

改善后的界面如下:

girdview分组,统计,排序的解决方案
                                                                                     图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();
}

girdview分组,统计,排序的解决方案
                                                                                             图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";
}

girdview分组,统计,排序的解决方案
                                                                                     图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();
}

girdview分组,统计,排序的解决方案
                                                                                                     图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();
}

结果如下:

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

                                                      图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.这些方法都被每一行所调用,同时接受当前行的某列的值.最终效果如下:

girdview分组,统计,排序的解决方案
                                                                                            图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] + " ]";
}

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

                                                                        图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》或《Operator Overloading with Generics》。在现实生活中,这种影响不太显著,除非有数百万行,或有数千个用户分组,且同时进行数据统计操作.

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

转自:http://hi.baidu.com/sneer_owen/blog/item/ec85e0df164d4651cdbf1a02.html

 

示例代码http://www.agrinei.com/gridviewhelper/gridviewhelpersample_en.zip