将 WPF 页面导出为 PDF 文件
此示例演示了如何从一个或一组 WPF 页面创建 PDF 报告。
引言
由于没有开箱即用的解决方案来创建 PDF 报告(或者我没有找到任何解决方案),我们创建了一组类和说明来支持此操作。
背景
在开发一个 WPF 应用程序(要在 PC 和带有触摸屏的笔记本电脑上使用)后,我们被要求在该应用程序中创建创建 PDF 报告的可能性。在网上搜索了很多内容并尝试了我们找到的内容之后,我们提出了一个相当简单的解决方案。
使用代码
在我们的应用程序中,我们使用带有菜单和命令按钮的主屏幕。然后,每个菜单项或命令按钮都可以在此主屏幕中打开一个用户控件。这样,我们可以跟踪导航并实现返回上一页和在同一主题中前进的可能性。无论如何,我们希望在 PDF 报告中包含这些用户控件的某种屏幕截图。
我们为报告创建了三个类。
1. ReportDocument
Public Class ReportDocument
Private mReportDocument As New FixedDocument
Public Property ReportDocument() As FixedDocument
Get
Return mReportDocument
End Get
Set(ByVal value As FixedDocument)
mReportDocument = value
End Set
End Property
Private mReportPage As New List(Of FixedPage)
Public Property ReportPage() As List(Of FixedPage)
Get
Return mReportPage
End Get
Set(ByVal value As List(Of FixedPage))
mReportPage = value
End Set
End Property
Private mReportWidth As Double
Public Property ReportWidth() As Double
Get
Return mReportWidth
End Get
Set(ByVal value As Double)
mReportWidth = value
End Set
End Property
Private mReportHeight As Double
Public Property ReportHeight() As Double
Get
Return mReportHeight
End Get
Set(ByVal value As Double)
mReportHeight = value
End Set
End Property
Public Sub New(dblWidth As Double, dblHeight As Double)
mReportWidth = dblWidth
mReportHeight = dblHeight
End Sub
Public Function CreateReport() As FixedDocument
'ADD PAGES TO CONTENT TO DOCUMENT
mReportDocument.DocumentPaginator.PageSize = New Size(mReportWidth, mReportHeight)
For Each itemElement In mReportPage
Dim pagContent As New PageContent
DirectCast(pagContent, IAddChild).AddChild(itemElement)
mReportDocument.Pages.Add(pagContent)
Next
Return mReportDocument
End Function
End Class
我们需要有一个具有宽度和高度(在初始化时定义)的 FixedDocument 和一个页面列表 (ReportPages)。函数 CreateReport 会将每个页面作为 PageContent 添加到 FixedDocument。
2. ReportPage
Public Class ReportPage
Private mReportGrid As New Grid
Public Property ReportGrid() As Grid
Get
Return mReportGrid
End Get
Set(ByVal value As Grid)
mReportGrid = value
End Set
End Property
Private mReportWidth As Double
Public Property ReportWidth() As Double
Get
Return mReportWidth
End Get
Set(ByVal value As Double)
mReportWidth = value
End Set
End Property
Private mReportHeight As Double
Public Property ReportHeight() As Double
Get
Return mReportHeight
End Get
Set(ByVal value As Double)
mReportHeight = value
End Set
End Property
Public Sub New(dblWidth As Double, dblheight As Double, blnHeader As Boolean, blnFooter As Boolean)
mReportHeight = dblheight
mReportWidth = dblWidth
'define grid
mReportGrid.Margin = New Thickness(0)
mReportGrid.VerticalAlignment = Windows.VerticalAlignment.Stretch
mReportGrid.HorizontalAlignment = Windows.HorizontalAlignment.Stretch
'CREATE ROW DEFINITION
Dim grdRow1 As New RowDefinition
Dim grdRow2 As New RowDefinition
Dim grdRow3 As New RowDefinition
Dim intLessHeight As Integer = 0
If blnHeader = True Then
grdRow1.Height = New GridLength(70)
intLessHeight += 70
End If
If blnFooter = True Then
grdRow3.Height = New GridLength(50)
intLessHeight += 50
End If
grdRow2.Height = New GridLength(mReportHeight - intLessHeight)
mReportGrid.RowDefinitions.Add(grdRow1)
mReportGrid.RowDefinitions.Add(grdRow2)
mReportGrid.RowDefinitions.Add(grdRow3)
End Sub
Public Sub AddHeader(objHeader As Object)
Dim uctHeaderCode As New UserControl
uctHeaderCode.Content = objHeader
Dim stpHeader As New StackPanel
stpHeader.Orientation = Orientation.Vertical
stpHeader.Width = mReportWidth - 80
stpHeader.Margin = New Thickness(0, 20, 0, 0)
stpHeader.VerticalAlignment = Windows.VerticalAlignment.Top
stpHeader.Children.Add(uctHeaderCode)
Grid.SetRow(stpHeader, 0)
mReportGrid.Children.Add(stpHeader)
End Sub
Public Sub AddFooter(strFooter As String)
Dim txtFooter As New TextBlock
txtFooter.Width = mReportWidth - 150
txtFooter.Margin = New Thickness(0)
txtFooter.FontSize = 8
txtFooter.HorizontalAlignment = HorizontalAlignment.Left
txtFooter.TextWrapping = TextWrapping.WrapWithOverflow
txtFooter.Text = strFooter
Dim txtDatePage As New TextBlock
txtDatePage.Width = 100
txtDatePage.Margin = New Thickness(60, 0, 0, 0)
txtDatePage.FontSize = 8
txtDatePage.HorizontalAlignment = HorizontalAlignment.Right
txtDatePage.TextWrapping = TextWrapping.WrapWithOverflow
txtDatePage.Text = Format(Date.Today, "dd/MM/yyyy").ToString
Dim stpFooter As New StackPanel
stpFooter.Orientation = Orientation.Horizontal
stpFooter.Width = mReportWidth - 50
stpFooter.Margin = New Thickness(0, 0, 0, 20)
stpFooter.VerticalAlignment = Windows.VerticalAlignment.Bottom
stpFooter.Children.Add(txtFooter)
stpFooter.Children.Add(txtDatePage)
Grid.SetRow(stpFooter, 2)
mReportGrid.Children.Add(stpFooter)
End Sub
Public Sub AddContent(elElement As System.Windows.UIElement)
Grid.SetRow(elElement, 1)
mReportGrid.Children.Add(elElement)
End Sub
Private Function CreateLineUnderHeader() As Line
Dim LinLine As New Line
LinLine.StrokeThickness = 1
LinLine.X1 = 20
LinLine.Y1 = 60
LinLine.X2 = mReportWidth - 20
LinLine.Y2 = 60
LinLine.Stroke = Brushes.Black
Return LinLine
End Function
Private Function CreateLineAboveFooter() As Line
Dim LinLine As New Line
LinLine.StrokeThickness = 1
LinLine.X1 = 20
LinLine.Y1 = mReportHeight - 55
LinLine.X2 = mReportWidth - 20
LinLine.Y2 = mReportHeight - 55
LinLine.Stroke = Brushes.Black
Return LinLine
End Function
Public Function CreateReportPage(blnWithLineUnderHeader As Boolean, blnWithLineAboveFooter As Boolean)
Dim tmpPage As New FixedPage
tmpPage.Width = mReportWidth
tmpPage.Height = mReportHeight
tmpPage.Children.Add(mReportGrid)
If blnWithLineUnderHeader Then tmpPage.Children.Add(CreateLineUnderHeader)
If blnWithLineAboveFooter Then tmpPage.Children.Add(CreateLineAboveFooter)
Return tmpPage
End Function
End Class
ReportPage 包含一个网格,该网格具有与文档相同的宽度和高度。在我们的例子中,这个网格包含 3 行:一个页眉、一些内容(我们的用户控件)和一个页脚。由于我们的页眉和页脚是相同的,我们添加了一些方法来添加它们。我们的页眉包含有关我们的“客户”的信息,我们的页脚包含一些法律信息。
我们可以通过 AddContent
方法向此页面添加内容,该方法接受一个 UiElement
作为参数。我们添加了另一个类 (ReportContent
) 的 stackpanel,其中包含我们的用户控件、标签等。
然后我们调用函数 CreateReportPage
,它将网格添加到 fixedpage 并返回此 fixedpage。此 fixedpage 将被添加到我们的 ReportDocument
的页面列表中(参见上文)。
3. ReportContent
Public Class ReportContent
Enum enmTypeOfControl
usercontrol
textBlock
End Enum
Private mReportStackPanel As New StackPanel
Public Property ReportStackPanel() As StackPanel
Get
Return mReportStackPanel
End Get
Set(ByVal value As StackPanel)
mReportStackPanel = value
End Set
End Property
Private mReportWidth As Double
Public Property ReportWidth() As Double
Get
Return mReportWidth
End Get
Set(ByVal value As Double)
mReportWidth = value
End Set
End Property
Private mReportHeight As Double
Public Property ReportHeight() As Double
Get
Return mReportHeight
End Get
Set(ByVal value As Double)
mReportHeight = value
End Set
End Property
Public Sub New(dblWidth As Double, dblheight As Double)
mReportHeight = dblheight
mReportWidth = dblWidth
Dim stpDashboard As New StackPanel
mReportStackPanel.Orientation = Orientation.Vertical
mReportStackPanel.Width = mReportWidth
'mReportStackPanel.Height = mReportHeight
End Sub
Public Function AddElement(objObject As Object, typType As enmTypeOfControl, _
Optional styStyle As Style = Nothing) As Integer
Select Case typType
Case enmTypeOfControl.usercontrol
Dim ucUsercontrol As New UserControl
ucUsercontrol.Margin = New Thickness(5)
ucUsercontrol.Width = mReportWidth - 40
ucUsercontrol.Content = objObject
mReportStackPanel.Children.Add(ucUsercontrol)
Case enmTypeOfControl.textBlock
Dim txtTextBlock As New TextBlock
txtTextBlock.Style = styStyle
txtTextBlock.Width = mReportWidth - 40
txtTextBlock.Text = objObject.ToString
mReportStackPanel.Children.Add(txtTextBlock)
End Select
Return mReportStackPanel.Children.Count - 1
End Function
End Class
在此类初始化时,我们创建了一个与我们实际报告必须具有的宽度和高度相同的 stackpanel。
然后我们可以使用函数 AddElement
添加元素。作为参数,我们添加一个对象(用户控件、textBlock...)、控件的类型(我们使用它进行格式化)和一个样式(可选)。它返回一个 stackpanel,然后将其添加到 ReportPage(参见上文)。
我们创建了一个 Window 来创建实际的报告
我们创建了一个窗口(可以设置为不可见)。我们必须使用一个窗口,因为所有内容都必须有时间进行渲染。在此窗口中,我们可以使用线程来等待内容渲染。
我们的窗口包含一个 docViewer,可用于预览文档。我们尝试不使用此控件,但页面没有完全渲染...
初始化报告
首先,我们使用 InitializeReport 方法初始化一个报告,该方法在窗口渲染时触发 (Me.ContentRendered)
Private dblPrtWidth As Double = 793
Private dblPrtHeight As Double = 1122
Private docRep As ReportDocument
Private docPaga as ReportPage
Public Sub InitializeReport()
docRep = New ReportDocument(dblPrtWidth, dblPrtHeight)
End Sub
创建页面(带有页眉、内容和页脚)
docPage = New ReportPage(dblPrtWidth, dblPrtHeight, True, True)
docPage.AddHeader(New MyHeaderUserControl)
docPage.AddFooter(DirectCast(TryFindResource("pdfFooter"), String))
docPage.AddContent(CreateContent.ReportStackPanel)
Dispatcher.Invoke(Sub() Action(), Windows.Threading.DispatcherPriority.ContextIdle, Nothing)
docRep.ReportPage.Add(docPage.CreateReportPage(True, True))
Dispatcher.Invoke(Sub() Action(), Windows.Threading.DispatcherPriority.ContextIdle, Nothing)
此块创建一个新页面,添加一个页眉(以 brkSignaletique
作为我们的一个用户控件),添加一个页脚(以一些文本作为我们的法律内容),并添加内容(窗口中的一个函数,请参见下一个代码块)。
然后,此页面被添加到 Reports (docRep)
dispatcher.invoke 调用允许代码被渲染,sub Action 只是一个空方法。
创建内容(在上面的页面中使用)
Public Function CreateContent() As ReportContent
'CREATE PAGECONTENT
Dim docContent As New ReportContent(dblPrtWidth, dblPrtHeight)
Dim i As Integer
'ADD TITLE TO PAGECONTENT
docContent.AddElement("TITLE", _
ReportContent.enmTypeOfControl.textBlock, FindResource("ReportTitle"))
Dispatcher.Invoke(Sub() Action(), Windows.Threading.DispatcherPriority.ContextIdle, Nothing)
'A USERCONTROL
objUserControl = New MyUserControl()
Dispatcher.Invoke(Sub() Action(), Windows.Threading.DispatcherPriority.ContextIdle, Nothing)
'ADD TO PAGECONTENT
i = docContent.AddElement(objUserControl , ReportContent.enmTypeOfControl.usercontrol)
Dispatcher.Invoke(Sub() Action(), Windows.Threading.DispatcherPriority.ContextIdle, Nothing)
'Additional actions
DirectCast(DirectCast(docContent.ReportStackPanel.Children(i), UserControl).Content, _
MyUserControl).imInside.Visibility = Windows.Visibility.Collapsed
Dispatcher.Invoke(Sub() Action(), Windows.Threading.DispatcherPriority.ContextIdle, Nothing)
'ADD SPACE TO PAGECONTENT
docContent.AddElement("", _
ReportContent.enmTypeOfControl.textBlock, FindResource("ReportSpacer"))
Dispatcher.Invoke(Sub() Action(), Windows.Threading.DispatcherPriority.ContextIdle, Nothing)
Return docContent
End Function
此函数在我们的内容中添加了 3 个内容:标题(一个 TextBlock
)、我们的用户控件和一个间隔(空 textblock)。当然,您可以添加任意数量的内容,只要它适合 1 页!当我们将我们的用户控件 (MyUserControl) 添加到 docContent 时,我们获取我们的用户控件在 docContent
的 stackpanel 中的位置。这样,我们可以更改我们的用户控件,例如,隐藏报告中不需要的按钮。
完成报告
Private Sub FinalizeReport()
'CREATE FINAL DOCUMENT
DocViewer.Document = docRep.CreateReport()
Dispatcher.Invoke(Sub() Action(), Windows.Threading.DispatcherPriority.ContextIdle, Nothing)
mFile = "c:\report"
Dim xpsDoc As New XpsDocument(mFile & ".xps", IO.FileAccess.ReadWrite)
Dim XpsDocWriter As Xps.XpsDocumentWriter = XpsDocument.CreateXpsDocumentWriter(xpsDoc)
XpsDocWriter.Write(docRep.ReportDocument)
xpsDoc.Close()
PdfSharp.Xps.XpsConverter.Convert(mFile & ".xps", mFile & ".pdf", 0)
End Sub
我们将 docRep 添加到我们的 docviewer。如果您在那里停止,您可以预览该文档。在这里,我们添加了一些东西来创建 XPF,然后使用 PdfSharp XPS 转换器(要下载并添加到引用)将其转换为 PDF。
这就是窍门。有什么问题吗?问吧!
祝你好运,Wim