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

使用 Reporting Services 报表生成将 DataGridView 导出到 Excel/PDF/图像文件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.94/5 (52投票s)

2008年7月31日

LGPL3

7分钟阅读

viewsIcon

391781

downloadIcon

29129

ReportExporters 是一个库,用于通过 Reporting Services 报表生成轻松地将 DataGridView 导出到 Excel/PDF/图像文件类型。

ample xls-file opened in MS Excel

ReportExporters 使用 Microsoft Report Viewer Redistributable 2005。您可以从此处下载。此外,如果您已安装 .NET Framework 3.5,您可以尝试更改对 Microsoft Report Viewer Redistributable 2008 库的引用。

引言

一段时间前,我正在寻找一种将数据从 DataGridView 导出到 MS Excel 的解决方案。我尝试了使用 Microsoft.Office.Interop.Excel.Application 类的众所周知的方法,但应用程序太慢了。所以,我试图找到另一种方法。

在处理 SQL Server 2005 Reporting Services 时,我发现组件 ReportViewer 可以将报表数据导出到原生 XLS 文件。当我仔细研究 报表定义语言规范 时,创建用于以编程方式生成 RDLC 文件的库的想法出现了。生成的 RDLC 文件应重现 DataGridView 控件的元数据(内容和布局)。此 RDLC 文件可以通过使用方法 LoadReportDefinition 加载到 Microsoft.Reporting.WinForms.LocalReport 类中,并且 Render 方法允许将报表导出为选定的格式。下面可以找到类描述和使用此库的示例。

类概述

我编写了一些接口、许多数据模型类和 RDLC 元素包装器类。这些是主要的

IReportExporter 提供导出到不同格式的常用方法
WinFormsReportExporter Microsoft.Reporting.WinForms 库的 IReportExporter 实现
IReportDataAdapter 提供用于收集导出数据元信息(类型、格式、分组等)的常用方法;应用于生成重现数据容器(控件)的报表
DataGridViewReportDataAdapter System.Windows.Forms.DataGridView 控件的 IReportDataAdapter 实现

Main interfaces and classes

DataGridViewReportDataAdapterWinFormsReportExporter 的最简单用法如下

DataGridView myDataGridView;
...
//Create DataGridViewReportDataAdapter instance
IReportDataAdapter reportDataAdapter = new DataGridViewReportDataAdapter(myDataGridView);

//Create WinFormsReportExporter instance for reportDataAdapter
IReportExporter winFormsReportExporter = new WinFormsReportExporter(reportDataAdapter);

//Execute method ExportToXls to get Excel file content
MemoryStream xlsFileData = winFormsReportExporter.ExportToXls();
...
//Than xlsFileData can be saved to local disk or etc.

ReportColumns And Styles classes

ReportColumn ReportDataColumnReportHyperlinkColumn 的基类。
ReportDataColumn 提供导出列(DataGridViewColumn 等)的内容和视图信息。
ReportHyperlinkColumn 包含 ReportDataColumn 单元格的超链接。此列的 ValueType 可以是 System.StringSystem.Uri。它没有自己的属性,只有继承的属性。

ReportColumn 属性

目录 ReportColumnCollection 中的列索引。
名称 列的名称。
ValueType 列单元格中值的數據類型。例如,它可以從 DataGridViewColumn.ValueType 初始化。

ReportDataColumn 属性

DataCellViewType 列项单元格的 ReportControl 渲染类型(TextBoxImage)。
DefaultCellStyle 列项单元格的样式。类型为 ReportStyle
HeaderCellHyperlink 列标题单元格超链接。
HeaderCellViewType 列标题单元格的 ReportControl 类型。
HeaderStyle 列标题单元格的样式。
HeaderText 列标题单元格中的文本。
HyperlinkColumn 包含项单元格超链接的列。
TemplateFormat 格式字符串,允许传递参数,如“图片大小为 {0} Kb”。
值转换器 (ValueConverter) 用于在生成报表时转换值的自定义 TypeConverter。例如,有一个 CustomBooleanConverter 可以将 Boolean 值转换为 "+"/"-""Yes"/"No"

BaseStyle 属性

