本文是在<动态水晶报表:任意表,任意列,以及动态格线>
http://www.cnblogs.com/babyt/archive/2009/04/08/1431328.html
原理基础上的一个扩展。
如果你之前没有看过此文,请务必仔细研读。否则本文你可能不能较快地理解。
本文适用场景:
出于显示效果或者打印节约纸张等的需要,将多个不同结构(设置来源于不同数据源)的表在同一个报表中展现。
一般我们可以通过子报表来实现这个效果。
不过一般一个子报表对应一个表,这样对于多个表操作起来就比较麻烦。
每个表都要做一个子报表,对于动态取表(表数目,表名称)的要求也满足不了。
虽然也可以借助SDK,对不同的表,动态增加一个子报表,但是代码量很大。
基本思路:
既然我们延续上文的原理,那么本方案的主要问题就是,如何把这任意多的表,塞到同一个datatable 里去
1:
我们同样要构造一个datatable来容纳我们的表数据。假设我们的表的最大列数是6,
那么我们需要创建一个7个列的datatable。
为什么上文是6个,这里要7个呢?
多出来的的这个字段f0,我们用来放表的名称。这样来区分数据是来源于哪个表的。
这样我们就创建了f0,f1~f6,共7个String行的datatable.
2:
好了,我们来改造上一个例子,模板基本上一样。只是多了个字段f0。但是界面上这个f0是不显示的,所以我们仍然用6个格子。
需要注意的是,有点不同:就是页眉上不再显示标头了,为什么这样做,下面会讲到。
3:
然后,来改造我们的核心方法
每个表的数据在写入前,先写一行列标题。这个标题,也替代了我们之前的页眉标题。
并且每一行数据的第一列,也就是f0,写入表名。
-
C# code
-
class clsDyCrystalReportCore { /// <summary> /// 将传入的datatable转换成报表模板所需要的datatable /// 数据全部转换为string /// </summary> /// <param name="dt"> 来源表 </param> /// <param name="tblName"> 各单表名称 </param> /// <param name="fldsName"> 字段名称,以半角逗号分隔。 </param> /// <returns> 报表模板所需要的datatable </returns> public DataTable dtx(DataTable dt,String tblName,String fldsName) { String oneRow = "" ; DataSet1.BigTatableDataTable dtx1 = new DataSet1.BigTatableDataTable(); object [] obj = new object [dt.Columns.Count]; // 特别注意:所选择的表的列的数目需<=Bigtable的字段数目 // 请自行填写保护代码 // 先列名称写到每个表的第一行 dtx1.Rows.Add(dtx1.NewRow()); // 第一行的第一列写表的名称 dtx1.Rows[ 0 ][ 0 ] = tblName; // 切割列名称字符串,写入第一行后面的位置 for ( int i = 0 ; i < fldsName.Split( new char [] { ' , ' }).Length; i ++ ) { dtx1.Rows[ 0 ][i + 1 ] = fldsName.Split( new char [] { ' , ' })[i]; } // 写入数据 for ( int i = 0 ; i < dt.Rows.Count ; i ++ ) { dtx1.Rows.Add(dtx1.NewRow()); // 每一行的第一列,也就是f0,写入表名称。 dtx1.Rows[i + 1 ][ 0 ] = tblName; // 写数据 for ( int j = 0 ; j < dt.Columns.Count ; j ++ ) { oneRow = oneRow + " , " + dt.Rows[i][j].ToString(); if (dt.Rows[i][j].ToString() == "" ) dtx1.Rows[i + 1 ][j + 1 ] = " " ; else dtx1.Rows[i + 1 ][j + 1 ] = dt.Rows[i][j].ToString(); } } return dtx1; } }
4:
好了,来看我们的前端代码。
-
C# code
-
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Text; using System.Windows.Forms; using CrystalDecisions.Shared; using CrystalDecisions.CrystalReports.Engine; using CrystalDecisions.Windows.Forms; using System.Data.OleDb; namespace DyCrystalReportDemo { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click( object sender, EventArgs e) { String connstr = " Provider=Microsoft.Jet.OLEDB.4.0;Data Source= " + System.Threading.Thread.GetDomain().BaseDirectory + " bbtcrall.mdb " + " ; " ; // 打开数据库连接 OleDbConnection cn = new OleDbConnection(connstr); DataTable dtxAll = new DataTable(); // 容纳所有数据 OleDbDataAdapter da = new OleDbDataAdapter(); clsDyCrystalReportCore xCore = new clsDyCrystalReportCore(); // 注意各表的字段数目不能大于我们设定的最大数目! // 请自行添加错误保护和对象释放代码 // 第1个表 DataTable dt1 = new DataTable(); DataTable dtx1 = new DataTable(); da = new OleDbDataAdapter( " SELECT * From Test1_1 " , cn); da.Fill(dt1); dtx1 = xCore.dtx(dt1, " 表1 " , " 编号,姓名,发信日期,其他 " ); dtxAll.Merge(dtx1); // 第2个表,注意,这个表可以从不同的数据源获取! // 重新初始化 dt1 = new DataTable(); dtx1 = new DataTable(); da = new OleDbDataAdapter( " SELECT * From Test1_2 " , cn); da.Fill(dt1); dtx1 = xCore.dtx(dt1, " 表2 " , " 编号,姓名,入职日期,日期1,日期2 " ); dtxAll.Merge(dtx1); // 第3个表,注意,这个表可以从不同的数据源获取! // 重新初始化 dt1 = new DataTable(); dtx1 = new DataTable(); da = new OleDbDataAdapter( " SELECT * From Test_4 " , cn); da.Fill(dt1); dtx1 = xCore.dtx(dt1, " 表3 " , " 年份,地区,指标,最大值,最小值 " ); dtxAll.Merge(dtx1); ReportDocument myReport = new ReportDocument(); string reportPath = System.Threading.Thread.GetDomain().BaseDirectory + " crystalreport1.rpt " ; myReport.Load(reportPath); // 绑定数据集 myReport.SetDataSource(dtxAll); crystalReportViewer1.ReportSource = myReport; crystalReportViewer1.RefreshReport(); } } }
注意,我们用了
dtxAll.Merge(dtx1);
将所有的数据合并在一个datatable里,从而达到我们开始设定的目标。
5:
执行一下,是如下效果,有点乱,是吧?
6:
这个时候f0就派上用场啦,在模板上增加一个f0的组,如下图所示。
7:
运行起来,帅多了吧?
格子有点难看,这个就不再本文中讨论了。以后专门讲讲这个画格子的问题。