在開發ERP系統的資料包表時,幾乎都是需要看到【小計】、【總計】這樣的彙總資料的,在資料包表的顯示列表中,最下面的一行通常就是【小計】或者【總計】的彙總行。如果手動為每個報表都增加彙總行,那也是一份不小的工作量。
所以,如果能自動為每個資料包表自動新增【小計】、【總計】彙總行,那將可以節省不少的開發時間。本文將給出實現這種方案的思路原理以及原始碼。
本文中,報表資料的顯示使用的是WinForm的DataGridView控制元件,如果是Web專案,其原理和思路是一樣的。
舉個栗子,零售店POS機上查詢銷售單的效果如下圖所示:
在報表的最下面有一行【總計】,對數量和金額項進行了彙總。
下面,我們來詳細講解是如何自動為其新增【總計】這一彙總行的。
一.基本原理
1.一般而言,資料包表的現實的核心資料是DataGridView繫結的一個列表List<T>,每一個T物件對應著報表中的一行資料。
2.在資料包表中,【小計】、【總計】所對應的彙總行與上面列表中的其它普通行,是有區別的,所以,它們對應的T物件也是有區別的。
3.我們通過T的一個名為 IsStatistics 的bool屬性來區別普通行與彙總行。如果IsStatistics為true,表示其對應的行就為彙總行。
4.為了達到上面描述的這一目的,我們讓T必須實現介面IStatisticabled這個介面。
public interface IStatisticabled { bool IsStatistics { set; get; } }
5.為報表新增彙總行,實際上就是向List<T>列表中新增一個 IsStatistics 為true的 T 物件。
6.然後,通過反射來統計需要彙總的那些項(即T的某些屬性),然後將彙總得到的結果賦值給彙總T物件對應的屬性。
7.在繫結到DataGridView時,通過判斷列表中 T 物件的IsStatistics屬性,如果為true,就將該行Row對應的RowHeader的文字設定為【小計】或【總計】。
二.思路實現
就上面描述的思路來看,稍微有點難度的地方在於最後兩點,下面我們就詳細講解一下。
1.為List<T>增加彙總行
增加彙總行,所用到的主要技術就是反射Relection。
/// <summary> /// 為資料包表增加一個彙總行。 /// </summary> /// <typeparam name="T">報表記錄物件的型別</typeparam> /// <param name="list">資料行物件列表</param> /// <param name="statColumns">需要進行統計的列</param> public static void AddSumRow<T>(List<T> list ,params string[] statColumns) where T :IStatisticabled, new() { T sum = new T(); sum.IsStatistics = true; foreach (string column in statColumns) //針對每一個彙總項 { double total = 0; foreach (T t in list) //統計 { object val = ReflectionHelper.GetProperty(t, column); total += double.Parse(val.ToString()); } object newTotal = TypeHelper.ChangeType(typeof(T).GetProperty(column).PropertyType, total); ReflectionHelper.SetProperty(sum, column, newTotal); } list.Add(sum); }
(1)為了可以動態new一個統計行,必須要讓T有 new() 這個約束。
(2)將統計行的 IsStatistics 標記設定為true。
(3)針對每一個統計項進行統計:通過反射拿到每一行該項的屬性值,並轉換成double型別(因為double相容了所有的數值型別),進行累加,然後將累加的結果轉換成正確的型別,最後,賦值給統計行對應的屬性。
(4)將統計行新增到list列表中,作為最後一個物件。
2.繫結到DataGridView
List<RetailOrder> list = this.GetOrderList(); AddSumRow(list, new string[] { "Count", "Money" }); this.dataGridView1.DataSource = list; DataGridViewCellStyle style = new DataGridViewCellStyle(); style.Font = new Font(this.dataGridView1.DefaultCellStyle.Font.Name, this.dataGridView1.DefaultCellStyle.Font.Size, FontStyle.Bold); this.dataGridView1.RowHeadersDefaultCellStyle = style; this.dataGridView1.Rows[list.Count - 1].HeaderCell.Value = "總計"; this.dataGridView1.Rows[list.Count - 1].DefaultCellStyle = style;
(1)通過GetOrderList方法獲取到銷售單列表後,我們通過AddSumRow方法為其新增一個彙總行,並對【Count】、【Money】進行彙總。
(2)將包含了彙總行的列表繫結到DataGridView。
(3)將最後一行的RowHeader的Cell的value設定為【總計】 。
(4)將【總計】行的所有資料顯示都變成粗體。
三.示例原始碼下載
1.本文Demo原始碼:DataReportsSample.rar
2.該Demo中用到了ESBasic的反射幫助類ReflectionHelper,這裡可以下載我的開源基礎類庫:ESBasic 原始碼。
四.後續功能
本文示例是一個【小計】、【總計】彙總功能的基礎展示,實際應用中,通常還會牽涉到以下問題:
(1)當報表資料存在分頁時,一般會同時存在【小計】、【總計】行,【小計】是本頁的彙總,【總計】是所有業的彙總。
(2)當DataGridView繫結的某些列對應著Entity的某個只讀屬性,並且這個只讀屬性不會返回null和string.Empty時,彙總行的這一列就會有文字顯示(這是一個非彙總行,目標單元格應該是空的),要如何處理?
(3)當DataGridView的某一些是一個操作列,即對應著DataGridViewLinkColumn,如果讓彙總行的目標單元格中不出現Link,而是留空了?
(4)有些資料的彙總,可能是要進行絕對值的彙總,那又該如何處理?
這些問題,我們都將在下篇文章的示例中一併解決,敬請期待。