asp.net实现通用水晶报表

时间:2023-11-23 10:28:38

  此片博文是在你有一定水晶报表基础的前提下参阅的;如果对于水晶报表的基础知识比较薄弱建议先去了解下水晶报表;

  因为项目需要,研究了下水晶报表。说实在,这个组件很强大,但是用起来也很麻烦。刚开始使用遇到了老多问题。然后上了搜索引擎搜索。但是我发现,有一个很痛疼的问题。那就是现在搜索引擎搜索到的东西都是COPY的。大家到处复制别人的答案,却连测试都不测试就贴上了,然后一搜索出来的都是千篇一律的东西。要找到正确的解决方案真的是一件很痛苦的事。我不知道你们有没有这样的经理。反正我经常是这样。所以也萌生了自己写帖子的想法。把自己遇到的问题的解决方案写出来。但是因为水平有限,所以可能也会存在很多问题。希望大家多多谅解。多多提出;

  好了 进入今天的主题。所谓通用报表,我们来模拟一个需求:现在需要做个报表。这个报表要实现的功能是。配置一个配置文件,配置好要显示的数据表,和数据字段,配置好每个字段在报表中的显示宽度,配置好报表数据要按那个字段的什么排序方式排序;这样,我就只需要一个报表就能显示不同的表的数据,并且可以根据字段数据的长度设置报表中列的宽度;其实这里的通用也是有点牵强的。为什么牵强看下面的解决方案就知道了。

  解决方案:通用报表是一个很强大的组件。所以其实很多东西都是我们还为挖掘的。那么通用报表怎么实现呢。我的想法是。先在报表上放一堆公式字段,或者文本对象。然后通过代码,设置每个公式字段(报表的列)的宽度和左边偏移量;而要按照配置文件的需求则可以获取配置文件的条件,然后组合成为sql语句。获取数据填充到一个dataset中。在将这个dataset作为报表的数据源;就可以实现了;

  第一步:

  假设我的报表最多有10列;那么我需要创建一个DataSet.xsd文件。然后创建一张表;

  问题:为什么一定要创建这个文件,代码中创建一个DataSet可不可以?

  水晶报表在初始化的时候,需要关联已个数据源,这个数据源哪怕没有数据也没关系,关联数据源有两种方式。一是直接和数据库的表关联,二就是像我们这样创建一个DataSet.xsd文件,然后与这个DataSet.xsd文件关联;

  如果没有用任何一种关联的话,会提示:

  该报表不包含表。

  说明: 执行当前 Web 请求期间,出现未处理的异常。请检查堆栈跟踪信息,以了解有关该错误以及代码中导致错误的出处的详细信息。   

  异常详细信息: CrystalDecisions.CrystalReports.Engine.DataSourceException: 该报表不包含表。

  所以如果不创建创建DataSet文件,而是代码中创建一个DataSet,那么就没有关联数据源,会报如上错误;

  接着讲:新增项创建一个DataSet.xsd后,右键新增一张表:然后在表上右键新增列,完事后如下

  asp.net实现通用水晶报表

  然后与报表关联:

asp.net实现通用水晶报表

第二步:创建水晶报表;然后右键公式字段,新增公式字段。然后把公式字段放到报表上,最后结果如下。

asp.net实现通用水晶报表

然后右键每个公式字段,选择设置对象个数,修改里面的对象名称,这里的对象名称是代码要使用的。建议设置成如:colu1,colu2....colu10  这样在代码中可以用循环来找到每个公式字段!

这里的公式字段我是为每个设置类单线边框,样式可能不是很好看。你也可以自己画线。这个下面的字报表会讲到;

第三步:创建页面,然后在页面上拉个水晶报表的控件

asp.net实现通用水晶报表

创建完后 有人可能不想要左边的这个组树。水晶报表的版本不一样。可能设置也不一样。我这个是10的。设置方式是将下面的属性改为none;

asp.net实现通用水晶报表

asp.net实现通用水晶报表

水晶报表的工具栏很多也是可以去掉的,然后在右上角会有个微标,点击会跳到该组件的官网,可以通过设置属性:

asp.net实现通用水晶报表

  接着是创建配置文件

  

