asp.net实现通用水晶报表
此片博文是在你有一定水晶报表基础的前提下参阅的;如果对于水晶报表的基础知识比较薄弱建议先去了解下水晶报表;
因为项目需要,研究了下水晶报表。说实在,这个组件很强大,但是用起来也很麻烦。刚开始使用遇到了老多问题。然后上了搜索引擎搜索。但是我发现,有一个很痛疼的问题。那就是现在搜索引擎搜索到的东西都是COPY的。大家到处复制别人的答案,却连测试都不测试就贴上了,然后一搜索出来的都是千篇一律的东西。要找到正确的解决方案真的是一件很痛苦的事。我不知道你们有没有这样的经理。反正我经常是这样。所以也萌生了自己写帖子的想法。把自己遇到的问题的解决方案写出来。但是因为水平有限,所以可能也会存在很多问题。希望大家多多谅解。多多提出;
好了 进入今天的主题。所谓通用报表,我们来模拟一个需求:现在需要做个报表。这个报表要实现的功能是。配置一个配置文件,配置好要显示的数据表,和数据字段,配置好每个字段在报表中的显示宽度,配置好报表数据要按那个字段的什么排序方式排序;这样,我就只需要一个报表就能显示不同的表的数据,并且可以根据字段数据的长度设置报表中列的宽度;其实这里的通用也是有点牵强的。为什么牵强看下面的解决方案就知道了。
解决方案:通用报表是一个很强大的组件。所以其实很多东西都是我们还为挖掘的。那么通用报表怎么实现呢。我的想法是。先在报表上放一堆公式字段,或者文本对象。然后通过代码,设置每个公式字段(报表的列)的宽度和左边偏移量;而要按照配置文件的需求则可以获取配置文件的条件,然后组合成为sql语句。获取数据填充到一个dataset中。在将这个dataset作为报表的数据源;就可以实现了;
第一步:
假设我的报表最多有10列;那么我需要创建一个DataSet.xsd文件。然后创建一张表;
问题:为什么一定要创建这个文件,代码中创建一个DataSet可不可以?
水晶报表在初始化的时候,需要关联已个数据源,这个数据源哪怕没有数据也没关系,关联数据源有两种方式。一是直接和数据库的表关联,二就是像我们这样创建一个DataSet.xsd文件,然后与这个DataSet.xsd文件关联;
如果没有用任何一种关联的话,会提示:
该报表不包含表。
说明: 执行当前 Web 请求期间,出现未处理的异常。请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的出处的详细信息。
异常详细信息: CrystalDecisions.CrystalReports.Engine.DataSourceException: 该报表不包含表。
所以如果不创建创建DataSet文件,而是代码中创建一个DataSet,那么就没有关联数据源,会报如上错误;
接着讲:新增项创建一个DataSet.xsd后,右键新增一张表:然后在表上右键新增列,完事后如下
然后与报表关联:
第二步:创建水晶报表;然后右键公式字段,新增公式字段。然后把公式字段放到报表上,最后结果如下。
然后右键每个公式字段,选择设置对象个数,修改里面的对象名称,这里的对象名称是代码要使用的。建议设置成如:colu1,colu2....colu10 这样在代码中可以用循环来找到每个公式字段!
这里的公式字段我是为每个设置类单线边框,样式可能不是很好看。你也可以自己画线。这个下面的字报表会讲到;
第三步:创建页面,然后在页面上拉个水晶报表的控件
创建完后 有人可能不想要左边的这个组树。水晶报表的版本不一样。可能设置也不一样。我这个是10的。设置方式是将下面的属性改为none;
水晶报表的工具栏很多也是可以去掉的,然后在右上角会有个微标,点击会跳到该组件的官网,可以通过设置属性:
接着是创建配置文件
<?xml version="1.0" encoding="utf-8" ?> <ConfigManage> <Config ID="1"> <TableName Title="系统日志报表">SYS_Log</TableName> <OrderBy Order="desc">logid</OrderBy> <Fields> <Field ChinesName="编号" IsShow="true" Width="1800">logid</Field> <Field ChinesName="日志类别" IsShow="true" Width="1800">logtype</Field> <Field ChinesName="用户编号" IsShow="true" Width="1800">userid</Field> <Field ChinesName="日志内容" IsShow="true" Width="1800">logcontent</Field> <Field ChinesName="操作用户名" IsShow="true" Width="1800">username</Field> <Field ChinesName="操作用户名" IsShow="true" Width="1800">username</Field> </Fields> </Config> </ConfigManage>
然后就是为报表设置数据源和控制列宽的代码了。
/// <summary> /// 获取配置文件的配置信息配置报表 /// </summary> public void GetData() { string Path = ""; string reportPath = ""; string tablename = "";//表名称 string OrderKey = "";//排序的字段 string Order = "";//按什么排序 string title = "";//报表标题 bool Separate = true;//显示页是分页还是连接 string serch = "";//筛选条件 Path = Server.MapPath("Config/ReportConfig.xml"); reportPath = Server.MapPath("CrystalReport3.rpt"); #region 获取配置文件的信息 XmlDocument document = new XmlDocument(); document.Load(Path); XmlNodeList NodeList = document.GetElementsByTagName("TableName"); if (NodeList != null && NodeList.Count > 0) { tablename = NodeList[0].InnerText; title = NodeList[0].Attributes["Title"].Value; } else { return; } NodeList = document.GetElementsByTagName("OrderBy"); if (NodeList != null && NodeList.Count > 0) { OrderKey = NodeList[0].InnerText; Order = NodeList[0].Attributes["Order"].Value; } else { return; } NodeList = document.GetElementsByTagName("Field"); string fields = ""; if (NodeList != null && NodeList.Count > 0) { foreach (XmlNode node in NodeList) { if (node.InnerText != "") { fields += node.InnerText + ","; } } } else { return; } #endregion if (fields.Length > 0) { fields = fields.Substring(0, fields.Length - 1); string sql = "select " + fields + " from " + tablename + " " + serch + " order by " + OrderKey + " " + Order; DataSet ds = new Maticsoft.BLL.CommonClass().Query(sql); if (ds != null && ds.Tables.Count > 0 && ds.Tables[0].Rows.Count > 0) { ReportDocument myReport = new ReportDocument(); myReport.Load(reportPath); DataTable dtx1 = new clsDyCrystalReportCore().dtx(ds.Tables[0]); int left = 100; #region 设置每列显示的数据和宽度 for (int i = 0; i < NodeList.Count; i++) { //设置公式字段对应的数据库字段 myReport.DataDefinition.FormulaFields["colu" + (i + 1).ToString()].Text = "{CryReport.colu" + (i + 1) + "}"; //设置公式字段的文本 myReport.DataDefinition.FormulaFields["mt" + (i + 1).ToString()].Text = "\"" + NodeList[i].Attributes["ChinesName"].Value + "\""; //设置宽度和左边偏移量 FieldObject fo; fo = (FieldObject)myReport.ReportDefinition.ReportObjects["colu" + (i + 1)]; //fo.Height = 567 * 3; int width = int.Parse(NodeList[i].Attributes["Width"].Value); fo.Width = width; fo.Left = left; fo.Top = 30; fo = (FieldObject)myReport.ReportDefinition.ReportObjects["mt" + (i + 1)]; //fo.Height = 567 * 3; fo.Width = width; fo.Left = left; fo.Top = 760; left += width+100; } #endregion HideColu(NodeList.Count, myReport); myReport.DataDefinition.FormulaFields["title"].Text = "\"" + title + "\""; //myReport.Database.Tables[0].ApplyLogOnInfo(GetConnectionInfo());//设置报表数据库连接信息 myReport.SetDataSource(dtx1); CrystalReportViewer1.SeparatePages = Separate; CrystalReportViewer1.ReportSource = myReport; CrystalReportViewer1.DataBind(); CrystalReportViewer1.RefreshReport(); } } }
GetData()方法的前报部分基本都是获取配置文件中的信息,然后组合成一个sql语句,然后通过ado.net执行获取数据
隐藏不显示的列的方法
/// <summary> /// 将不显示的列隐藏 /// </summary> /// <param name="StarCulu"></param> /// <param name="myReport"></param> private void HideColu(int StarCulu, ReportDocument myReport) { for (int i = StarCulu; i < 10; i++) { //myReport.DataDefinition.FormulaFields["mt" + (i + 1).ToString()].Text = ""; FieldObject fo; fo = (FieldObject)myReport.ReportDefinition.ReportObjects["colu" + (i + 1)]; fo.ObjectFormat.EnableSuppress = true; fo = (FieldObject)myReport.ReportDefinition.ReportObjects["mt" + (i + 1)]; fo.ObjectFormat.EnableSuppress = true; ///TODO 此处少了个抑制显示的代码 } }
然后调用下面的类的方法,将获取的数据放入前面创建的DataSet.xsd文件的表中;
class clsDyCrystalReportCore { /// <summary> /// 将传入的datatable转换成报表模板所需要的datatable /// 数据全部转换为string /// </summary> /// <param name="dt">来源表</param> /// <returns>报表模板所需要的datatable</returns> public DataTable dtx(DataTable dt) { DataSet1.CryReportDataTable dtx1 = new DataSet1.CryReportDataTable(); object[] obj = new object[dt.Columns.Count]; //特别注意:所选择的表的列的数目需<=Bigtable的字段数目 //请自行填写保护代码 for (int i = 0; i < dt.Rows.Count; i++) { dtx1.Rows.Add(dtx1.NewRow()); for (int j = 0; j < dt.Columns.Count; j++) { dtx1.Rows[i][j] = dt.Rows[i][j].ToString(); } } return dtx1; } }
这里有个需要注意的是,如果你在报表关联的是DataSet.xsd文件,但是在个报表设置数据源的是你通过sql语句获取的dataset或这datatable,那么就会出现如下界面
我想你看到这个一定蒙了。你可能会去页面的报表控件去设置不需要提示连接数据库,但是当你设置成不需要后,在刷新就会出现这个错:
这是因为你关联的是解决方案中的数据集文件,却用获取的数据表当作数据源,这里应该用你关联的数据集文件作为数据源,这样的话,每次提示或不提示数据库连接设置成true或false都无所谓了。但是如果是关联的是数据库中的表,设置数据源的时候也是用sql取得的数据表。那么要去掉每次提示连接数据库,除了这只这个属性还得这是报表的数据库连接信息
设置方式
myReport.Database.Tables[0].ApplyLogOnInfo(GetConnectionInfo());//设置报表数据库连接信息
/// <summary> /// 返回设置报表对象的数据库信息的对象 /// </summary> /// <returns></returns> private TableLogOnInfo GetConnectionInfo() { TableLogOnInfo info = new TableLogOnInfo(); info.ConnectionInfo.ServerName = "192.168.2.1"; info.ConnectionInfo.DatabaseName = "dad"; info.ConnectionInfo.UserID = "sa"; info.ConnectionInfo.Password = "123456"; return info; }
这样就可以不需要每次都刷新页面填写数据库连接信息了。
该报表中,关键在于设置列宽和为列绑定要显示的数据:
在代码中如下:
//设置公式字段对应的数据库字段 myReport.DataDefinition.FormulaFields["colu" + (i + 1).ToString()].Text = "{CryReport.colu" + (i + 1) + "}"; //设置公式字段的文本 myReport.DataDefinition.FormulaFields["mt" + (i + 1).ToString()].Text = "\"" + NodeList[i].Attributes["ChinesName"].Value + "\""; //设置宽度和左边偏移量 FieldObject fo; fo = (FieldObject)myReport.ReportDefinition.ReportObjects["colu" + (i + 1)]; //fo.Height = 567 * 3; int width = int.Parse(NodeList[i].Attributes["Width"].Value); fo.Width = width; fo.Left = left; fo.Top = 30; fo = (FieldObject)myReport.ReportDefinition.ReportObjects["mt" + (i + 1)]; //fo.Height = 567 * 3; fo.Width = width; fo.Left = left; fo.Top = 760; left += width+100;
"{CryReport.colu" + (i + 1) + "}":这句是为公式字段关联要显示的数据,格式为表名加字段,这里的表名是DataSet.xsd中的表的名称和列的名称。而不是sql语句获取的表的名称和字段名称
int width = int.Parse(NodeList[i].Attributes["Width"].Value):这句是获取配置文件中的列宽,然后设置给相应的列,列的偏移量都是从报表的最左端算起的。所以每个公式字段的偏移量要是他前面的公式字段的宽度的和;
在这里,有一个需要注意的是:数据表返回的数据必须的是字符串类型的。否则当公式字段类型(如数字)和数据表中字段的类型不一样(如字符串)那么在设置列宽和偏移量的时候会报错。所以DataSet.xsd中的表的字段和报表上的公式字段都设置为字符串类型,就不会出现这种错误了;
最后:将不现实的列隐藏掉:
FieldObject fo; fo = (FieldObject)myReport.ReportDefinition.ReportObjects["colu" + (i + 1)]; fo.ObjectFormat.EnableSuppress = true;
通过设置EnableSuppress 属性,可以将公式字段设置为抑制显示,就可以隐藏掉了
因为代码都是写完的,然后要去模拟错误,找错误信息,所以导致写的有点乱,有什么不懂的可以问我。我之所以没把源码贴出来,是我觉得 coding其实是一件很开心的事,特别是当自己通过搜索资料和努力实现了一个功能,那种心情只有自己明白,所以我希望大家可以照着帖子做一下。实在不行可以问我我的qq:282338159
后面会陆续贴一些帖子,会是各种知识点的。本人也处于一种学习状态,所以很多东西也还在摸索。希望公共探讨共同学习!!!!