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();
                }
            }

        }                
View Code

  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;
        }

    }
View Code

这里有个需要注意的是,如果你在报表关联的是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
后面会陆续贴一些帖子,会是各种知识点的。本人也处于一种学习状态,所以很多东西也还在摸索。希望公共探讨共同学习!!!!

 

posted @ 2013-09-13 14:53  虾米瓦格  阅读(2800)  评论(0编辑  收藏  举报