65.9K
CodeProject 正在变化。 阅读更多。
Home

快速将报告添加到你的网站

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.74/5 (20投票s)

2007年7月18日

CPOL

11分钟阅读

viewsIcon

134827

downloadIcon

2284

演示如何将 Crystal Reports 报表添加到 ASP.NET 页面,包括分组、筛选和钻取功能。

Screenshot - CrystalReportIntro.gif

引言

每当网站在数据库中存储数据时,您都会希望对这些数据运行报表,以寻找改进客户服务、检测滥用等方法。

幸运的是,Visual Studio 附带了一个功能强大的报表生成器——Crystal Reports,它是免费包含的!一旦您学会了如何使用它,它就能让您快速创建显示各种报表的网页。它的众多功能包括分组、排序、筛选、图表、导出为 PDF 和 Excel 文件等。然而,弄清楚如何利用这些功能可能有点棘手。

本文将一步步展示如何使用 Crystal Reports 将报表添加到网页。您将看到如何以编程方式应用分组和筛选,以及如何提供从组别总计到支持详细记录的钻取功能。

运行代码所需

  • Visual Studio 2005(也可能适用于 Visual Studio 2003,但未在此版本中进行测试)。
  • 您需要将 Crystal Reports 作为 Visual Studio 的一部分进行安装。要查看是否已安装,请在 Visual Studio 中添加新项——Crystal Report 应包含在模板列表中。如果不存在,请从 Visual Studio 安装中进行安装。
  • 可以访问 SQL Server 2005、SQL Server 2000 或 SQL Server Express 服务器,并能够将数据库还原到服务器以及创建数据库用户。

安装代码

  1. 下载源代码并解压缩到某个目录。
  2. 您将找到一个简单的 ASP.NET 2.0 网站的源代码,以及一个数据库备份文件,名为 CrystalReportIntro.bak

本文将引导您创建网站,因此网站源代码仅为方便起见。但是,您确实需要安装数据库。

  1. CrystalReportIntro.bak 还原到您的数据库服务器,数据库名称为 "CrystalReportIntro"。
  2. 创建一个登录名为 "mp",密码为 "crystal2007" 的登录。
  3. 在数据库 "CrystalReportIntro" 上,创建一个基于登录 "mp" 的用户 "mp"。
  4. 查看数据库。您会发现它只有一个名为 "Sales" 的表,包含简单的销售信息,以及一个名为 "vSales" 的视图——基于 "Sales" 表——该视图添加了一个计算列,用于汇总销售额。报表将基于此视图。

一、创建空白网站

创建一个新的 ASP.NET 网站,语言选择 Visual C#。

二、创建 Sales.rpt 定义文件

Sales.rpt 文件将保存报表的定义。稍后,您将看到如何将该定义部署到您的一个网页上。

  1. 向网站添加新项。
  2. 在模板列表中,选择 Crystal Report。
  3. 将其命名为 Sales.rpt,然后单击添加。
  4. 如果出现注册弹出窗口,请将其关闭。
  5. 在 Crystal Reports Gallery 弹出窗口中,选择使用报表向导和 Standard expert。单击确定。
  6. 在接下来的页面上,展开 "Create new connection"(创建新连接)部分。
  7. 然后,展开 "OLE DB (ADO)" 部分。将弹出一个对话框。
  8. 选择 "SQL Native Client" 作为您的提供程序。将 "Use Data Link File"(使用数据链接文件)保持未选中状态。单击下一步。
  9. 输入您的数据库服务器名称。将用户名设置为 "mp",密码设置为 "crystal2007",数据库名称设置为 "CrystalReportIntro"。
  10. 还有一页,但其中包含我们不需要的高级内容。单击完成。
  11. 现在您回到了报表创建向导。它允许您钻取到刚才添加的数据库服务器和数据库,以及数据库中的表、视图等。
  12. 一直钻取直到看到名为 "vSales" 的视图。将其移到右侧的字段区域。单击下一步。

  13. 现在您可以选择要在报表中显示的字段。
  14. 单击 "vSales" 旁边的加号,查看各个字段,并将所有字段移到右侧。但是,您不需要 ID 字段,所以将其移回左侧。单击下一步。

  15. 此页面允许您按特定字段进行分组。报表将包含分组,并显示每个分组的销售总计。为了让 Crystal Reports 立即添加分组,请指定按某个字段分组。稍后,您将看到如何动态更改 C# 代码中的分组字段。
  16. 将 "ProductName" 字段(位于 Report Fields 下)移到右侧框中。单击下一步。

  17. 现在它允许您指定汇总。为了提供帮助,Crystal Reports 已经为您指定了一些字段。但在此示例中,只需对 Total 字段进行求和。
  18. 将右侧框中的所有字段(除了 Total 字段)移回左侧。单击下一步。

  19. 连续单击三次“下一步”,跳过“组排序”、“图表”和“记录选择”页面。
  20. 选择您喜欢的报表样式。我个人倾向于选择 "Red/Blue Border"(红/蓝边框)样式。单击完成。
  21. 报表定义文件 Sales.rpt 现在应该已打开,显示您的报表。

