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仍然进行了保存.