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(); } |
如果你現在運行此代碼,你會發現該節點行如預期一樣創建,但大綱樹型圖是不可見的,所以你不能展開和折疊節點。下一節會對大綱樹型圖進行描述。