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

Microsoft 报表,无需 SQL Server 报表服务

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.72/5 (19投票s)

2008年4月14日

CPOL

10分钟阅读

viewsIcon

125784

downloadIcon

4014

了解如何将 ReportViewer 与任何数据源一起使用,并即时构建报表,用于 Web 或 Windows 应用程序。

The ReportViewer control

引言

作为开发人员,显示报表是我们都花费大量时间的一件事。显示报表的方式有很多——格式化的 HTML、GridViews、Repeaters——不胜枚举。当我的一个客户要求一个允许动态报表的报表引擎时——也就是说,用户定义的参数定义了报表中显示的数据,我开始研究 ReportViewer 控件。这个控件的一个很棒的功能是能够将报表直接保存到 Excel 中。它还具有分页、缩放等功能。我最初在我的博客上讨论了这个话题,但决定 CodeProject 是一个更好的地方。

应该注意的是,ReportViewer 控件有两个版本。第一个位于 Microsoft.Reporting.WinForms 库中,用于 Windows 应用程序开发。第二个位于 Microsoft.Reporting.WebForms 中,设计用于 ASP.NET。这两个控件都只能在 Microsoft Visual Studio 2005 或更高版本以及 .NET 2.0 Framework 中使用。

背景

我使用该控件的首要任务是确定如何实际使用它。它使用一个 .rdlc 文件,该文件充当传入数据和 ReportViewer 内部格式的模板。除了 RDLC 文件,ReportViewer 还需要绑定实际数据。最简单的方法是使用安装了 Reporting Services 的 SQL Server 2005,但我经过一番探索发现,将这两个对象绑定到 ReportViewer 的过程非常灵活,足以支持使用任何数据源,只要你不介意多花一点精力。

解码 RDLC

要确定 RDLC 是如何生成的,我们必须创建一个示例。报表可以简单到表格数据、图表,或者两者的组合,包含图像、徽标等。

在 Visual Studio 2005 中,创建一个新项目。项目类型并不重要,但为了测试目的,最好选择您打算使用 ReportViewer 的应用程序类型。有了应用程序后,向项目中添加一个 DataSet。定义一个包含几个列的表。

接下来,向项目添加一个报表。在左侧,您应该会看到一个可以添加数据源的窗口。继续添加我们创建的 DataSet 作为数据源。

我知道你在想什么。如果我们要构建一个 DataSet 对象,为什么不直接将其连接到数据库,将其用作单一来源,然后为每个报表添加一个新的 RDLC 呢? 嗯,这是一个好问题。实际上,我们可以创建一个 DataSet 对象,添加我们需要的表和列,将每个字段连接到我们想要的报表,并在项目中拥有与我们需要的报表数量一样多的 RDLC。如果您只需要一两个报表并且不介意手动管理它们,这是一个很好的解决方案。但是,如果您的用户想要 30 种不同的报表呢?此外,您将失去数据源的灵活性。在初始构建类之后,基于这些原则构建的报表引擎可以反复重用。此外,在任意数量的报表中标准化报表布局变得非常容易,然后如果您的公司更新了徽标或决定更改报表外观,则可以更改该标准化。此外,您不必单独管理报表文件。如果要向数据添加列,只需更改 SQL 查询即可。无论如何,让我们回到手头的任务。

在设计窗口中,将您的报表设计成您想要的样子。如果您的报表需要特定功能,请务必在此处添加它们,以便您可以看到它们在 RDLC 中是如何处理的。如果您的用户想要一个徽标或任何特殊格式,现在是时候这样做了。您将能够看到它是如何通过 RDLC 完成的,然后您可以在自己的 RDLC 生成器中模仿它。

布局完成后,导航到项目所在的文件夹,并将 .rdlc 文件另存为 .xml 文件。这可以保存到其他位置。使用您喜欢的 XML 编辑器打开 XML 文件。我更喜欢直接使用 Visual Studio。此文件向您展示了许多可以使用的格式选项。既然我们有了要使用的模板,那么让我们来剖析它。

Creating an rdlc in Visual Studio 2005

这是 RDLC 中与实际 DataSet 相对应的部分。DataSet1 是我们 DataSet 的名称,每个数据字段都在列表中指定。您应该注意,我们稍后将传入的数据源是添加到 DataSet 中的单个 DataTable。这是由于我们绑定数据的方式造成的。似乎可以使用多个表创建报表,但我还没有尝试过,所以不要相信我的话。

