之前一直使用各種報表工具,如RDLC、DevExpress套件的XtraReport報表,在之前一些隨筆也有介紹,最近接觸銳浪的Grid++報表,做了一些測試例子和輔助類來處理報表內容,覺得還是很不錯的,特別是它的作者提供了很多報表的設計模板案例,功能還是非常強大的。試著用來做一些簡單的報表,測試下... ...
之前一直使用各種報表工具,如RDLC、DevExpress套件的XtraReport報表,在之前一些隨筆也有介紹,最近接觸銳浪的Grid++報表,做了一些測試例子和輔助類來處理報表內容,覺得還是很不錯的,特別是它的作者提供了很多報表的設計模板案例,功能還是非常強大的。試著用來做一些簡單的報表,測試下功能,發現常規的二維表、套打、條形碼二維碼等我關註的功能都有,是一個比較強大的報表控制項,本篇隨筆主要介紹在Winform開發中使用Grid++報表設計報表模板,以及綁定數據的處理過程。
1、報表模板設計
這個報表系統,報表模板提供了很多案例,我們可以大概瀏覽下其功能。
它對應在相應的文件目錄裡面,我們可以逐一查看瞭解下,感覺提供這麼多報表還是很贊的,我們可以參考著來用,非常好。
整個報表主要是基於現有數據進行一個報表的模板設計的,如果要預覽效果,我們一般是需要綁定現有的數據,可以從各種資料庫提供數據源,然後設計報表模板,進行實時的數據和格式查看及調整。
空白的報表模板大概如下所示,包含頁眉頁腳,以及明細表格的內容。
根據它的教程,模仿著簡單的做了一個報表,也主要是設計報表格式的調整,和數據源的處理的關係,我們做一個兩個報表就可以很快上手了。
為了動態的加入我們表格所需要的列,我們可以通過資料庫裡面的欄位進行加入,首先提供數據源,指定我們具體的表即可(如果是自定義的信息,則可以手工添加欄位)
這個裡面就是配置不同的資料庫數據源了
如SQLServer資料庫的配置信息如下。
為了方便,我們可以利用案例的Access資料庫,也就是Northwind.mdb來測試我們的報表,弄好這些我們指定對應的數據表數據即可。
這裡面配置好資料庫表信息後,我們就可以用它生成相關的欄位和對應的列信息了
修改列的表頭,讓它符合中文的表頭列,如下所示。
我們在頁腳出,加入了列印時間,頁碼的一些系統變數,具體操作就是添加一個綜合文本,然後在內容裡面插入指定的域內容即可,如下所示
預覽報表,我們就可以看到具體的報表格式顯示了。
通過上面的操作,感覺生成一個報表還是很方便的,接著我有根據需要做了一個二維碼的報表顯示,方便列印資產標簽。
綁定數據源顯示的報表視圖如下所示,看起來還是蠻好的。
2、數據綁定
一般我們綁定數據源,有的時候可以直接指定資料庫連接,有時候可以綁定具體的數據列表,如DataTable或者List<T>這樣的數據源,不同的方式報表控制項的代碼綁定不同。
直接綁定數據表的路徑如下所示。
/// <summary> /// 普通連接資料庫的例子-列印預覽 /// </summary> private void btnNormalDatabase_Click(object sender, EventArgs e) { Report = new GridppReport(); string reportPath = Path.Combine(Application.StartupPath, "Reports\\testgrid++.grf"); string dbPath = Path.Combine(Application.StartupPath, "Data\\NorthWind.mdb"); //從對應文件中載入報表模板數據 Report.LoadFromFile(reportPath); //設置與數據源的連接串,因為在設計時指定的資料庫路徑是絕對路徑。 if (Report.DetailGrid != null) { string connstr = Utility.GetDatabaseConnectionString(dbPath); Report.DetailGrid.Recordset.ConnectionString = connstr; } Report.PrintPreview(true); }
而如果需要綁定和資料庫無關的動態數據源,那麼就需要通過控制項的FetchRecord進行處理了,如下代碼所示。
Report.FetchRecord += new _IGridppReportEvents_FetchRecordEventHandler(ReportFetchRecord);
通過這樣我們增加每一個對應的列單元格信息,如下是隨帶案例所示
//在C#中一次填入一條記錄不能成功,只能使用一次將記錄全部填充完的方式 private void ReportFetchRecord() { //將全部記錄一次填入 Report.DetailGrid.Recordset.Append(); FillRecord1(); Report.DetailGrid.Recordset.Post(); Report.DetailGrid.Recordset.Append(); FillRecord2(); Report.DetailGrid.Recordset.Post(); Report.DetailGrid.Recordset.Append(); FillRecord3(); Report.DetailGrid.Recordset.Post(); } private void FillRecord1() { C1Field.AsString = "A"; I1Field.AsInteger = 1; F1Field.AsFloat = 1.01; } private void FillRecord2() { C1Field.AsString = "B"; I1Field.AsInteger = 2; F1Field.AsFloat = 1.02; } private void FillRecord3() { C1Field.AsString = "C"; I1Field.AsInteger = 3; F1Field.AsFloat = 1.03; }
這樣處理肯定很麻煩,我們常規做法是弄一個輔助類,來處理DataTable和List<T>等這樣類型數據的動態增加操作。
/// <summary> /// 綁定實體類集合的例子-列印預覽 /// </summary> private void btnBindList_Click(object sender, EventArgs e) { Report = new GridppReport(); //從對應文件中載入報表模板數據 string reportPath = Path.Combine(Application.StartupPath, "Reports\\testList.grf"); Report.LoadFromFile(reportPath); Report.FetchRecord += ReportList_FetchRecord; Report.PrintPreview(true); } /// <summary> /// 綁定DataTable的例子-列印預覽 /// </summary> private void btnBindDatatable_Click(object sender, EventArgs e) { Report = new GridppReport(); //從對應文件中載入報表模板數據 string reportPath = Path.Combine(Application.StartupPath, "Reports\\testList.grf"); Report.LoadFromFile(reportPath); Report.FetchRecord += ReportList_FetchRecord2; Report.PrintPreview(true); } private void ReportList_FetchRecord() { List<ProductInfo> list = BLLFactory<Product>.Instance.GetAll(); GridReportHelper.FillRecordToReport<ProductInfo>(Report, list); } private void ReportList_FetchRecord2() { var dataTable = BLLFactory<Product>.Instance.GetAllToDataTable(); GridReportHelper.FillRecordToReport(Report, dataTable); }
其中輔助類 GridReportHelper 代碼如下所示。
/// <summary> /// Gird++報表的輔助類 /// </summary> public class GridReportHelper { private struct MatchFieldPairType { public IGRField grField; public int MatchColumnIndex; } /// <summary> /// 將 DataReader 的數據轉儲到 Grid++Report 的數據集中 /// </summary> /// <param name="Report">報表對象</param> /// <param name="dr">DataReader對象</param> public static void FillRecordToReport(IGridppReport Report, IDataReader dr) { MatchFieldPairType[] MatchFieldPairs = new MatchFieldPairType[Math.Min(Report.DetailGrid.Recordset.Fields.Count, dr.FieldCount)]; //根據欄位名稱與列名稱進行匹配,建立DataReader欄位與Grid++Report記錄集的欄位之間的對應關係 int MatchFieldCount = 0; for (int i = 0; i < dr.FieldCount; ++i) { foreach (IGRField fld in Report.DetailGrid.Recordset.Fields) { if (string.Compare(fld.RunningDBField, dr.GetName(i), true) == 0) { MatchFieldPairs[MatchFieldCount].grField = fld; MatchFieldPairs[MatchFieldCount].MatchColumnIndex = i; ++MatchFieldCount; break; } } } // 將 DataReader 中的每一條記錄轉儲到Grid++Report 的數據集中去 while (dr.Read()) { Report.DetailGrid.Recordset.Append(); for (int i = 0; i < MatchFieldCount; ++i) { var columnIndex = MatchFieldPairs[i].MatchColumnIndex; if (!dr.IsDBNull(columnIndex)) { MatchFieldPairs[i].grField.Value = dr.GetValue(columnIndex); } } Report.DetailGrid.Recordset.Post(); } } /// <summary> /// 將 DataTable 的數據轉儲到 Grid++Report 的數據集中 /// </summary> /// <param name="Report">報表對象</param> /// <param name="dt">DataTable對象</param> public static void FillRecordToReport(IGridppReport Report, DataTable dt) { MatchFieldPairType[] MatchFieldPairs = new MatchFieldPairType[Math.Min(Report.DetailGrid.Recordset.Fields.Count, dt.Columns.Count)]; //根據欄位名稱與列名稱進行匹配,建立DataReader欄位與Grid++Report記錄集的欄位之間的對應關係 int MatchFieldCount = 0; for (int i = 0; i < dt.Columns.Count; ++i) { foreach (IGRField fld in Report.DetailGrid.Recordset.Fields) { if (string.Compare(fld.Name, dt.Columns[i].ColumnName, true) == 0) { MatchFieldPairs[MatchFieldCount].grField = fld; MatchFieldPairs[MatchFieldCount].MatchColumnIndex = i; ++MatchFieldCount; break; } } } // 將 DataTable 中的每一條記錄轉儲到 Grid++Report 的數據集中去 foreach (DataRow dr in dt.Rows) { Report.DetailGrid.Recordset.Append(); for (int i = 0; i < MatchFieldCount; ++i) { var columnIndex = MatchFieldPairs[i].MatchColumnIndex; if (!dr.IsNull(columnIndex)) { MatchFieldPairs[i].grField.Value = dr[columnIndex]; } } Report.DetailGrid.Recordset.Post(); } } /// <summary> /// List載入數據集 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="Report">報表對象</param> /// <param name="list">列表數據</param> public static void FillRecordToReport<T>(IGridppReport Report, List<T> list) { Type type = typeof(T); //反射類型 MatchFieldPairType[] MatchFieldPairs = new MatchFieldPairType[Math.Min(Report.DetailGrid.Recordset.Fields.Count, type.GetProperties().Length)]; //根據欄位名稱與列名稱進行匹配,建立欄位與Grid++Report記錄集的欄位之間的對應關係 int MatchFieldCount = 0; int i = 0; MemberInfo[] members = type.GetMembers(); foreach (MemberInfo memberInfo in members) { foreach (IGRField fld in Report.DetailGrid.Recordset.Fields) { if (string.Compare(fld.Name, memberInfo.Name, true) == 0) { MatchFieldPairs[MatchFieldCount].grField = fld; MatchFieldPairs[MatchFieldCount].MatchColumnIndex = i; ++MatchFieldCount; break; } } ++i; } // 將 DataTable 中的每一條記錄轉儲到 Grid++Report 的數據集中去 foreach (T t in list) { Report.DetailGrid.Recordset.Append(); for (i = 0; i < MatchFieldCount; ++i) { object objValue = GetPropertyValue(t, MatchFieldPairs[i].grField.Name); if (objValue != null) { MatchFieldPairs[i].grField.Value = objValue; } } Report.DetailGrid.Recordset.Post(); } } /// <summary> /// 獲取對象實例的屬性值 /// </summary> /// <param name="obj">對象實例</param> /// <param name="name">屬性名稱</param> /// <returns></returns> public static object GetPropertyValue(object obj, string name) { //這個無法獲取基類 //PropertyInfo fieldInfo = obj.GetType().GetProperty(name, bf); //return fieldInfo.GetValue(obj, null); //下麵方法可以獲取基類屬性 object result = null; foreach (PropertyDescriptor prop in TypeDescriptor.GetProperties(obj)) { if (prop.Name == name) { result = prop.GetValue(obj); } } return result; } }
綁定數據的報表效果如下所示
導出報表為PDF也是比較常規的操作,這個報表控制項也可以實現PDF等格式文件的導出,如下所示。
private void btnExportPdf_Click(object sender, EventArgs e) { List<ProductInfo> list = BLLFactory<Product>.Instance.GetAll(); //從對應文件中載入報表模板數據 string reportPath = Path.Combine(Application.StartupPath, "Reports\\testList.grf"); GridExportHelper helper = new GridExportHelper(reportPath); string fileName = "d:\\my.pdf"; var succeeded = helper.ExportPdf(list, fileName); if(succeeded) { Process.Start(fileName); } }
以上就是利用這個報表控制項做的一些功能測試和輔助類封裝,方便使用。