BackgroundColor 背景颜色。
BackgroundGradientEndColor 背景渐变的结束颜色。
BackgroundGradientType 背景渐变的类型。
BackgroundImage 背景图像。
Border 边框(包括 BorderColorBorderStyleBorderWidth)。
日历 用于格式化日期的日历。
Color 前景色。
方向 指示文本是从左到右还是从右到左书写。
字体 字体(包括 FontFamilyFontSizeFontStyleFontWeight)。
格式 .NET Framework 格式字符串。
填充 填充。
TextAlign 文本的水平对齐方式。
TextDecoration 文本格式。
VerticalAlign 文本的垂直对齐方式。
WritingMode 文本的书写模式。指示文本是水平书写还是垂直书写。

ReportStyle 属性

宽度 列的当前宽度。
高度 列单元格高度。
NullValue 在列单元格空值中显示的字符串。
Wrap 指定单元格内容在单元格中换行。

演示应用程序

在演示应用程序中,我想向您展示一个导出不同类型数据(System.StringSystem.DoubleSystem.Byte[]System.Drawing.Image)并带有自定义格式和超链接的示例。该演示使用 Ilan Assayag 的 Google Image Search API 库来获取图像数据,以便在 DataGridView 中显示它们。

结果如下

DemoApplication

作为数据源绑定,我正在使用 GImage 对象的数组,该数组可以通过 Ilan.Google.API.ImageSearch.SearchResult 的实例初始化,该实例包含从 Google Image Search 返回的每个图像的信息。

public class GImage
{
  // Methods
  public GImage(SearchResult _searchResult, string _searchTag);

  // Properties
  public string FullFileName { get; set; }
  public double ImageSize { get; set; }
  public string ImageUrl { get; set; }
  public string SearchTag { get; set; }
  public byte[] ThumbnailData { get; set; }
  public Image ThumbnailImage { get; set; }
  public string ThumbnailUrl { get; set; }
}

自定义 ReportDataAdapter

为了更改在 DataGridViewReportDataAdapter 中收集的元数据(列宽、格式、DataCellViewType 等),或添加行分组,需要创建一个继承自 DataGridViewReportDataAdapter 类的自定义 ReportDataAdapter 类(在此演示中为 GImageReportDataAdapter)。DataGridViewReportDataAdapter 无法初始化 ReportDataColumn.DefaultCellStyle.Border,因为 DataGridView 类不包含有关其列边框的信息。

public class GImageReportDataAdapter : DataGridViewReportDataAdapterter
{
  private bool useGrouping;
  
  public GImageReportDataAdapter(DataGridView _dataGridView, 
         bool _useGrouping) : base(_dataGridView)
  {
    useGrouping = _useGrouping;
  }

  public override ReportColumnCollection GetColumns()
  {
    ReportColumnCollection toRet = new ReportColumnCollection();

    ReportColumnCollection baseColumns = base.GetColumns();

    ReportDataColumn rcSearchTag = baseColumns[0] as ReportDataColumn;
    ReportDataColumn rcFullFileName = baseColumns[1] as ReportDataColumn;
    ReportDataColumn rcThumbnailUrl = baseColumns[2] as ReportDataColumn;
    ReportDataColumn rcImageSize = baseColumns[3] as ReportDataColumn;
    ReportDataColumn rcImageUrl = baseColumns[4] as ReportDataColumn;
    ReportDataColumn rcThumbnailImage = baseColumns[5] as ReportDataColumn;
    ReportDataColumn rcThumbnailData = baseColumns[6] as ReportDataColumn;

    //Replace ReportDataColumn to ReportHyperlinkColumn
    ReportHyperlinkColumn hyperlinkColumnImageUrl = 
                   ReportHyperlinkColumn.ReplaceDataColumn(rcImageUrl);

    //set ImageUrl column as Hyperlink for ThumbnailUrl Column
    rcThumbnailUrl.HyperlinkColumn = hyperlinkColumnImageUrl;

    // Change ReportControl for ThumbnailData(byte[]) to Image instead TextBox
    CellViewImage databaseCellViewImage = 
       CellViewImage.CreateDatabaseImage(ImageMIMEType.Jpeg);
    databaseCellViewImage.Properties.Sizing = ImageSizing.FitProportional;
    rcThumbnailData.DataCellViewType = databaseCellViewImage;
    
    toRet.Add(rcSearchTag);
    toRet.Add(rcFullFileName);
    toRet.Add(rcThumbnailUrl);
    toRet.Add(rcImageSize);
    // add ReportHyperlinkColumn instead ReportDataColumn
    toRet.Add(hyperlinkColumnImageUrl);
    toRet.Add(rcThumbnailImage);

    //Skip rcThumbnailData because thumbnailDataDataGridViewImageColumn 
    //in DataGridView is invisible
    if (this.dataGridView.Columns[6].Visible)
    {
      toRet.Add(rcThumbnailData);
    }
    
    #region Apply custom formatting for ImageSize ReportDataColumn
    
    rcImageSize.DefaultCellStyle.Format = "N2";
    rcImageSize.TemplateFormat = "{0} Kb";
    
    #endregion

    #region Set custom BackgroundImage for FullFileName ReportDataColumn
    //Get embedded image
    Stream cellBackgroundImageStream = 
      GetResourceStream(Assembly.GetExecutingAssembly(), 
      "cellBackground.png");
    if (cellBackgroundImageStream != null)
    {
      BackgroundImage bgrdImage = new BackgroundImage(
        new EmbeddedImage("cellBackground_png", 
        Image.FromStream(cellBackgroundImageStream)));

      
      rcFullFileName.DefaultCellStyle.BackgroundImage = bgrdImage;
    }
    
    #endregion

    #region Apply custom border to all reportDataColumns instead first(rcSearchTag)

    Border customBorder = new Border();
    customBorder.Color = Color.Red;
    customBorder.Style = System.Web.UI.WebControls.BorderStyle.Dashed;
    customBorder.Width = new System.Web.UI.WebControls.Unit(2, 
                                  System.Web.UI.WebControls.UnitType.Point);
    foreach (ReportColumn rColumn in toRet)
    {
      if ((rColumn is ReportDataColumn) && (rColumn != rcSearchTag))
      {
        ((ReportDataColumn)rColumn).DefaultCellStyle.Border = customBorder;
      }
    }
    
    #endregion

    return toRet;
  }
 ......
}

