Microsoft 报表服务 - 第二部分
本文介绍了一些改进 Microsoft 报表的小技巧,例如嵌入式报表、显示存储的图像以及来自路径的图像,使用自定义代码和自定义程序集,导出报表以及在没有 ReportViewer 控件的情况下打印报表。
引言
在我上一篇关于 Microsoft 报表的文章中,我介绍了创建报表的基本步骤、在运行时定义数据源、处理参数、包含图像、使用表达式编辑器以及如何为子报表和图表提供数据。我还演示了一些自定义 ReportViewer
的简单方法。
在本文中,我将尝试向您展示一些中级/高级技巧、一些有用的功能以及一些代码,您可以用来改进您的报表。
这是列表
嵌入式报表
Microsoft 报表是 XML 文件,扩展名不同 (*.rdlc),其中包含报表定义、图像、自定义代码等。因为它们易于编辑(您可以使用 XML 编辑器甚至记事本来完成),所以它们可以被更改,从而改变最终结果。这既有优点也有缺点。
您可以查看 XML 内部并检查任何问题,或者您可以将报表保留在应用程序外部,这样 .exe 文件的大小就不会增加。另一方面,您无法确保报表的安全性和保护性。这些只是开发人员需要考虑的一些事项。
默认情况下,当您将新报表添加到解决方案时,生成操作设置为嵌入式资源。如果您使用了 ReportViewer
并希望选择在运行时加载哪个报表,您可以使用以下方法定义报表:
Me.ReportViewer1.LocalReport.ReportEmbeddedResource = "WindowsApplication1.Report1.rdlc"
如果您选择使用外部报表而不是将其嵌入应用程序中,则需要将生成操作属性更改为“无”,然后使用以下方法定义报表路径:
Me.ReportViewer1.LocalReport.ReportPath = "..\..\Report1.rdlc"
显示存储的图像和来自路径的图像
将图像存储在数据库中,通常称为 BLOB
(二进制大对象),这是处理数据时的常见过程。将图像存储在数据库中或仅存储路径/URL 有一些优缺点:首先,您可以非常轻松地将图像存储在 SQL Server 中并在以后使用;但是,对于 Microsoft Access 等其他数据库引擎,情况就不同了,尤其是在您想在以后使用它时,因为 Access 将图像存储为 Object
。
本文的目的不是枚举两种方法的优缺点,而只是演示如何从两者中在报表中显示图像。
如果图像存储在数据库中,您只需从工具箱中将 Image
控件插入到表中。
然后,在控件的属性中,将 Value
定义为 DataTable
中的相应字段(别忘了先为报表定义数据集)。然后,将 Source
属性定义为 Database
,并将 MIMEType
属性设置为正确的值。在此示例中,我选择了 Image/png。
如果只存储了图像的路径/URL,那么您可以执行与上面相同的过程,但需要将 Value
属性定义为:="File://" & <field name>。您还需要将 Source
定义为 External
。
最后,无论是硬编码还是使用 ReportViewer
的属性窗口,都可以通过将 EnableExternalImages
属性设置为 True
来配置 LocalReport
以允许外部图像。
Me.ReportViewer1.LocalReport.EnableExternalImages = True
使用自定义代码
Microsoft 报表已内置了多个函数,可让您自定义报表中的信息。使用表达式编辑器,您可以在“常用函数”类别中看到这些函数,并使用它们来格式化、修改信息输出等。
您还可以创建自己的函数并扩展其限制。
通过转到“报表属性”(菜单“报表”->“报表属性”)并选择“代码”选项卡,您可以在可用文本框中定义自定义代码。
此示例将检查国家/地区代码并定义不同的格式。
这是上面图像中的代码
Function currencyFormat(ByVal value As Double, _
ByVal countryCode As String) As String
Select Case countryCode
Case "PT"
Return String.Format("{0:n} €", value)
Case "US"
Return String.Format("$ {0:n}", value)
Case Else
Return String.Format("{0:n}", value)
End Select
End Function
如果编辑报表文件 (*.rdlc),您会看到定义的自定义代码位于 <Code> ... </Code>
标签之间。
要使用此自定义代码,您可以使用表达式编辑器,或者直接在 Table 单元格、Matrix 单元格、Textbox 等中输入。
在此,定义“=Code.<function name>”以指示您正在使用自定义代码。在这种情况下,对于此示例,它将指示第一个参数价格的自定义代码,第二个参数国家/地区代码的自定义代码。
=Code.currencyFormat(Fields!price.Value,Fields!countryCode.Value)
如您所见,此自定义代码的智能感知不可用,因为报表未编译,并且无法验证创建的代码。您只需确保函数和参数定义正确。
此演示报表的最终结果是具有不同国家/地区代码不同格式的列表。
自定义程序集
从上一节可以看出,您可以将自定义代码添加到 Microsoft 报表中。如果您需要自定义某些内容,这将非常有帮助。但是,如果您需要在多个报表中这样做怎么办?嗯,您可以使用自定义程序集!
要将自定义程序集用于报表,您只需创建一个类库项目,该项目将生成一个 *.dll 文件,并将此文件用于您需要的所有报表。
创建文件后,需要将该 *.dll 文件复制到应用程序的 debug\release 文件夹以及 PrivateAssemblies 文件夹(在 Visual Studio 2008 中,通常是 C:\Program Files\Microsoft Visual Studio 9.0\Common7\IDE\PrivateAssemblies\)。
在这个例子中,我创建了这个小程序,它将截断文本,如果其长度等于或大于 30。
Public Class rptClass
Public Function CutText(ByVal txt As String) As String
If txt.Length >= 30 Then
Return txt.Substring(0, 26) & " ..."
Else
Return txt
End If
End Function
Public Shared Function SharedCutText(ByVal txt As String) As String
If txt.Length >= 30 Then
Return txt.Substring(0, 26) & " ..."
Else
Return txt
End If
End Function
End Class
之后,您需要向报表添加对该程序集的引用。打开报表 (*.rdlc),然后在“报表”菜单中,选择“报表属性”选项。在“引用”选项卡中,选择创建的 *.dll 文件。
注意:程序集名称描述将稍后在代码中使用。
在此窗口中,您有两个网格
- 引用:如果您使用
Shared
方法,您将直接调用它。 - 类:如果您不使用
Shared
方法,并按此方式进行,则需要创建类的新实例(必须直接编写)。
之后,您可以在报表中这样使用它
请注意,如果您创建了类的新实例,则必须定义为 =Code.<class>.<method name>。
最后,在代码中,您需要使用以下方法指示您的自定义程序集是受信任代码:
Me.ReportViewer1.LocalReport.AddTrustedCodeModuleInCurrentAppDomain( _
"ClassLibrary1, Version=1.0.0.1, Culture=neutral, PublicKeyToken=null")
导出报表
导出报表可能非常有用,它允许您将报表保存到文件或用于某些自动化。ReportViewer
控件已提供了一些内置的导出报表选项。尽管如此,在没有 ReportViewer
的情况下进行导出更有用,因为这样您可以自动化一些过程,例如将文件作为电子邮件附件发送。
当它在本地模式 (LocalReport
) 下使用时,有几种格式可用于导出:Microsoft Excel、PDF 或图像。
要导出报表,只需将 LocalReport
Render()
到字节数组,然后使用 FileStream
将其写入最终文件。
这里有一个例子:
Public Class ReportUtils
Enum rptFormat
Excel
PDF
Image
End Enum
'''<summary>
''' Exports a LocalReport
'''</summary>
'''<param name="report">LocalReport</param>
'''<param name="output">Format</param>
'''<param name="filePath">File path</param>
'''<remarks></remarks>
Public Sub Export(ByVal report As LocalReport, _
ByVal output As rptFormat, ByVal filePath As String)
Dim warnings As Warning() = Nothing
Dim streamids As String() = Nothing
Dim mimeType As String = Nothing
Dim encoding As String = Nothing
Dim extension As String = Nothing
Dim bytes() As Byte = report.Render(output.ToString, Nothing, _
mimeType, encoding, extension, streamids, warnings)
Using fs As New IO.FileStream(filePath, IO.FileMode.Create)
fs.Write(bytes, 0, bytes.Length)
fs.Close()
End Using
bytes = Nothing
End Sub
End Class
然后,您可以使用上面的类,在过程结束时打开文件
Dim rpt As New LocalReport
rpt.ReportPath = Application.StartupPath & "\..\..\rptProducts.rdlc"
' ---------------------------------------------------------
' Define report DataSource, Parameters, etc.
' ---------------------------------------------------------
Dim fileName As String = "c:\test.pdf"
Dim clsReport As New ReportUtils
clsReport.Export(rpt, ReportUtils.rptFormat.PDF, fileName)
Process.Start(fileName)
在没有 ReportViewer 的情况下打印报表
Microsoft 报表通常与 ReportViewer
相关联,并且通常,当使用一个时,也会使用另一个。诚然,此控件具有许多不错的特性,如预览、导出、打印等,但并非总是有必要在打印报表之前预览它!
LocalReport
没有内置选项允许您直接打印报表,而无需 ReportViewer
。要打印报表,您需要将报表 Render()
到 Stream
,然后,使用 PrintDocument()
,在 PrintDocument
打印时,将 Stream
列表中的每个正确页面绘制出来。
这是一个可以做到这一点的类
Imports System.IO
Imports System.Data
Imports System.Text
Imports System.Drawing.Imaging
Imports System.Drawing.Printing
Imports System.Collections.Generic
Imports Microsoft.Reporting.WinForms
Public Class ReportUtils
Implements IDisposable
Private currentPageIndex As Integer
Private tmpFileName As String = String.Empty
Private streamList As List(Of Stream)
Enum Orientation
Landscape
Portrait
End Enum
''' <summary>
''' Add the Stream to the list
''' </summary>
Private Function CreateStream(ByVal name As String, _
ByVal fileNameExtension As String, _
ByVal encoding As Encoding, _
ByVal mimeType As String, _
ByVal willSeek As Boolean) As Stream
tmpFileName = My.Computer.FileSystem.GetTempFileName()
Dim s As New FileStream(tmpFileName, FileMode.Create)
streamList.Add(s)
Return s
End Function
''' <summary>
''' Exports the file to the list of Streams
''' </summary>
Private Sub ExportToStream(ByVal report As LocalReport, _
ByVal Orientation AsOrientation)
Dim deviceInfo As New StringBuilder
With deviceInfo
.Append("<deviceinfo>")
.Append(" <outputformat>EMF</outputformat>")
If Orientation = ReportUtils.Orientation.Portrait Then
.Append(" <pagewidth>8.5in</pagewidth>")
.Append(" <pageheight>11.5in</pageheight>")
Else
.Append(" <pagewidth>11.5in</pagewidth>")
.Append(" <pageheight>8.5in</pageheight>")
End If
.Append(" <margintop>0.3in</margintop>")
.Append(" <marginleft>0.3in</marginleft>")
.Append(" <marginright>0.3in</marginright>")
.Append(" <marginbottom>0.3in</marginbottom>")
.Append("</deviceinfo>")
End With
Dim warnings() As Warning = Nothing
report.Render("Image", deviceInfo.ToString, _
AddressOf CreateStream, warnings)
For Each s As Stream In streamList
s.Position = 0
Next
deviceInfo = Nothing
End Sub
'''<summary>
''' When the PrintDocument is printing, draw the right page from the list
'''</summary>
Private Sub PrintPage(ByVal sender As Object, _
ByVal ev As PrintPageEventArgs)
Using pageImage As New Metafile(streamList(currentPageIndex))
currentPageIndex += 1
ev.Graphics.DrawImage(pageImage, ev.PageBounds)
ev.HasMorePages = (currentPageIndex < streamList.Count)
End Using
End Sub
'''<summary>
''' Prints the report without preview
'''</summary>
'''<param name="report">Report Name </param>
Public Sub Print(ByVal report As LocalReport, ByVal Orientation As Orientation)
streamList = New List(Of Stream)
' Exports the file to a list of Streams
Call ExportToStream(report, Orientation)
If streamList IsNot Nothing AndAlso streamList.Count > 0 Then
' Start the printing process
Using printDoc As New PrintDocument()
If Not printDoc.PrinterSettings.IsValid Then
Dim msg As String= "Printer is not available or is not valid!"
Throw New ArgumentException(msg)
End If
AddHandler printDoc.PrintPage, AddressOf PrintPage
If Orientation = ReportUtils.Orientation.Portrait Then
printDoc.DefaultPageSettings.Landscape = False
Else
printDoc.DefaultPageSettings.Landscape = True
End If
printDoc.Print()
End Using
End If
End Sub
'''<summary>
''' Prints the report without preview
'''</summary>
'''<param name="report">Report Name</param>
Public Sub Print(ByVal report As LocalReport)
Print(report, Orientation.Portrait)
End Sub
Public Overloads Sub Dispose() Implements IDisposable.Dispose
Try
If streamList IsNot Nothing Then
For Each s As Stream In streamList
s.Close()
Next
streamList.Clear()
streamList = Nothing
End If
If tmpFileName <> String.Empty AndAlso _
IO.File.Exists(tmpFileName) Then
IO.File.Delete(tmpFileName)
End If
tmpFileName = String.Empty
Catch ex As Exception : End Try
End Sub
End Class
然后,您可以这样使用它
Imports Microsoft.Reporting.WinForms
Public Class frmMain
'''<summary>
''' Prints the report
'''</summary>
Private Sub btnPrint_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles btnPrint.Click
Try
Dim rpt As New LocalReport
rpt.ReportPath = Application.StartupPath & "\..\..\rptProducts.rdlc"
' ---------------------------------------------------------
' Define report DataSource, Parameters, etc
' ---------------------------------------------------------
Using cls As New ReportUtils
cls.Print(rpt)
End Using
Catch ex As Exception
MessageBox.Show(ex.Message, My.Application.Info.Title, _
MessageBoxButtons.OK, MessageBoxIcon.Error)
End Try
End Sub
End Class
感谢...
感谢 Gary Lima(又名 VB Rocks),Microsoft Visual Basic MVP,感谢您的所有支持和帮助!
结论
现在就到这里!
我希望本文能帮助您改进报表,并认识到 Microsoft Reports 是一个出色的报表工具。
历史
- 2009 年 7 月 28 日:首次发布。