<?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 > )
{
tablename = NodeList[].InnerText;
title = NodeList[].Attributes["Title"].Value;
}
else
{
return;
}
NodeList = document.GetElementsByTagName("OrderBy");
if (NodeList != null && NodeList.Count > )
{
OrderKey = NodeList[].InnerText;
Order = NodeList[].Attributes["Order"].Value;
}
else
{
return;
}
NodeList = document.GetElementsByTagName("Field");
string fields = "";
if (NodeList != null && NodeList.Count > )
{
foreach (XmlNode node in NodeList)
{
if (node.InnerText != "")
{
fields += node.InnerText + ",";
}
}
}
else
{
return;
}
#endregion
if (fields.Length > )
{
fields = fields.Substring(, fields.Length - );
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 > && ds.Tables[].Rows.Count > )
{
ReportDocument myReport = new ReportDocument();
myReport.Load(reportPath);
DataTable dtx1 = new clsDyCrystalReportCore().dtx(ds.Tables[]);
int left = ;
#region 设置每列显示的数据和宽度
for (int i = ; i < NodeList.Count; i++)
{
//设置公式字段对应的数据库字段
myReport.DataDefinition.FormulaFields["colu" + (i + ).ToString()].Text = "{CryReport.colu" + (i + ) + "}";
//设置公式字段的文本
myReport.DataDefinition.FormulaFields["mt" + (i + ).ToString()].Text = "\"" + NodeList[i].Attributes["ChinesName"].Value + "\"";
//设置宽度和左边偏移量
FieldObject fo;
fo = (FieldObject)myReport.ReportDefinition.ReportObjects["colu" + (i + )];
//fo.Height = 567 * 3;
int width = int.Parse(NodeList[i].Attributes["Width"].Value);
fo.Width = width;
fo.Left = left;
fo.Top = ;
fo = (FieldObject)myReport.ReportDefinition.ReportObjects["mt" + (i + )];
//fo.Height = 567 * 3;
fo.Width = width;
fo.Left = left;
fo.Top = ;
left += width+; }
#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 < ; i++)
{
//myReport.DataDefinition.FormulaFields["mt" + (i + 1).ToString()].Text = "";
FieldObject fo;
fo = (FieldObject)myReport.ReportDefinition.ReportObjects["colu" + (i + )];
fo.ObjectFormat.EnableSuppress = true;
fo = (FieldObject)myReport.ReportDefinition.ReportObjects["mt" + (i + )];
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 = ; i < dt.Rows.Count; i++)
{
dtx1.Rows.Add(dtx1.NewRow()); for (int j = ; j < dt.Columns.Count; j++)
{
dtx1.Rows[i][j] = dt.Rows[i][j].ToString();
}
}
return dtx1;
} }

这里有个需要注意的是,如果你在报表关联的是DataSet.xsd文件,但是在个报表设置数据源的是你通过sql语句获取的dataset或这datatable,那么就会出现如下界面

asp.net实现通用水晶报表

我想你看到这个一定蒙了。你可能会去页面的报表控件去设置不需要提示连接数据库,但是当你设置成不需要后,在刷新就会出现这个错:

asp.net实现通用水晶报表

  这是因为你关联的是解决方案中的数据集文件,却用获取的数据表当作数据源,这里应该用你关联的数据集文件作为数据源,这样的话,每次提示或不提示数据库连接设置成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 = "";
return info;
}

这样就可以不需要每次都刷新页面填写数据库连接信息了。

该报表中,关键在于设置列宽和为列绑定要显示的数据:

在代码中如下:

//设置公式字段对应的数据库字段
myReport.DataDefinition.FormulaFields["colu" + (i + ).ToString()].Text = "{CryReport.colu" + (i + ) + "}";
//设置公式字段的文本
myReport.DataDefinition.FormulaFields["mt" + (i + ).ToString()].Text = "\"" + NodeList[i].Attributes["ChinesName"].Value + "\"";
//设置宽度和左边偏移量
FieldObject fo;
fo = (FieldObject)myReport.ReportDefinition.ReportObjects["colu" + (i + )];
//fo.Height = 567 * 3;
int width = int.Parse(NodeList[i].Attributes["Width"].Value);
fo.Width = width;
fo.Left = left;
fo.Top = ;
fo = (FieldObject)myReport.ReportDefinition.ReportObjects["mt" + (i + )];
//fo.Height = 567 * 3;
fo.Width = width;
fo.Left = left;
fo.Top = ;
left += width+;
"{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
后面会陆续贴一些帖子,会是各种知识点的。本人也处于一种学习状态,所以很多东西也还在摸索。希望公共探讨共同学习!!!!