分组

Microsoft Reporting Services 允许按条件对数据进行分组。为了设置分组数据,需要重写接口 IReportDataAdapterGetTableGroups 方法。如果不需要分组,函数应返回 null

下面是 GImageReportDataAdapter 的实现

public override ReportTableGroupList GetTableGroups(ReportColumnCollection columns)
{
  if (!useGrouping)
  {
    return null;
  }
  else
  {
    ReportTableGroupList gImagesGroupList = new ReportTableGroupList();
    {
      ReportTableGroup reportTableGroupBySearchTag = new ReportTableGroup();
      //group by SearchTag
      reportTableGroupBySearchTag.ColumnGrouping.Add(columns[0]);
      //sort by SearchTag(Descending)
      reportTableGroupBySearchTag.ColumnSorting.Add(columns[0],
        ReportExporters.Common.Rdlc.Enums.SortOrder.Descending);

      gImagesGroupList.Add(reportTableGroupBySearchTag);
    }
    return gImagesGroupList;
  }
}

在此演示中,我使用 ApplyRandomOrderToArray 方法为 GImage 数组应用随机顺序。您可以通过 SearchTag 查看行数据分组的结果;也可以按 SearchTag 降序排序,如以下屏幕截图所示。Google 搜索查询为“Paris; London; New York”,并且“Use Grouping”复选框已选中。

Grouping rows by SearchTag

自定义导出设置

报表渲染可以通过使用 设备信息设置 进行自定义。我为每个可用的报表渲染类型编写了继承自 BaseDeviceInfoSettings 的类。PdfDeviceInfoSettings 类(导出到 Excel 的 ExcelDeviceInfoSettings,以及导出到图像的 ImageDeviceInfoSettings)可用于获取 DeviceInfo XML 元素并将其传递给接口 IReportExporter 的方法 ExportToPdf(string deviceInfo)

PdfDeviceInfoSettings deviceInfo = new PdfDeviceInfoSettings();
//set paper size to A3 (11in × 17in)
deviceInfo.PageHeight = new Unit(11, UnitType.Inch);
deviceInfo.PageWidth = new Unit(17, UnitType.Inch);
string deviceInfoXml = deviceInfo.ToString(); 
MemoryStream pdfFileData = winFormsReportExporter.ExportToPdf(deviceInfoXml);

一个 Excel 工作簿中的多个工作表

令我惊讶的是,MS Reporting Services 允许我们将报表导出到包含多个工作表的 Excel 工作簿中。这可以通过在主报表中逐个放置几个 Rectangle 控件,并在每个 Rectangle 控件中放置一个 Subreport 控件来实现。在这种情况下,我们可以创建一个包含多达 1000 个工作表的 Excel 工作簿。但是这种方法有一个小问题——在第二个及以后的工作表中,第一行是隐藏的(其高度等于 1 像素)。