<DataSources>
    <DataSource Name="DataSet1" />
</DataSources>
<DataSets>
    <DataSet Name="DataSet1">
        <Fields>
            <Field Name="Column1" DataField="ColumnName" />
            ...
        </Fields>
    </DataSet>
</DataSets>

通过将“DataSet1”更改为任何值,您可以传入任何命名的 DataSet。我们稍后会讨论这个发现的重要性。现在,让我们继续剖析 RDLC。

如果您向报表添加一个表,您将看到一个 Details 部分,其中列出了您的列。让我们看一下。

<Details>
    <TableRows>
        <TableRow>
            <TableCells>
                <TableCell>
                    <ReportItems>
                        <TextBox Name="textBox1" ZIndex="1" />
                        <Value>=Fields!Column1.Value</Value>
                    </ReportItems>
                </TableCell>
                ...
            </TableCells>
        </TableRow>
    </TableRows>
</Details>

我们在这个 RDLC 块中看到,我们 DataSet 中的列在这里定义了数据在报表中的位置。我想要实现的一个复杂任务是让行交替背景颜色。通过使用特殊的报表语法,可以通过在 TextBox 上设置此属性来完成:BackgroundColor="=iff(RowNumber(Nothing) Mod 2, "#000000", "#FFFFFF")"是的,您需要双引号。

通过这些简单的步骤,您可以解析 RDLC 定义中您希望报表具有的功能。您将遵循类似的过程来确定如何在报表中生成图表、显示公司徽标或您希望实现的任何其他功能。

如何使用 RDLC

现在我们对 RDLC 如何定义报表有了基本的了解,我们可以编写一个类来构建 RDLC 中包含的 XML,然后在报表中使用该 XML。在我提供的代码示例中,您可以看到图表和表格报表的 RDLC 构建器的基本实现。这些是构建简单 RDLC 的简单类。将来,我想将整个过程模块化,将 RDLC 的各个部分拆分成专门的小类,然后将所有内容整合到一个报表类中,该类允许您添加多个数据集、表格形式、图表、定义格式等。

当我第一次编写生成 RDLC 的代码时,我将 XML 写入文件,然后将文件加载到 ReportViewer 中。这在 Windows 应用程序环境中很好,因为它按用户安装,但有时,将文件写入磁盘并不是一个好主意,尤其是在 Web 开发中。我尝试使用 ReportViewer,发现了一种将 RDLC 作为 System.IO.MemoryStream 对象而不是写入文件的方法。这是一个更好的解决方案,考虑到在 Web 服务器上写入文件存在安全隐患,以及每次需要显示报表时都将 RDLC 写入文件的纯粹 WTF 性。让我们看看如何将 RDLC 和数据源绑定到 ReportViewer

DataTable customerDataTable = GetCustomerData();

customerDataTable.TableName = "CustomerDataTable";

System.Data.DataSet customerData = customerDataTable.DataSet;
customerData.DataSetName = "CustomerData";

Rdlc report = new Rdlc(customerData);

reportViewer1.LocalReport.DataSources.Add(
      new Microsoft.Reporting.WinForms.ReportDataSource(customerData.DataSetName,
      customerDataTable));
reportViewer1.LocalReport.LoadReportDefinition(report.GetRdlcStream());

this.reportViewer1.RefreshReport();

GetCustomerData() 是一个返回 System.Data.DataTable 的方法。从技术上讲,我通常会做的操作是针对数据库运行查询,将数据加载到 DataSet 中,然后返回 DataSet - 这是 ADO.NET 101。这些数据可以来自任何地方 - WebService XML、分隔文件或任何你想使用的数据库。诀窍是将其加载到 System.Data.DataTable 中,如果它不属于任何 DataSet,则将其添加到 DataSet 中。对于 XML,你可以直接将 XML 加载到 DataSet 中,但务必为 DataSetDataTable 指定唯一的名称。RDLC 类将选择名称并构建 RDLC 以匹配传入的数据。这个粗略的类目前不支持过滤列;但是,它可以通过传入标题名称的字符串数组来覆盖列名。这不是最好的方法,但这绝不是一个完整的报表库。

一旦我们为 DataSetDataTable 正确命名,就该将其绑定到 ReportViewer 了。如您所见,我正在使用控件的 WinForms 版本,并且必须使用 ReportDataSource 类的 WinForms 版本。Web 版本有相应的 ReportDataSource。只需将 WinForms 更改为 WebForms 即可。之后,我们正在从 System.IO.MemoryStream 加载 RDLC。

