C1FlexGrid控件具有可以让你汇总数据并以分层方式显示的方法和属性。若要汇总数据并添加聚合值,请使用C1FlexGrid.Subtotal方法。若要显示数据的分层视图,请使用“树型”属性。
如果您是第一次阅读本系列文章,建议您阅读:
- ComponentOne FlexGrid for WinForms 中文版快速入门(1)--开始使用 FlexGrid
- ComponentOne FlexGrid for WinForms 中文版快速入门(2)--设计时支持
- ComponentOne FlexGrid for WinForms 中文版快速入门(3)--单元格、行列交互
- ComponentOne FlexGrid for WinForms 中文版快速入门(4)--设置单元格格式
- ComponentOne FlexGrid for WinForms 中文版快速入门(5)--设置单元格类型(上)
- ComponentOne FlexGrid for WinForms 中文版快速入门(5)--设置单元格类型(下)
- ComponentOne FlexGrid for WinForms 中文版快速入门(6)—合并单元格
1.1.1 创建分类汇总
C1FlexGrid.Subtotal方法可以增加包含普通(非小计)行的汇总数据的分类汇总行。
分类汇总支持分层聚合。例如,如果你的表格包含销售数据,你可能会通过产品、地区和推销员来小计一下以得出总的销售数字。下面的代码说明了这一点:
· Visual Basic
Private Sub ShowTotals() ' 在第零列显示大纲栏。 _flex.Tree.Column = 0 _flex.Tree.Style = TreeStyleFlags.Simple ' 清除现有的分类汇总。 _flex.Subtotal(AggregateEnum.Clear) ' 获得总计(使用-1,而不是列索引)。 _flex.Subtotal(AggregateEnum.Sum, -1, -1, 3, "Grand Total") ' 每个产品的总计(第零列)。 _flex.Subtotal(AggregateEnum.Sum, 0, 0, 3, "Total {0}") ' 每个区域的总计(第一列)。 _flex.Subtotal(AggregateEnum.Sum, 1, 1, 3, "Total {0}") ' 基于内容来调整列宽。 _flex.AutoSizeCols() End Sub |
· C#
private void ShowTotals() { // 在第零列显示大纲栏。 _flex.Tree.Column = 0; _flex.Tree.Style = TreeStyleFlags.Simple; // 清除现有的分类汇总。 _flex.Subtotal(AggregateEnum.Clear); // 获得总计(使用-1,而不是列索引)。 _flex.Subtotal(AggregateEnum.Sum, -1, -1, 3, "Grand Total"); // 每个产品的总计(第零列)。 _flex.Subtotal(AggregateEnum.Sum, 0, 0, 3, "Total {0}"); // 每个区域的总计(第一列)。 _flex.Subtotal(AggregateEnum.Sum, 1, 1, 3, "Total {0}"); // 基于内容来调整列宽。 _flex.AutoSizeCols(); } |
当C1FlexGrid.Subtotal方法添加了汇总信息行,它会自动分配汇总样式到新的行(有五个层级的分类汇总内置样式)。你可以使用“样式编辑器”或代码在设计器中改变大纲样式的属性,以此来自定义分类汇总行的外观。例如:
· Visual Basic
' 设置分类汇总的样式。 Dim cs As C1.Win.C1FlexGrid.CellStyle cs = _flex.Styles(C1.Win.C1FlexGrid.CellStyleEnum.GrandTotal) cs.BackColor = Color.Black cs.ForeColor = Color.White cs.Font = New Font(Font, FontStyle.Bold) cs = _flex.Styles(C1.Win.C1FlexGrid.CellStyleEnum.Subtotal0) cs.BackColor = Color.DarkRed cs.ForeColor = Color.White cs.Font = New Font(Font, FontStyle.Bold) cs = _flex.Styles(C1.Win.C1FlexGrid.CellStyleEnum.Subtotal1) cs.BackColor = Color.DarkBlue cs.ForeColor = Color.White |
· C#
// 设置分类汇总的样式。 CellStyle cs; cs = _flex.Styles[CellStyleEnum.GrandTotal]; cs.BackColor = Color.Black; cs.ForeColor = Color.White; cs.Font = new Font(Font, FontStyle.Bold); cs = _flex.Styles[CellStyleEnum.Subtotal0]; cs.BackColor = Color.DarkRed; cs.ForeColor = Color.White; cs.Font = new Font(Font, FontStyle.Bold); cs = _flex.Styles[CellStyleEnum.Subtotal1]; cs.BackColor = Color.DarkBlue; cs.ForeColor = Color.White; |
执行此代码后,表格看起来是这样的:
总计行中包含的所有产品,地区和销售人员的销售总额。它是使用groupOn参数-1在调用C1FlexGrid.Subtotal方法时被创建的。其他分类汇总显示产品和地区的销售总额。他们是用Groupon的参数值0和1创造的。
除了总量之外,你也可以计算其他分类汇总(例如,平均值或百分比),并计算每一行的几个汇总(例如,毛销售额及净销售额)。
由分类汇总的方法创建的小计行不同于其他普通行,主要体现在三个方面:
1. 以flexSTClear参数来调用分类汇总的方法,小计行可以被自动删除。在用户可以移动列并对数据进行重新排序的地方,重新计算分类汇总很有必要,而这在提供数据的动态视图方面非常有用。
2. 小计行可作为一个大纲中的节点使用,它可以让你折叠和展开行组来展现数据的概述或透露其细节。要看到大纲的树型图,你需要设置列和“树型样式”的属性来确定大纲树型图的位置和外观。
3. 小计行可以被视为树型结构上的的节点。通过“节点”属性,你可以为任何小计行获得一个“节点”对象。
4. 当表格被绑定到一个数据源,小计行并不符合实际的数据。如果你在数据源中移动光标,则小计行将在表格中被跳过。
大纲树型图可以允许用户通过点击节点来折叠和展开表格的各部分。你可以使用大纲树型图来显示许多类型的信息,不仅仅是汇总这一种。下一主题将会指出如何创建一个自定义的大纲树型图来显示目录信息。
1.1.2 创建自定义树型图
要想不使用分类汇总法来创建大纲树型图,你需要遵循以下步骤:
1. 将行添加到表格中。
2. 通过将他们的“是节点”属性设置为“真”,将一些行转变成大纲的节点。
3. 获得每个节点行的节点对象,并设置其“等级”属性来确定树型层次结构中节点的位置值越高意味着该节点在大纲树型图中越深入(缩进得多)。
例如,下面的代码可以创建一个目录树:
· Visual Basic
' 在窗体的顶部添加这些输入声明。 Imports System.IO Imports C1.Win.C1FlexGrid Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load ' 初始化表格布局。 _flex.Cols.Fixed = 0 _flex.Cols.Count = 1 _flex.Rows.Count = 1 _flex.ExtendLastCol = True _flex.Styles.Normal.TextAlign = TextAlignEnum.LeftCenter _flex.Styles.Normal.Border.Style = BorderStyleEnum.None ' 显示大纲树型图。 _flex.Tree.Column = 0 _flex.Tree.Style = TreeStyleFlags.SimpleLeaf _flex.Tree.LineColor = Color.DarkBlue ' 填充表格。 AddDirectory("c:\", 0) End Sub |
· C#
// 在窗体的顶部添加这些输入声明。 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.IO; using C1.Win.C1FlexGrid; private void Form1_Load(object sender, EventArgs e) { // 初始化表格布局。 _flex.Cols.Fixed = 0; _flex.Cols.Count = 1; _flex.Rows.Count = 1; _flex.ExtendLastCol = true; _flex.Styles.Normal.TextAlign = TextAlignEnum.LeftCenter; _flex.Styles.Normal.Border.Style = BorderStyleEnum.None; // 显示大纲树型图。 _flex.Tree.Column = 0; _flex.Tree.Style = TreeStyleFlags.SimpleLeaf; _flex.Tree.LineColor = Color.DarkBlue; // 填充表格。 AddDirectory(@"c:\\", 0); } |
以上的代码初始化了表格布局,并且调用了“添加目录”程序,这意味着做了填充网格和建立的树型结构的工作:
· Visual Basic
Private Sub AddDirectory(ByVal dir As String, ByVal level As Integer) ' 添加该目录。 Dim thisDir As String thisDir = Path.GetFileName(dir) If thisDir.Length = 0 Then thisDir = dir _flex.AddItem(thisDir) ' 使这个新行成为一个节点。 Dim row As Row row = _flex.Rows(_flex.Rows.Count - 1) row.IsNode = True ' 设置该节点的层级。 Dim nd As Node nd = row.Node nd.Level = level ' 在此目录中添加文件。 Dim file As String, cnt As Integer, r As Row cnt = 0 For Each file In Directory.GetFiles(dir) _flex.AddItem(Path.GetFileName(file).ToLower()) r = _flex.Rows(_flex.Rows.Count – 1) r.IsNode = True r.Node.Level = level + 1 cnt = cnt + 1 If cnt > 10 Then Exit For Next ' 添加子目录(到4级)。 If level <= 4 Then Dim subdir As String cnt = 0 For Each subdir In Directory.GetDirectories(dir) AddDirectory(subdir, level + 1) cnt = cnt + 1 If cnt > 10 Then Exit For Next |
· C#
private void AddDirectory(string dir, int level) { // 添加该目录。 string thisDir = Path.GetFileName(dir); if (thisDir.Length == 0) { thisDir = dir; } _flex.AddItem(thisDir); // 使这个新行成为一个节点。 Row row = _flex.Rows[_flex.Rows.Count - 1]; row.IsNode = true; // 设置该节点的层级。 Node nd = row.Node; nd.Level = level; // 在此目录中添加文件。 int cnt = 0; Row r; foreach (string file in Directory.GetFiles(dir)) { _flex.AddItem(Path.GetFileName(file).ToLower()); // 将没有子行的行标记为节点。 r = _flex.Rows[_flex.Rows.Count - 1]; r.IsNode = true; r.Node.Level = level + 1; cnt = cnt + 1; if (cnt > 10) break; } // 添加子目录(到4级)。 if (level <= 4) { cnt = 0; foreach (string subdir in Directory.GetDirectories(dir)) { AddDirectory(subdir, level + 1); cnt = cnt + 1; if (cnt > 10) break; } } } |
“添加目录”是一个递归程序,它横跨当前目录及其所有子目录。在这个例子中,为了节省时间,树的大小局限在四个目录层次。在实际应用中,当它们被扩大时该程序应改为仅填充树型分支(请参阅式FlexGrid for WinForms教程(第107页))。
此代码可以创建一个表格,看起来如下图:
1.1.3 用C1FlexGrid控件来创建大纲和树型图
C1FlexGrid控件独特的和流行的特点之一是能够添加层次分组到常规的非结构化数据。
为了实现这一目标,C1FlexGrid介绍了节点行的概念。节点行不包含常规的数据。相反,他们作为表头在类似数据的分组下面运作,酷似一个常规TreeView控件中的节点。就像一个TreeView控件中的节点,节点行可以折叠和扩展,隐藏或显示它们所包含的数据。像一个TreeView控件中的节点的另一方面是,节点行有一个能定义节点层次的层级属性。较低级别的节点包含较高级别的节点。例如,假设你有一个可以显示客户名称,国家,城市,销售额的表格。这种典型的表格通常看起来是这样的
所有的信息都是存在的,但很难看到每一个国家或客户的总销售额。你可以使用C1FlexGrid的概述功能按国家(0级)对数据进行分组,然后按每个国家的城市(1级),然后按每个城市的顾客(2级)。下面是加入大纲之后的相同表格:
该表格会像前一个(被绑定到同一数据源的)一样显示相同的信息,但它增加了一个树型图,那里的每个节点都包含了它下面的数据摘要。节点可以折叠起来只显示摘要,或展开以显示细节。请注意,每个节点一行都可以显示多个列的摘要(在这种情况下,合计单位销售量并合计总金额)。
在这篇文章中,我们将引导你熟悉将一个普通的表格转变成一个更丰富的大纲型表格的过程。
加载数据
将数据加载到一个大纲型表格与将其加载到一个普通的表格是完全相同的。如果你的数据源在设计时是可用的,你可以使用Visual Studio属性窗口来设置表格的“数据源”属性,并且无需编写任何代码就可以将表格绑定到数据。
如果数据源在设计时是不可用的,你可以在代码中设置表格的“数据源”属性。数据绑定代码看起来通常是这样的:
public Form1() { InitializeComponent(); // 获取数据 var fields = @" Country, City, SalesPerson, Quantity, ExtendedPrice"; var sql = string.Format("SELECT {0} FROM Invoices ORDER BY {0}", fields); var da = new OleDbDataAdapter(sql, GetConnectionString()); da.Fill(_dt); // 将表格绑定到数据 this._flex.DataSource = _dt; // 为“总价”列设置格式 _flex.Cols["ExtendedPrice"].Format = "n2"; } |
代码使用一个OleDbDataAdapter来用数据填充一个数据表,然后,将数据表分配给表格的“数据源”属性。
运行此代码后,你会看到在第一张图片中显示了一个“普通的表格”。要使这个普通的表格变成第二张图片中显示的那种大纲型表格,我们需要插入节点行来整理这个大纲。
创建节点行
节点行几乎都是同样的普通行,但以下情况除外:
· 节点行不是数据绑定。当表格被绑定到一个数据源时,每个普通的行会对应数据源中的一个项目。而节点行则不然。相反,它们的存在是为了给包含类似数据的普通行分组。
· 节点行可以折叠或展开。当一个节点行折叠起来时,它的所有数据和子节点都隐藏起来了。如果大纲树型图可见,用户可以用鼠标或键盘来折叠和展开节点。
如果大纲树型图不可见,则只能用代码来扩展或折叠节点。
你可以使用“是节点”属性来确定一个行是否是节点行:
var row = _flex.Rows[rowIndex]; if (row.IsNode) { // 该行是一个节点 var node = row.Node; DoSomethingWithTheNode(node); } else { // 该行不是一个节点 } |
节点行可以用以下三种方法来创建:
1. 使用Rows.InsertNode方法。这将在指定的索引中插入一个新的节点行。一旦该节点行被创建成功,你可以像使用任何其他行一样使用它(设置每列的数据、应用样式等)。
2. 使用C1FlexGrid.Subtotal方法。这种方法会在表格中数据发生变化的地方用可选的分类汇总来扫描整个表格并自动插入节点行。这是插入汇总和构建大纲的“高层次”方式。它只需要非常少的代码,但对表格中的数据是如何排列的和大纲看起来应该像什么样子做了一些相关的假设。
3. 如果表格是未绑定的,那么你可以通过将 “是节点”属性设置为“真”来将一些普通行变成节点行。请注意,这仅限于当表格处于未绑定的状态下。试图将一个普通的数据绑定行变成一个节点,可能会造成表格抛出一个异常。
以下的代码演示了你应该如何来执行一个“分组依据”程序,插入节点行并对一个给定列的近似值进行分组。
// 在给定列上将插入的同一个给定级别的节点分组 void GroupBy(string columnName, int level) { object current = null; for (int r = _flex.Rows.Fixed; r < _flex.Rows.Count; r++) { if (!_flex.Rows[r].IsNode) { var value = _flex[r, columnName]; if (!object.Equals(value, current)) { // 值的变化:插入节点 _flex.Rows.InsertNode(r, level); // 在第一个滚动列显示分组的名称 _flex[r, _flex.Cols.Fixed] = value; // 更新当前值 current = value; } } } } |
代码可以跳过现有的节点行(因此它可以被称为添加几个层次的节点)来扫描所有列,并记录分组列的当前值的轨迹。当当前值发生变化时,在第一滚动列会插入一个节点行,新组的名称会在此显示。
回到我们的例子,你可以使用此方法通过调用来创建两个级别的大纲:
void _btnGroupCountryCity_Click(object sender, EventArgs e) { GroupBy("Country", 0); GroupBy("City", 1); } |
这很简单,但也有一些需要注意的事项。首先,该方法假定数据是按照大纲结构进行排序的。在这个例子中,如果数据是按照销售人员,而不是按照国别排序的,那么该大纲中每个国家都会对应好几个零级节点,这可能并不是你想要的。此外,“分组依据”程序可以插入许多行,这可能导致表格闪烁不定。为了避免这种情况,通常你应该先将“重绘”属性设置为“假”,然后再作出更新,并且当完成后再将其设置回“真”。
为了解决这些问题,用来创建大纲的代码应该重新编写如下:
void _btnGroupCountryCity_Click(object sender, EventArgs e) { // 暂停重绘,同时更新 using (new DeferRefresh(_flex)) { // 恢复原来的排序(按照国家、城市等) ResetBinding(); // 按照国家、城市来分组 GroupBy("Country", 0); GroupBy("City", 1); } } |
“延迟刷新”类是一种简单的功能,它可以将表格的“重绘”属性设置为“假”,并且当它被破坏时可以恢复其原有的值。这将确保“重绘”属性 被完全地恢复,即便是在更新时发生例外的情况下。以下是 “延迟刷新”类的执行情况:
/// 实用工具类,用于封装在重绘版块的表格冗长的操作。 /// 这样就避免了闪烁,并且可以确保在发生万一的情况下“重绘”属性能够妥善地复位。 /// 在操作过程中抛出一个异常。 class DeferRefresh : IDisposable { C1FlexGrid _grid; bool _redraw; public DeferRefresh(C1FlexGrid grid) { _grid = grid; _redraw = grid.Redraw; grid.Redraw = false; } public void Dispose() { _grid.Redraw = _redraw; } } |
“绑定表格”的方法可以确保表格会按照我们的大纲结构所需的顺序排序。在我们的例子中,排序的顺序是按照国家、城市和销售人员。代码看起来则是像这个样子的:
// unbind and re-bind grid in order to reset everything void ResetBinding() { // 解除绑定表格 _flex.DataSource = null; // 重置任何自定义排序 _dt.DefaultView.Sort = string.Empty; // 重新绑定表格 _flex.DataSource = _dt; // 设置总价列的格式 _flex.Cols["ExtendedPrice"].Format = "n2"; // 自动调整列宽以适配其内容 flex.AutoSizeCols(); } |
如果你现在运行此代码,你会发现该节点行如预期一样创建,但大纲树型图是不可见的,所以你不能展开和折叠节点。下一节会对大纲树型图进行描述。