在代码中,为了利用向工作簿添加额外工作表的功能,需要在构造函数 WinFormsReportExporter 中放置 List<IReportDataAdapter>(每个工作表一个 IReportDataAdapter 实例)。

    List<IReportDataAdapter> sameAdapters = new List<IReportDataAdapter>();
    for (int aIndex = 0; aIndex < nudSheets.Value; aIndex++)
    {
        sameAdapters.Add(gImageReportDataAdapter);
    }
    winFormsReportExporter = new WinFormsReportExporter(sameAdapters);
    
    //Execute method ExportToXls to get Excel WorkBook with several worksheets
    MemoryStream xlsFileData = winFormsReportExporter.ExportToXls();
    ...
    //Than xlsFileData can be saved to local disk or etc.

Several worksheets

如何导出 DataSet

DataSetAdapterProvider 允许您为数据集表创建一组 IReportDataAdapter。默认情况下,它会创建 DataViewReportDataAdapter 类的对象(可以通过 DataTable.DefaultView 初始化)。DataViewReportDataAdapter 类的实现不格式化数据(列)。此任务留给自定义应用程序开发人员。

DataSetAdapterProvider 方法

CreateAdapter DataTable 创建 IReportDataAdapter(默认为 DataViewReportDataAdapter)。重写以提供带格式的自定义 ReportDataAdapter。
ReorderAdapters 用于排序 ReportDataAdapter 列表(Excel 工作表)。
GetAdapters 检索 ReportDataAdapter 列表(每个数据集表一个 ReportDataAdapter)。
    DataSet myDataSet;
    ....
    DataSetAdapterProvider dsaProvider = new DataSetAdapterProvider(myDataSet);
    
    //Retreive list of IReportDataAdapters
    List<IReportDataAdapter> datasetAdapters = dsaProvider.GetAdapters();
    
    //Pass list of IReportDataAdapters to WinFormsReportExporter constructor
    winFormsReportExporter = new WinFormsReportExporter(datasetAdapters);
    
    //Execute method ExportToXls to get Excel file content
    MemoryStream xlsFileData = winFormsReportExporter.ExportToXls();
    ...
    //Than xlsFileData can be saved to local disk or etc.

Displayed in DataGrid exporting DataSet

其他格式

Anton Ponomarev 在他的文章《为 Microsoft Report Viewer 控件添加 DOC、RTF 和 OOXML 导出格式》中描述了向标准 Microsoft Report Viewer 控件添加自定义渲染扩展。他使用 .NET 工具修改了 ReportViewer 程序集。结果,他得到了 Microsoft.ReportViewer.WinForms.Modified.dll 程序集,其中 Report Viewer 组件能够在本地模式下工作时以 Microsoft Word 格式(DOC、RTF、WordprocessingML 和 OOXML)生成报表。

我猜想,如果 ReportExporters 中对 Microsoft.ReportViewer.WinForms.dll 的引用更改为 Microsoft.ReportViewer.WinForms.Modified.dll,并且扩展了 IReportExporter 接口,那么就可以将 DataGridView 导出为 DOC、RTF、WordprocessingML 和 OOXML 格式。

我还没有 Microsoft.ReportViewer.WinForms.Modified.dll。我很快会研究这个问题。

结论

使用 ReportExporters 库,您可以

  • DataGridView/DataSet 导出为原生 XLS 而不是 SpeedSheetXML。不需要安装 MS Excel。
  • 还可以导出为 PDF(未压缩)、BMP、EMF、GIF、JPEG、PNG 和 TIFF 格式。
  • 为导出单元格和列标题指定格式(字体、对齐方式、数字格式、边框、背景等)。
  • 指定要导出的列列表。
  • 为行添加分组和排序。
  • 在 Excel 和 PDF 文档中添加超链接。
  • 在 Excel 工作簿中添加额外的工作表。
  • 导出嵌入式、外部(位于本地系统或 Internet 中)图像。

关注点

编写这个库对我来说非常有趣和激动。我希望它对正在寻找类似解决方案的开发人员有所帮助。

欢迎提出任何建议和问题!

历史

  • 2008-07-31:文章创建。
  • 2008-08-18:添加功能
    • 导出到包含多个工作表的 Excel 工作簿;
    • System.Data.DataSet 导出到工作簿(DataSet 的每个表一个工作表);
© . All rights reserved.