源代码

我包含了四个类,它们包含用于构建简单表格报表和简单图表的 RDLC 的逻辑。在图表方面,只有饼图经过测试,因此此代码可能存在错误。事实上,这些代码中的任何一个都可能存在错误,因为它仍处于“概念验证”阶段。

  • Chart - 生成图表 RDLC 的类。
  • ChartType - 图表类型的枚举(饼图、折线图、散点图等)。
  • ChartSubType - 图表子类型的枚举(普通、堆叠等)。注意:并非所有子类型都适用于所有图表类型。
  • Rdlc - 生成表格数据 RDLC 的类。

让我们研究最简单的类,Rdlc

格式化选项目前通过属性进行管理。有更好的方法,但这留待另一篇文章。让我们直奔主题:GetRdlcString()

XmlTextWriter _rdl = new XmlTextWriter(writer);

DataTable data = _data.Tables[0];

_rdl.Formatting = Formatting.Indented;
_rdl.Indentation = 3;
_rdl.Namespaces = true;

int _columns = data.Columns.Count;

此代码构建了传入构造函数的 DataSet 部分。

// DataSource element
_rdl.WriteStartElement("DataSources");
_rdl.WriteStartElement("DataSource");
_rdl.WriteAttributeString("Name", null, data.DataSet.DataSetName);
_rdl.WriteStartElement("ConnectionProperties");
_rdl.WriteElementString("DataProvider", "Oracle");
_rdl.WriteElementString("ConnectString", "ItsaSecret");
_rdl.WriteElementString("IntegratedSecurity", "true");
_rdl.WriteEndElement(); // ConnectionProperties
_rdl.WriteEndElement(); // DataSource
_rdl.WriteEndElement(); // DataSources

// DataSet element
_rdl.WriteStartElement("DataSets");
_rdl.WriteStartElement("DataSet");
_rdl.WriteAttributeString("Name", null, data.DataSet.DataSetName);

// Query element
_rdl.WriteStartElement("Query");
_rdl.WriteElementString("DataSourceName", data.DataSet.DataSetName);
_rdl.WriteElementString("CommandType", "Text");
_rdl.WriteElementString("CommandText", "wouldntyouliketoknow");
_rdl.WriteElementString("Timeout", "30");
_rdl.WriteEndElement(); // Query

// Fields elements
_rdl.WriteStartElement("Fields");

for (int x = 0; x < _columns; x++)
{
    _rdl.WriteStartElement("Field");
    _rdl.WriteAttributeString("Name", null, data.Columns[x].ColumnName);
    _rdl.WriteElementString("DataField", null, data.Columns[x].ColumnName);
    _rdl.WriteEndElement(); // Field
}

// End previous elements
_rdl.WriteEndElement(); // Fields
_rdl.WriteEndElement(); // DataSet
_rdl.WriteEndElement(); // DataSets

Query 节点是 DataSet 声明中的一个部分,我将其保留以备将来支持。报表有可能运行查询。这都很好,但我更喜欢直接传入已经编译好的数据。您可以通过向 DataSet 对象添加查询来测试 RDLC 的这一部分。

// Query element
_rdl.WriteStartElement("Query");
_rdl.WriteElementString("DataSourceName", data.DataSet.DataSetName);
_rdl.WriteElementString("CommandType", "Text");
_rdl.WriteElementString("CommandText", "wouldntyouliketoknow");
_rdl.WriteElementString("Timeout", "30");
_rdl.WriteEndElement(); // Query

接下来,我们有包含列规范的 Details 部分。它只是遍历 DataTable 中的列,并将每个单元格添加到 RDLC。它具有交替行颜色和行文本颜色的逻辑。

_rdl.WriteStartElement("", "Details", null);
_rdl.WriteStartElement("", "TableRows", null);
_rdl.WriteStartElement("", "TableRow", null);
_rdl.WriteStartElement("", "TableCells", null);

int _detailIndex = _columns * 2;