三、将报表添加到 Default.aspx 页面

既然我们已经有了报表定义,就将其添加到 ASP.NET 页面上。

  1. 在设计模式下打开 Default.aspx
  2. 展开工具箱中的 Crystal Reports 部分。
  3. 将一个 CrystalReportViewer 控件拖放到页面上。您会看到它添加了 CrystalReportViewer 控件。
  4. <CR:CrystalReportViewer ID="CrystalReportViewer1" 
        runat="server" AutoDataBind="true" />
  5. 按 F5 运行 Default.aspx。您只会看到一个空白页面。需要添加一些代码才能将 Sales.rpt 文件绑定到页面上的 CrystalReportViewer 控件。
  6. 首先,向 Default.aspx.cs 添加几个支持 Crystal Reports 的程序集。
  7. using CrystalDecisions.CrystalReports.Engine;
    using CrystalDecisions.Shared;
    
    public partial class _Default : System.Web.UI.Page 
    {
  8. 添加一个名为 CreateCrystalReportDocument 的方法,该方法构建一个 ReportDocument 对象。正是这个对象将告诉页面上的 CrystalReportViewer 控件在哪里找到报表定义、使用什么记录选择和分组等。
  9. public partial class _Default : System.Web.UI.Page 
    {
        private const string sourceTableOrView = "vSales";
    
        // -------------------------------------
    
        //
    
        private ReportDocument CreateCrystalReportDocument(
                                    string filterString,
                                    string groupFieldName)
        {
            ReportDocument rpt = new ReportDocument();
    
            // ---- Load the report definition
    
            string reportPath = Server.MapPath("Sales.rpt");
            rpt.Load(reportPath);
    
            // ---- Assign connection information for each table in the database
    
            // Build connection info
    
            ConnectionInfo connectionInfo = new ConnectionInfo();
            connectionInfo.ServerName = @"YOUR DATABASE SERVER NAME";
            connectionInfo.DatabaseName = "CrystalReportIntro";
            connectionInfo.UserID = "mp";
            connectionInfo.Password = "crystal2007";
    
            // Assign to all tables used by the report
    
            Tables tables = rpt.Database.Tables;
            foreach (CrystalDecisions.CrystalReports.Engine.Table table in tables)
            {
                TableLogOnInfo tableLogonInfo = table.LogOnInfo;
                tableLogonInfo.ConnectionInfo = connectionInfo;
                table.ApplyLogOnInfo(tableLogonInfo);
            }
    
            // ---- Set the record selection. If filterString
            //      is null, all records will be selected.
    
            rpt.DataDefinition.RecordSelectionFormula = filterString;
    
            // ---- Set grouping field
    
    
            // We'll set the first level of grouping (group 0)
    
            Group group = rpt.DataDefinition.Groups[0];
    
            // We'll be grouping on a field in vSales
            // ("vSales" is the value of sourceTableOrView)
    
            CrystalDecisions.CrystalReports.Engine.Table groupFieldTable = 
                             rpt.Database.Tables[sourceTableOrView];
    
            // Assign the field whose name is passed
            // into this function via parameter groupFieldName
    
            group.ConditionField = groupFieldTable.Fields[groupFieldName];
    
            return rpt;
        }

    不要忘记将字符串 "YOUR DATABASE SERVER NAME" 替换为您的数据库服务器名称。

    在实际应用程序中,您会希望将此结构稍作改进,提取方法等。这里的代码结构便于理解。

    1. CreateCrystalReportDocument 首先加载 Sales.rpt 文件中包含的报表定义。
    2. 然后它会告知报表如何连接到数据库。Crystal Reports 报表为每个表都有连接信息——这样,您的报表就可以显示来自不同数据库的数据。因此,代码会循环遍历报表使用的所有表以分配连接信息。
    3. 然后它会设置记录选择字符串,该字符串通过参数 filterString 传递给函数。稍后,您将看到这基本上是一个 SQL WHERE 子句。
    4. 最后,它设置用于对数据进行分组的字段,该字段通过参数 groupFieldName 传递给函数。代码保持简单,只设置了一个分组级别(组 0)。并且,它假设分组字段始终来自我们的视图 "vSales"。可以轻松实现其他分组级别,并使用来自不同表的字段。
  10. 最后,在 Page_Init 方法中,将 CreateCrystalReportDocument 创建的 ReportDocument 对象分配给 CrystalReportViewer 控件。将 filterString 传递为 null(以显示所有记录而不进行筛选),并将 ProductName 作为分组字段。稍后,我们将对此做一些更有用的事情。
  11.     private void Page_Init(object sender, EventArgs e)
        {
            ReportDocument rpt = null;
    
            rpt = CreateCrystalReportDocument(null, "ProductName");
            CrystalReportViewer1.ReportSource = rpt;
        }

    由于 Crystal Report 软件的技术问题,它需要放在 Page_Init 而不是 Page_Load 中。

  12. 再次按 F5 运行您的站点。默认页面将显示报表,按产品名称分组。
  13. 单击灰色条中左侧的导出按钮(在打印按钮旁边)。您会发现现在可以将报表导出为多种格式,包括 PDF 和 Excel 文件。很酷吧?

四、添加动态筛选和分组

  1. 首先,通过向 Default.aspx 添加多个输入字段来设置分组和筛选,从而奠定基础。
  2.     <form id="form1" runat="server">
        <div>
        
        <p>
            Filter
            <blockquote>
            <table>
            <tr><td>Product Name:</td>
            <td><asp:TextBox ID="tbProductName" runat="server" /></td></tr>
            <tr><td>Price:</td>
            <td><asp:TextBox ID="tbPrice" runat="server" /></td></tr>
            <tr><td>Nbr Sold:</td>
            <td><asp:TextBox ID="tbNbrSold" runat="server" /></td></tr>
            <tr><td>Sold In City:</td>
            <td><asp:TextBox ID="tbSoldInCity" runat="server" /></td></tr>
            </table>
            </blockquote>
        </p>
        
        <p>
            Group on: 
            <asp:DropDownList ID="ddlGrouping" runat="server">
              <asp:ListItem Selected="True" Value="ProductName">Product Name</asp:ListItem>
              <asp:ListItem Selected="False" Value="Price">Price</asp:ListItem>
              <asp:ListItem Selected="False" Value="NbrSold">Nbr Sold</asp:ListItem>
              <asp:ListItem Selected="False" Value="SoldInCity">Sold In City</asp:ListItem>
            </asp:DropDownList>
        </p>
        
        <p>
            <asp:Button ID="btnReload" runat="server" 
                 Text="Reload Report" OnClick="btnReload_Click" />
        </p>
        
        <CR:CrystalReportViewer ID="CrystalReportViewer1" 
                     runat="server" AutoDataBind="true" />

    TextBox 将用于按一个或多个字段进行筛选。如果 TextBox 为空,它将不参与筛选。如果您输入一个值,该值将被添加到筛选条件中。如果您只想查看波士顿的销售额且商品价格为 3.50 美元,则非常有用。

    为了保持简单,筛选仅测试相等性(= 运算符)。添加更多选项并不难,例如 Like(用于字符串)、大于和小于。

    下拉框将允许您指定用于分组的字段。

  3. 回到 Default.aspx.cs,添加一个名为 AddFilter 的实用函数来帮助构建筛选表达式。
  4.     private void AddFilter(
                            ref string filterString,
                            string databaseFieldName,
                            TextBox fieldInput,
                            string valueQuoteChar)
        {
            string fieldValue = fieldInput.Text.Trim();
    
            // If input field left blank, don't filter on it.
    
            if (fieldValue == "") return;
    
            // Escape for quotes if we're going to quote the value
    
            string escapedFieldValue = fieldValue;
            if (valueQuoteChar == @"'")
                escapedFieldValue = fieldValue.Replace(@"'", @"''");
    
            // Add boolean clause to the complete filter expression
    
            // This method always uses operator =
    
            string booleanSqlExpression =
                "{" + sourceTableOrView + "." + databaseFieldName + "} = " +
                valueQuoteChar +
                escapedFieldValue +
                valueQuoteChar;
    
            // separate boolean clauses with AND operator
    
            if (!string.IsNullOrEmpty(filterString)) filterString += " AND ";
            filterString += booleanSqlExpression;
        }

    它接受您刚才添加到 Default.aspx 的一个 TextBox、对应的数据库字段以及一个引号字符(用于字符串值),生成一个布尔子句,并将其添加到 filterString 参数中已有的任何内容之后。

    它生成的代码与 SQL WHERE 子句基本相同,只是字段名包含在大括号 { } 中。

  5. 最后,为 Reload 按钮添加点击处理程序 btnReload_Click
  6.     protected void btnReload_Click(object sender, EventArgs e)
        {
            // Create filter expression, based on the filter input fields.
    
            // If an input field is left blank, it isn't used in the filter expression.
    
            string filterString = null;
            AddFilter(ref filterString, "ProductName", tbProductName, @"'");
            AddFilter(ref filterString, "Price", tbPrice, "");
            AddFilter(ref filterString, "NbrSold", tbNbrSold, "");
            AddFilter(ref filterString, "SoldInCity", tbSoldInCity, @"'");
    
            // Get the name of the field to group on
    
            string groupFieldName = ddlGrouping.SelectedValue;
    
            // Create report document based on the new filter and grouping, and assign to 
    
            // CrystalReportViewer control. This will show the new report.
    
    
            ReportDocument rpt = 
                CreateCrystalReportDocument(filterString, groupFieldName);
    
            CrystalReportViewer1.ReportSource = rpt;
        }

    它只需使用 AddFilter 来构建筛选字符串,从下拉列表中获取分组字段名,然后使用 CreateCrystalReportDocument 创建一个新报表。

  7. 按 F5 再次运行网站。尝试按不同字段分组。在产品名称筛选字段中输入 Toy 以仅查看玩具销售额等。

五、页面重新加载后保留报表设置

  1. 页面运行时,在灰色的水平框中,您会看到一个缩放下拉菜单——它显示为 100%。将其更改为 150%。报表将以更大的字体重新加载,但会丢失任何筛选和分组!这是因为报表设置未存储在 ViewState 中。需要一些代码来在页面重新加载时保留设置。
  2. 您可能想知道在 Page_Init 中添加

    if (!Page.IsPostBack) { .... }

    是否能通过停止重新初始化 CrystalReportViewer 来提供帮助。试试看——页面重新加载后报表根本不会加载。

    另一种方法可能是让 Page_Init 根据筛选文本框和分组下拉列表构造一个包含正确筛选字符串和分组的报表文档。但是,当 Page_Init 执行时,这些控件的值尚不可用。

    一个有效的简单方法是将报表文档存储在 Session 对象中。

  3. 更新 Page_Init,使其将报表文档存储在名为 "SalesRpt" 的 Session 对象中。它不再总是重新生成报表文档,而是加载 Session 对象中的报表文档(如果存在)。
  4.     private void Page_Init(object sender, EventArgs e)
        {
            ReportDocument rpt = null;
    
            // Load the report document from the Session object.
    
            // If it's not there, generate a new report document.
    
            if (Session["SalesRpt"] == null)
            {
                rpt = CreateCrystalReportDocument(null, "ProductName");
                Session["SalesRpt"] = rpt;
            }
            else
            {
                rpt = (ReportDocument)Session["SalesRpt"];
            }
    
            CrystalReportViewer1.ReportSource = rpt;
        }
  5. 同时更新 btnReload_Click,使其用它刚刚生成的新的报表文档更新 Session 对象——这样 Page_Init 在下次页面重新加载时就能获取到它。
  6.     protected void btnReload_Click(object sender, EventArgs e)
        {
            // Create filter expression, based on the filter input fields.
    
            // If an input field is left blank, it isn't used in the filter expression.
    
            string filterString = null;
            AddFilter(ref filterString, "ProductName", 
                      tbProductName, @"'");
            AddFilter(ref filterString, "Price", tbPrice, "");
            AddFilter(ref filterString, "NbrSold", tbNbrSold, "");
            AddFilter(ref filterString, "SoldInCity", 
                      tbSoldInCity, @"'");
    
            // Get the name of the field to group on
    
            string groupFieldName = ddlGrouping.SelectedValue;
    
            // Create report document based on the new filter and grouping, and assign to 
    
            // CrystalReportViewer control. This will show the new report.
    
    
            ReportDocument rpt = 
                CreateCrystalReportDocument(filterString, groupFieldName);
    
            CrystalReportViewer1.ReportSource = rpt;
    
            // Store newly generated report document
            // in Session object, so Page_Init will pick it up.
    
            Session["SalesRpt"] = rpt;
        }
  7. 再次按 F5 运行页面。设置一个筛选条件并重新加载报表。现在,更改缩放——缩放会改变,但报表上的筛选和分组将保持不变。

六、修正标题和列

报表中的一些元素看起来不太好——例如,一个标题是 "ProductName" 而不是 "Product Name"。幸运的是,更改报表的外观非常简单。

  1. 在 Visual Studio 中打开 Sales.rpt 文件。它允许您编辑报表——更改文本,移动字段等。
  2. 双击 ProductName 标题。它将允许您在 "Product" 和 "Name" 之间插入一个空格。
  3. Crystal Reports 可能将每个组的销售总计放在了错误的位置。单击它,然后将其拖到一个更好的位置。
  4. 右键单击字段并选择 "Format Object"(格式化对象)来更改颜色、边框等。

七、为报表添加钻取功能

通过钻取功能,报表最初只显示组名和组总计。这为用户提供了一个很好的概览,而不会让他们感到信息过载。然后,他们可以通过单击组名来钻取到组的详细信息。

  1. 打开 Sales.rpt。右键单击显示 "Details"(详细信息)的灰色水平条,然后选择 "Hide (drill down OK)"(隐藏(可钻取))。
  2. 再次按 F5 运行页面。单击其中一个组标题(例如,Toys)——将打开一个新页面,显示该组的individual 销售记录。使用灰色水平条上的下拉菜单可以再次打开主报表。
  3. 请注意,主报表看起来有点奇怪。
    • 标题仅在钻取状态下才有意义,在主报表中则不然。
    • 但是,当您钻取时,标题会消失!
    • 每个组标题显示两次。

    我们现在来解决这个问题。

  4. 为了在钻取时保留标题
    • 回到 Sales.rpt(在 Visual Studio 中)。
    • 右键单击报表(在 Visual Studio 中)。
    • 将鼠标悬停在 "Report"(报表)上,以显示 Report 子菜单。
    • 单击 Report Options(报表选项)。
    • 勾选 "Show All Headers on drill-down"(钻取时显示所有标题)。
    • 点击“确定”。
  5. 删除重复的组名。在 Sales.rpt 中,您会看到组名字段两次(它显示 "Group #1 Name")——一次在详细信息节上方,一次在下方。删除详细信息节上方的组名字段——单击它以选中它,然后按 Delete 键。
  6. 最后,确保标题仅在钻取时显示。
    • 将所有标题从报表顶部拖到 GroupHeaderSection1(详细信息节上方)。这是您刚刚删除重复组名字段的区域。
    • 右键单击显示 "GroupHeaderSection1" 的灰色水平条,然后选择 "Hide (drill down OK)"(隐藏(可钻取))。
  7. 完成!再次运行报表。组标题现在应该只显示一次,并带有它们的总计。并且,只有在单击标题进行钻取时,标题才会出现。

结论

Crystal Reports,它免费包含在 Visual Studio 中,可以帮助您快速地将报表添加到任何使用数据库的网站。

本文已分步展示了如何实现这一目标,以及如何允许用户筛选记录、选择用于分组数据的字段,以及如何提供钻取功能。

历史

  • 这是版本 1。
© . All rights reserved.