.net Crystal Report dynamic columns

 

前言: 

聽說Crystal Report 官方已經很久沒有維護了
公司在用的版本 也都找不到文件 ((而且公司用的是VB
想到頭就好痛QAQ

正文:

最近在寫一個需求
有一個可選欄位(動態)的Table 要用CR呈現資料
想呈現的樣子如圖:

但使用者可以任意選擇需要的欄位例如:

且單個欄位資料可能有多行
如果用visual studio設計工具直接拉 可能很難達成以上需求
如果全部用 RAS (Report Application Server) API 寫是可以達到目的
但用程式layout 也是有些麻煩

於是我的腦袋瓜想到把兩個摻在一起做撒尿牛丸
把元件排用設計工具layout完成後
在用RAS API動態設定欄位

在開始之前先說明一下
本文使用環境為
.Net Framework 4.7.2
Crystal Report for .Net 13.0.4000 

UI大概長這樣

資料會用System.DataSet傳給報表
所以我們要新增資料集.xsd檔
設計如下:

我們有 test 這個table , 裡面有 4 個欄位分別為 col1~4, 型別都是 string
接著到report的設計頁面用資料庫專家把他加入
然後把report的畫面拉成下圖


接著回到winform
我們想要按下generate後
根據CheckedListBox勾選的內容
動態調整欄位長寬位置等
部分程式如下
const int PAGE_MARGIN = 400;
private void btnGenerate_Click(object sender, EventArgs e)
{
//process data
DataSet data = new DataSet();
DataTable table = new DataTable("test");
string[] rowData = new string[] { "line1", "line1\nline2", "line1\nline2\nline3", "line1\nline2\nline3\nline4" };
if (cbl_col.CheckedItems.Count < 1)
{
MessageBox.Show("Please ensure that you have chosen at least 1 item.");
return;
}
foreach (string name in cbl_col.Items)
{
table.Columns.Add(name);
}
for (int i = 0; i < rowData.Length; i++)
{
table.Rows.Add(rowData[i], rowData[(i + 1) % 4], rowData[(i + 2) % 4], rowData[(i + 3) % 4]);
}
data.Tables.Add(table);
//load rpt
ReportDocument rpt = new ReportDocument();
rpt.Load(".\\test.rpt");
//set data source
rpt.SetDataSource(data);
//do setting columns dynamically
//get page width
int width = rpt.PrintOptions.PageContentWidth - PAGE_MARGIN;
//we set each columns' weight to 1 here
int totalWeight = cbl_col.CheckedItems.Count;
//now, we want to remove columns that user isn't select
//so we get the Enumerator of Section which the Section name is 'Section2' & 'Section3'
IEnumerator section2Enumerator = rpt.ReportDefinition.Sections["Section2"].ReportObjects.GetEnumerator();
IEnumerator section3Enumerator = rpt.ReportDefinition.Sections["Section3"].ReportObjects.GetEnumerator();
//walk through & remove unused columns
while (section2Enumerator.MoveNext())
{
if (section2Enumerator.Current == null) continue;
//remove ReportObjects if the name equals database(or data definition) column name
if (!cbl_col.CheckedItems.Contains((section2Enumerator.Current as ReportObject).Name.Replace("txt_", "")))
{
CrystalDecisions.ReportAppServer.ReportDefModel.ISCRReportObject obj = rpt.ReportClientDocument.ReportDefController.ReportObjectController
.GetAllReportObjects()[(section2Enumerator.Current as ReportObject).Name];
rpt.ReportClientDocument.ReportDefController.ReportObjectController.Remove(obj);
}
}
//do the same thing with Section3
while (section3Enumerator.MoveNext())
{
if (section3Enumerator.Current == null) continue;
//remove ReportObjects if the name equals database(or data definition) column name
if (!table.Columns.Contains((section3Enumerator.Current as ReportObject).Name.Replace("fd_", "")))
{
CrystalDecisions.ReportAppServer.ReportDefModel.ISCRReportObject obj = rpt.ReportClientDocument.ReportDefController.ReportObjectController
.GetReportObjectsByKind(CrystalDecisions.ReportAppServer.ReportDefModel.CrReportObjectKindEnum.crReportObjectKindField)[(section3Enumerator.Current as ReportObject).Name];
rpt.ReportClientDocument.ReportDefController.ReportObjectController.Remove(obj);
}
}
//if we set border in design tool, assume there is data that has different lines between each other
//you were found that vertical(left & right) border height is different between each columns
//to solve the problem, we need to use line object and set 'ExtendToBottomOfSection' to true
//and because our columns are dynamic, we also need to add line object dynamically
//get Section3 as ReportDefModel.Section, we're able to add LineObject later
CrystalDecisions.ReportAppServer.ReportDefModel.Section section3 = rpt.ReportClientDocument.ReportDefController.ReportDefinition.FindSectionByName("Section3");
//draw line & set columns position
int last = 50;
CrystalDecisions.ReportAppServer.ReportDefModel.LineObject startLine = new CrystalDecisions.ReportAppServer.ReportDefModel.LineObject();
startLine.LineThickness = 20;
startLine.LineColor = 0x0;
startLine.LineStyle = CrystalDecisions.ReportAppServer.ReportDefModel.CrLineStyleEnum.crLineStyleSingle;
startLine.EnableExtendToBottomOfSection = true;
startLine.Left = last;
startLine.Right = last;
startLine.Bottom = 254;
startLine.Top = 0;
startLine.SectionName = section3.Name;
startLine.EndSectionName = section3.Name;
startLine.SectionCode = section3.SectionCode;
rpt.ReportClientDocument.ReportDefController.ReportObjectController.Add(startLine, section3);
last += 50;
for (int i = 0; i < cbl_col.CheckedItems.Count; i++) {
int mWidth = width/totalWeight;
TextObject mTxt = rpt.ReportDefinition.Sections["Section2"].ReportObjects["txt_" + cbl_col.CheckedItems[i]] as TextObject;
FieldObject mDt = rpt.ReportDefinition.Sections["Section3"].ReportObjects["fd_" + cbl_col.CheckedItems[i]] as FieldObject;
CrystalDecisions.ReportAppServer.ReportDefModel.LineObject mLine = new CrystalDecisions.ReportAppServer.ReportDefModel.LineObject();
mLine.LineThickness = 20;
mLine.LineColor = 0x0;
mLine.LineStyle = CrystalDecisions.ReportAppServer.ReportDefModel.CrLineStyleEnum.crLineStyleSingle;
mLine.EnableExtendToBottomOfSection = true;
mTxt.Left = last;
mDt.Left = last;
mTxt.Width = mWidth;
mDt.Width = mWidth;
mLine.Left = last + mWidth;
mLine.Right = last + mWidth;
mLine.Bottom = 211;
mLine.Top = 0;
last = last + mTxt.Width + 45;
mLine.SectionName = section3.Name;
mLine.EndSectionName = section3.Name;
mLine.SectionCode = section3.SectionCode;
rpt.ReportClientDocument.ReportDefController.ReportObjectController.Add(mLine, section3);
}
rpt.Refresh();
crystalReportViewer1.ReportSource = rpt;
crystalReportViewer1.Refresh();
}
view raw Form1.cs hosted with ❤ by GitHub



執行結果如下

附上github連結
https://github.com/aps32221/CRDynamicMultipleColumn
有疑問或錯誤的話歡迎指教


留言

這個網誌中的熱門文章

[android]QR code掃描