代码可从 MSDN 代码库下载。
导引
作为业务的一部分,许多公司都需要由扫描图像或各种官方支持的不同格式来生成报表和表格。对于这些文档,通常都有着严格的格式要求,甚至连对文本框、标签大小和字体样式的丝毫变动都不允许。而且,重新创建与原始表格完全一致的报表,是一项代价可能很大、也非常容易出错的任务。填充表格的数据通常存储在关系型数据库中,在输出的表格中,必须将它们显示在准确的位置上。这种情况有时被称为固定布局的报表,有时被叫做像素级完美报表。
本文基于 SQL Server 2008 的报表服务(SSRS)讨论了针对这一问题的解决方案。在本方案中,我们假设有一个表格以由政府或其他有关当局指定的 PDF 或图像格式的方式输入。该表格的构成是:固定元素(各种标题、标签和说明)和可变文本或图像元素,这些变化的部分依赖于存储在企业数据库中的数据。另外,这个表格可能会是单页或多页的。
场景
一家企业扫描或下载了一张官方表格,以生成员工信息表。它可能是图片,也可能是 PDF 格式,其实任何以 PDF 格式提供的表格都可以很容易地转换为图像。在此例中,我们使用“关于工伤/ 病的雇主报告 ─ 纽约州 ─ 劳工赔偿局”。
由于PDF 格式的文件很容易转换为图像格式,我们将转换出来的图像作为新SSRS 报告的背景。该图片中有源表格中的所有固定元素项。在SSRS 的报表设计器中,我们在所有需要显示数据库中信息的位置上添加文本框和复选标签,并根据其背景图像上的线条来设定它们的位置。因此,该图片将被数据库中的数据以准确的位置加以覆盖。该报告可在所有Web 浏览器的页面中显示正确且不失真,并能够在不损失质量的前提下进行打印。用户还可以输出报告到TIFF,PDF 格式,或者SQL Server Reporting Services 支持的其他格式。
解决方案概述
最核心的需求是在所有典型场景下对报表应用支持的灵活性。这意味着,报表要能在所有浏览器的上无失真的正确显示,以及能够无质量损失的进行打印输出,当然,还应该有一个导出报表不同格式的选项,如TIFF 或PDF 格式。
挑战
本解决方案中出现了一些不同的挑战,主要是由于报告要按照我们的要求在网络浏览器显示和打印。浏览器通常不能理解或支持图像编码中”点每英寸”(DPI)的计量单位,而总是以96 dpi 分辨率来显示图像。 96 dpi 的精度在打印报告和表格时通常也不能被接受,而是更高的分辨率,一般300 dpi 或以上,才能够达到可接受的印刷质量。
A:HTML 渲染时使用的 96 dpi 分辨率光栅被 ReportViewer 组件用于浏览屏幕上的图像。与此同时,在有打印要求的其它场景下,需要较高的分辨率(300 dpi 或更高)才能保证可接受的文档打印质量。
B:当使用高清晰度的图像时,打印预览将消耗大量资源,在某些情况下甚至会因为性能太低和内存溢出而无法使用。
C:HTML 渲染不支持元素的叠加显示。这意味着,如果在报表中包含有放置在图片图像元素(如背景或表单中的图像)之上的文本框(表单中的某个字段域),那么它在浏览器中将不会被显示在图片在上面,而是在最右边。而其他一些内置的渲染器是支持元素叠加显示的。
权衡和决定
各种业务都需要固定布局的报表,它们往往会有许多不同的需求,而这些需求都有着不同的优先级。我们将各种不同的需求和多样的选择列出来,一起讨论不同方案的利弊。我们将不会触及这些方案的细节,而是讨论如何设计和实现一种能满足既要让报表在浏览器中显示、又可以进行高分辨率打印需求的方案。
所有场景都使用一个 96 dpi 分辨率的背景图片
利:这种方法意味着解决问题的最简单方法。
弊:以 96 dpi 分辨率的图片为背景的报表在 ReportViewer 里应该能正确显示,但在打印或输出为 PDF 时,质量应该会很差。
所有场景都使用一个高分辨率的背景图片
利:报表基于高分辨率图像组件,可以获得高品质印刷效果。
弊:报表在浏览器中的效果不会像打印它时一样。预览进程可能会因消耗大量资源而表现不佳,并很可能最终导致内存溢出的异常。
在一个报表中混合使用低分辨率和高分辨率的图像
利:将一个报表内同时放入低分辨率和高分辨率图像,能够涵盖最典型的应用场景。
弊:这种方案在设计每张报表和在进行可复用的开发时,都需要额外的代价。这种方案的另一种实现方法是针对网页和 / 或 Windows 应用实现一个定制的报表查看器。
高低双分辨率解决方法
此解决方案在一份报表中使用两种不同分辨率的图像,一张 96 dpi 的图片和一张本例中为 300dpi 的高分辨率图片。ReportViewer 默认就定制有此功能,可以在不同场景中选择正确处理的图像,如在浏览时和打印时。
在低分辨率时对打印质量低下的挑战(见上文挑战 A),通过在每页中使用两个不同版本的图像,并执行一个特殊的逻辑在浏览和打印时来切换这些图像得到了解决。
第二个挑战是在高分辨率图片时如何在屏幕上正确显示报表(见上文挑战 B),这个只要不在浏览和打印预览时使用高分辨率图像就解决掉了。我们在 WinForms 报表查看器中指定预览时使用低分辨率的图像,在网页激活打印时使用 PDF 格式预览。这使我们能够显著减少对性能的影响和内存的压力。
第三个挑战是 HTML 渲染不支持元素重叠显示(见上文挑战 C),只要不使用 HTML 渲染器,而使用内建的 JPEG 渲染器就可以解决这个问题,一次只将报表的一个单页渲染成一张 JPEG 图像,并建立一个自定义报表查看器来浏览报表中的每个单独页面。
至此,我们已经创建了两个自定义 ReportViewers:一个是针对 Windows 应用,基于 WinForms 的 ReportViewer 控件;另一个是针对 Web 场景,基于自定义 aspx 页通过 web 请求充分利用 SSRS 的功能,同时内部调用 WebForm ReportViewer 控件。报表每一次在屏幕上显示时都会使用 96 dpi 的低像素图片,而在打印或导出报表时就会使用高分辨率的图片。由于报表的代码本身不能确定是在什么环境中被使用,我们就在报表中增加了一个名为 ForPrint 的布尔型参数。我们的自定义代码会在报表是在被显示在屏幕上时将该参数设置为 False,而当报表是在被打印或输出时,将其设置为 True。
我们的解决方案见下图所示:图 1 所示的是针对 Windows 应用,图 2 是针对各种 Web 应用场景。
图 1 Windows 应用程序解决方案概览
图 2 Web 应用场景解决方案概览
实现说明
如前所述,我们解决的挑战之一(上文提到的挑战 C)是与网络浏览器的图像失真有关:默认 SSRS 的 HTML 渲染器会让文本框和复选框控件、与设计好的相对于背景图片的位置有轻微偏移。产生这一结果的原因是,HTML 标签生成的报表包含有一个 HTML 表格 / 网格和叠加于网格不同单元格中的控件(如编辑框)。这些单元格的坐标在不同的浏览器会以不同的方式计算(例如,Internet Explorer 7 似乎是四舍五入到下一个整数),而且偏差会随着行和列的增加替增。不同的浏览器都会让最终的图像显示有不同的失真,但却没有一个浏览器能让报表实现像素级完美的呈现。
一种可能的解决办法是开发自定义的 HTML 渲染器(查看 MSDN 的文章可以获得更多关于自定义渲染器的资料)来渲染那些经常用于固定布局的报表组件,例如文本框,图像,Tablix 等。使用自定义渲染器已经超出了我们的示例范围,我们决定使用更简单的解决方案:用一个自定义的 aspx 页面代替默认的 ReportViewer.aspx 来生成固定布局的报表。浏览报表时,这个自定义页面通过定制相应的网址发起 Web 请求来调用 SSRS 得到报表的各页,它们是 JPEG 格式的 96 dpi 图像。在报表各页之间浏览并打印或者导出时,我们在页面顶部添加自定义按钮。打印的实际上是后台导出到 PDF,因此实际上是后台生成的 PDF 文档被发送到打印机,而非网页显示的内容。
我们的方案关注于解决基本问题:通过在表格图像上叠加数据来生成报表,我们已经说明了其可行性。一些个别的报表可能需要额外的自定义代码。例如某些表单的字段,跨越了几行,每行的长度也可能不尽相同,这使得标准的报表组件很难处理。类似的还有,一些表单有些固定字段,如 SSN 就是由多个独立的单元格组成。对于这些额外要求,可以通过定制报表组件的报表设计时行为和运行时行为来实现:该设计时行为允许精确定义每条件的尺寸和字符串“切分之后”填充到单元格的每个字符和准确位置;而运行时行为则会考虑在设计时就已经定义好的尺寸和“间距”。更多关于自定义报表项的信息你也可以在这里找到,但是我们认为实现它们已经超出了本文的范围。
实施细节
我们的解决方案包括三个部分,每个都由一个 Visual Studio 2008 的项目代表:
- 图像上重叠数据的多页固定格式报表示例
- 在 Windows 程序中浏览,打印,导出各种报表的通用解决方案示例
- 基于 Web 的浏览,打印,导出各种报表的通用解决方案示例
环境要求
要打开并运行它需要安装 Visual Studio 2008,SQL Server 2008 并将这两个产品正确地集成,最简单的集成方法就是将两种产品都安装在本地。
对于 PDF 格式页面的浏览,并从网上打印网页,您将需要安装 Adobe Reader 6 或以上版本。
为有图表叠加内容的 C - 2 表格创建一个 SSRS 的报表
以下是如何使用 SQL Server 2008 Reporting Services 创建一个多页固定布局的图表叠加报表的步骤说明。在此例中,我们使用一个命名为“关于工伤 / 病的雇主报告 ─ 纽约州 ─ 劳工赔偿局”的多页表格 - 以下文简称 C2 表格。创建一个单页报表可以按照如下步骤进行。
示例报表是完整可用的,可以通过在 Visual Studio 2008 中打开“Fixed Layout Image Overlay Report.sln”工程来查看。报表需要使用的数据结构和样例数据,可以在 SQL Server Management Studio 中执行“SQLScripts”目录下的“C2SampleData.sql”脚本来生成。
第 1 步 ─ 创建表单图像
每个表单页面都创建两个图像:即每页都有一个 96 dpi 的版本(下文称之为 c2_Page__96,用于屏幕浏览)和一个 300 dpi 的版本(c2_Page_,用于打印)。
我们还创造了一个用于检查的小图像(“Check.jpg”)。
第 2 步 ─ 创建项目
打开 Visual Studio 2008 并创建一个报表服务器项目。添加一个新的报告,不要使用向导(右键单击解决方案资源管理器,选择“Add” ->“New Item”。在“Add New Item - Report Project 1”窗口中选择“Report”,然后点击“Add”按钮)。
第 3 步 ─ 将图像添加到项目
将所有在步骤 1 中创建的图像添加到报表项目中。在此演示项目中,我们将使用最简单的方法在项目嵌入图像,但需要注意的是,当图像文件总的大小超过报表服务器对 rdl 文件尺寸的限制时,它将无法工作。这种情况下,应该使用外部图像。
第 4 步 ─ 添加模式参数
添加一个隐藏的名为 ForPrint 报表参数名,布尔类型,默认值为“false”。这将用于以编程方式切换低、高分辨率的背景图像。要添加该参数,在“Report Data”窗口中的右键点击“Parameters”选择“Add Parameter”即可。
第 5 步 ─ 添加报表主 Tablix
从工具箱中将“List”报表组件添加到“Body”区域。在“Data Source Properties”窗口中设定数据源信息(它会在拖进"List"控件之后自动弹出),然后在 Data Source 栏中编写查询来提供数据。重命名 Tablix 为 BaseTable,从工具箱把拖动矩形控件到 BaseTable 区域并重命名为 BaseRectangle。
第 6 步 ─ 设置页面大小和分页符
在“Report Properties”对话框中选择纸张尺寸(8.5in * 11in),并设置边界距都为 0 的。
调整区域大小,使之为一个查询记录的所有页面的大小之和(例如,8.5in * 11in 是一张信纸的大小,8.5in * 22in 是两张信纸的大小,8.5in *(11in * N)是 N 张信纸的大小)
将 BaseTable 的位置设置为 (0in; 0in),并将其尺寸调整到与 Body 大小相同。设置 BaseRectangle 的 KeepTogether 属性置为 False,以允许软分页方式。
第 7 步 ─ 添加页面
为每个报表页面的 BaseRectangle 容器添加一个矩形(检查该矩形的 Parent 属性,其值必须是“BaseRectangle”,而不是“Body”!),将其命名为 Page_。
矩形的大小是基于原报告页面的大小,位置为 x = 0,y<=(之前所有页面高度之总和),例如: (0in; 0in),(0in; 11in),(0in; 22in)。
第 8 步 ─ 添加低分辨率的背景图像
低分辨率的图像将只用于报表在屏幕的显示——因为如果用它来打印,质量会很差。
我们将使用的页面矩形的 BackgroundImage 的属性,因为所有的渲染器都支持它。按照下面的说明更改所有 Page_ 矩形的属性:
- Source = “Embedded”;
- Value = “=IIF(Parameters!ForPrint.Value = false, “c2_Page__96”, “”)”,n 是指定矩形所在的实际页码
- BackgroundRepeat = “Clip”
这样,我们只有当页面是在屏幕上显示时使用低分辨率的背景图像和并在打印时隐藏它(根据 ForPrint 参数值进行判断)。
第 9 步 ─ 添加高分辨率的背景图像
对于打印操作,我们将使用高分辨率背景图像,并以 Image 报表控件作为每个页面的矩形的背景(矩形的 BackgroundImage 属性不支持图像拉伸,所以我们需要为每个报表页添加额外的 Image 控件)。HTML 渲染器不支持这种高分辨率图像的背景,而且它将消耗大量系统资源,所以我们将只用在打印和 PDF 导出操作上。
对于每个报告页面添加一个 Image 控件到 BaseRectangle,将其命名为:Page__Image)。将其大小定义为原始报表页的大小,位置为 x = 0,y=<(之前所有页面高度之总和),例如: (0in; 0in),(0in; 11in),(0in; 22in),因此,它的尺寸和位置与在同页上的矩形大小完全一致。大小和位置数值的单位是英寸或者毫米,而不是点。
更改 Page__Image 的属性如下:
- Source = “Embedded”
- Value = “c2_Page_”, < n> is appropriate page number
- Sizing = “FitProportional”
- Hidden = “= IIF(Parameters!ForPrint.Value = true, false, true)”
- 在每一个图片上应用工具栏上的“Send To Back”按钮(右键单击图片 ->Layout->Send To Back,或打开 Layout 面板:View->Toolbars->Layout)
经过这样的设置,Image 控件只在当 ForPrint 参数值为 True 时可见。
第 10 步 ─ 添加文本框
各种报表数据都可以很容易地通过 Text 报表组件来展现。从“Report Data”标签中将“字段”拖到选定的 Page_rectanglle 中(不要忘记检查它的 Parent 属性!)。你可以指定文本框的位置,大小和字体,以符合原始报告的外观。对每个数据字段重复此操作。
请务必将 CanGrow 属性设置为 false(默认为 true),并确保 CanShrink 属性值为 false。
第 11 步 ─ 添加复选框项
为了达到原始 PDF 表格中的复选框的效果,基于该字段的数据逻辑(如:性别 =“男”),我们将设置一个小的图片的 visible 属性(置为“checked”)使其可见。
要创建一个复选框添加一个 Image 控件,设置好它的位置,并调整其大小。更改其属性为:
- Source = “Embedded”
- Value = "= IIF( = true, “Check”, “”),为这个域使用一些逻辑
- Sizing = “FitProportional”
第 12 步 ─ 部署
到此,该报表已经可以用于在报表服务器上部署。设置“Project Deployment > properties”:
- TargetDataSourceFolder = “Data Sources”
- TargetReportFolder = “FixedLayoutReport”
- TargetServerURL = http://localhost/ReportServer (或者你自己的报表服务器地址)
之后,就可以部署它:
在接下来的部分中,我们将展示如何在不同场景中使用已布署好的报表。
如果您还没有从零开始创建报表,而是希望直接使用示例ImageReportC2 中的C - 2 报表,那么,您需要在Visual Studio 2008 中打开此报表,执行第12 步将其部署到报表服务器。
在使用此报表运行下一个示例之前,请确保该报表的数据源正确的链接到了已经安装有示例数据库的SQL Server 2008 实例上。确认这一点,只要查看DataSource.rds 的连接属性,该数据库的名称应该是C2,而且,与SQL Server 的连接认证测试必须是成功的。
请注意,当报表需要由许多数据行(超过10,000 行以上)生成时,报表服务器上的资源消耗将大幅增长。推荐使用64 位系统制作这类报告。
创建一个可以在Windows 应用程序中浏览、打印和导出固定布局报表的通用解决方案
下一步,是创建一个通用应用,允许Windows 应用中程序中浏览、打印和导出固定布局的报表。在本例中,我们将使用WinForms ReportViewer 控件和报表服务器远程处理,以及可自动处理ForPrint 参数的自定义代码。
要实现这个应用,你可以创建一个新的WinForms 项目,将工具箱中的ReportViewer 控件到窗体上,按照下图设置ReportServerURL 和ReportPath 属性:
然后,你需要实现几个事件处理程序来正确管理ForPrint 参数。在ReportExport 和Print 事件处理器中,该参数必须设置为true,见下例:
<span color="#0000ff">private void</span> rvMain_Print(<span color="#0000ff">object</span> sender, <span color="#000080">CancelEventArgs</span> e) { <span color="#008000">//Reset report to high-resolution mode for printing</span> rvMain.ServerReport.SetParameters(<span color="#0000ff">new</span> <span color="#000080">ReportParameter</span>[] { <span color="#0000ff">new</span> <span color="#000080">ReportParameter</span>(<span color="#ff0000">"ForPrint", "True"</span>) }); }
在 RenderingBegin 和 FormActived 事件处理器中,该参数需要被置回到 False,见下例:
<span color="#0000ff">private void</span> rvMain_RenderingBegin(<span color="#0000ff">object</span> sender, <span color="#000080">CancelEventArgs</span> e) { //Reset report to low-resolution mode for screen rendering <span color="#0000ff">if</span> (rvMain.ServerReport.GetParameters()[0].Values[0] == "True") { rvMain.ServerReport.SetParameters(<span color="#0000ff">new</span> <span color="#000080">ReportParameter</span>[] { <span color="#0000ff">new</span> <span color="#000080">ReportParameter</span>(<span color="#ff0000">"ForPrint", "False"</span>) }); } }
要查看示例应用程序,在 Visual Studio 2008 中 打开到 WinFormsViewer 目录下的 FixedLayoutReportWinFormsViewer.sln 解决方案。由于该解决方案引用了示例 C - 2 报告,它(或其它被引用的报表)必须是已经被部署到服务器上的(上文第 12 步说明了如何进行部署)。
在运行示例应用程序之前,在 app.config 文件中指定报表的实际位置。你可以编辑 ReportWinForms.Properties.Settings 成员框内的两个设置:
- “ReportServerURL”─这里填写部署了 C-2 报表的报表服务器地址
- “ReportPath”─报表名称。本例中,它的值是 /FixedLayoutReports/c2
如果你不能确定报表服务器的 URL,可以在报表服务配置管理器中的“Web Services URL”页中找到它。
图 xx 显示了一个由示例应用基于 C2 表格图片生成的图表数据叠加 C-2 表单
创建一个基于网页的用于浏览,打印和导出固定布局报表的通用解决方案
在实现说明环节中已经提到,基于 Web 解决方案的关键部分是使用一个自定义 aspx 页面来替代缺省的 ReportViewer.aspx 页面执行其角色,但它被专门设计用于解决默认的 ReportViewer 处理固定布局报表时所出现的问题。我们的自定义页面包括:
- 一个单独的标准 Image WebControl
- 几个用来在报表页面间导航的控件(第一页,上一页,下一页和最后一页按钮,当前页和最后一页文本框)
- 下拉列表控件,它包含了可用的导出格式:MHTML,PDF 和 TIFF。这里我们并没有考虑 Word 和 Excel 格式因为它们在固定格式导出应用中并不普遍。
- 导出按钮,用来执行导出到指定格式。
- 打印按钮,把报表页导出到 PDF 格式,然后调用 Adobe PDF 进行打印预览。
“Web Viewer”目录包含了我们基于 Web 的解决方案。要查看请在 Visual Studio 2008 中打开“FixedLayoutReportWebViewer.sln”。在运行示例程序之前,请打开 Web.config 文件,在 configuration/appSettings 配置区指定报表服务器的实际 URL。
报表名和路径应该作为参数附加到 URL 串中。示例解决方案中报表名应该是“/FixedLayoutReports/C2”。要设置该项,打开 Property Pages>Start options>Specific Page,把已对斜线等字符进行过编码的报表 URL 填写到报表路径中:
FixedLayoutReportViewer.aspx?%2f FixedLayoutReports%2fC2
或者,你可以在浏览器地址栏中指定该地址。要注意的,本解决方案依赖于报表服务器 URL 访问机制,尤其是当命令和格式都是在 URL 参数中指定时更要注意。例如,要在以 JPEG 格式显示一个指定的页面,我们使用下面的 URL 参数:
(rs:Command=Render&rs:Format=IMAGE&rc:OutputFormat=JPEG&rc:StartPage=<current_page_number>).</current_page_number>
如前所述,不使用 HTML 渲染器,而是通过将整个报表中的每一页都以一个单独的 jpg 图片的方式来呈现,这解决了失真问题(上文提到的挑战 C)。导出功能是通过在报表 URL 中包含导出命令(“rs:Command=Export”)和格式参数(e.g. “rs:Format=MHTML”)实现的。对于 PDF 输出高分辨率的图像我们通过设置报表 URL 的 ForPrint 参数值为 True 实现。要打印文件,我们执行交互式 PDF 导出,一段 JavaScript 脚本将调用 PDF 对象的 PrintWithDialog() 方法实现打印。
结论
读过本文,你知道了如何使用 SQL Server 2008 Reporting Service 来创建符合官方标准格式的精确报表。本方案通过使用符合正确用途的图片作为背景图像来创建固定格式的表格,最大限度地减少了所需的工作量。我们还演示了哪些报表可以在基于 Windows 应用程序和基于 Web 应用程序的解决方案中进行处理。
查看英文原文: SQL Server Reporting Services and Working with Overlay Data 。
感谢张海龙对本文的审校。
给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。
评论