//write all the detail items
for (int x = 0; x < _columns; x++)
{
    int _zindex = (_detailIndex + x);
    string _name = "textbox" + _zindex;
    string _value = "=Fields!" + data.Columns[x].ColumnName + ".Value";

    //Alternate the colors by row
    string _bgcolor = "=iif(RowNumber(Nothing) Mod 2, \"" +
        System.Drawing.ColorTranslator.ToHtml(BackgroundColorBody) +
        "\", \"" + System.Drawing.ColorTranslator.ToHtml(BackgroundColorBodyAlternate) +
        "\")";
    string _textcolor = "=iif(RowNumber(Nothing) Mod 2, \"" +
        System.Drawing.ColorTranslator.ToHtml(TextColorBody) + "\", \"" +
        System.Drawing.ColorTranslator.ToHtml(TextColorBodyAlternate) + "\")";

    AddCell(_rdl, _name, _bgcolor, _textcolor, TextAlignFooter, 0, _zindex, _value);
}

//end TableCells
_rdl.WriteEndElement();

_rdl.WriteStartElement("", "Height", null);
_rdl.WriteString("0.25in");
_rdl.WriteEndElement();

//end TableRow
_rdl.WriteEndElement();

//end TableRows
_rdl.WriteEndElement();

//end detail
_rdl.WriteEndElement();

为了方便起见,我有一个名为 AddCell 的方法,它为每个单元格定义创建 XML。它有一些我传入的格式。

private static void AddCell(XmlTextWriter _writer, string name,
    string backgroundColor, string textColor,
    string textAlign, int fontWeight, int ZIndex, string value)
{
    _writer.WriteStartElement("", "TableCell", null);
    _writer.WriteStartElement("", "ReportItems", null);

    _writer.WriteStartElement("", "Textbox", null);
    _writer.WriteAttributeString("Name", name);

    _writer.WriteStartElement("", "ZIndex", null);
    _writer.WriteString(ZIndex.ToString(CultureInfo.InvariantCulture));
    _writer.WriteEndElement();

    _writer.WriteStartElement("", "Style", null);

    _writer.WriteStartElement("", "TextAlign", null);
    _writer.WriteString(textAlign);
    _writer.WriteEndElement();

    _writer.WriteStartElement("", "Color", null);
    _writer.WriteString(textColor);
    _writer.WriteEndElement();

    _writer.WriteStartElement("", "BackgroundColor", null);
    _writer.WriteString(backgroundColor);
    _writer.WriteEndElement();

    _writer.WriteStartElement("", "FontWeight", null);
    if (fontWeight == 0)
    {
        _writer.WriteString("100");
    }
    else
    {
        _writer.WriteString(fontWeight.ToString(CultureInfo.InvariantCulture));
    }
    _writer.WriteEndElement();

    _writer.WriteStartElement("", "PaddingLeft", null);
    _writer.WriteString("2pt");
    _writer.WriteEndElement();

    _writer.WriteStartElement("", "PaddingBottom", null);
    _writer.WriteString("2pt");
    _writer.WriteEndElement();

    _writer.WriteStartElement("", "PaddingRight", null);
    _writer.WriteString("2pt");
    _writer.WriteEndElement();

    _writer.WriteStartElement("", "PaddingTop", null);
    _writer.WriteString("2pt");
    _writer.WriteEndElement();

    //end Style
    _writer.WriteEndElement();

    _writer.WriteStartElement("", "CanGrow", null);
    _writer.WriteString("true");
    _writer.WriteEndElement();

    _writer.WriteStartElement("", "Value", null);
    _writer.WriteString(value);
    _writer.WriteEndElement();

    //end TextBox
    _writer.WriteEndElement();

    //End ReportItems
    _writer.WriteEndElement();

    //end TableCell
    _writer.WriteEndElement();
}

注意事项

在基于网络的版本中,一旦加载了 ReportViewer,就无法重新加载它。您必须再次访问页面并使用查询字符串、会话、视图状态或其他传递数据的方法来更改报表。WinForms 版本没有这个问题。

如果 RDLC 中的 XML 标签格式不完全正确,有时如果遗漏某些元素,报表将无法加载。如果 ReportViewer 无法加载报表,它将显示一个模糊的错误。

结论

可以使用 Microsoft Reporting 和任何您想要的数据源。Microsoft 和所有销售图表应用程序库的公司(抱歉 Dundas)都不希望您知道这一点。我还没有深入研究图表功能以找到任何限制,但我确信有一些。有了我在这里介绍的基础知识,任何开发人员都应该能够构建自己的报表库,利用 ReportViewer 的强大功能,并连接到他们应用程序可能使用的任何数据源。

© . All rights reserved.