在 UWP 应用中将 XAML 转换为 PDF [英] Converting XAML to PDF in an UWP app
问题描述
我在 XAML 页面上有一个 Canvas 元素,我在 UWP 应用中将其用作打印根.我正在使用 PrintManager.PrintTaskRequested 和 PrintDocument.Paginate 等事件来准备我的报告并将其发送到打印机.
我需要以编程方式将报告导出为 PDF 文件.理想的解决方案是以某种方式利用现有的打印根(画布),打印到它,然后将结果转换为 PDF.之后,我就可以将 PDF 保存到文件中或将其附加到电子邮件中.
我一直在寻找合适的解决方案,但在 UWP 中没有任何效果.例如,这篇文章提供了一个完美的解决方案,它在 UWP 中似乎不起作用:
I have a Canvas element on a XAML page that I'm using as a printing root in my UWP app. I'm using events such as PrintManager.PrintTaskRequested and PrintDocument.Paginate to prepare my report and send it to a printer.
I need to export the report programmatically to a PDF file. The ideal solution would somehow utilize the existing printing root (the Canvas), print to it, and then convert the result to PDF. After that, I would be able to save PDF to a file or attach it to an email.
I've been searching for a suitable solution for a while but nothing works in UWP. For example, this post provides a perfect solution it does not seem to work in UWP:
I would appreciate any help.
I struggled with this issue for a long time - automatic, programatic XAML to PDF conversion in UWP - and finally figured out an excellent solution.
There are several libraries for creating PDFs programatically in UWP. The trick is the XAML conversion. I took the following approach:
A) Traverse the XAML tree and generate a list of controls to be converted. In my case, textblocks and borders, but this could be expanded.
B) Declare a PDF page size matching your XAML's actual size.
C) Go through the list, get the control coordinates. Use the appropriate function in C1PDF to create the same element in the PDF. This code also checks for any RotateTransforms and applies the angle of rotation to the text as well.
Using this solution, I was able to produce an exact likeness of the XAML UI - which itself represented a print document - as a PDF, fully scalable and with perfect print rendering.
Here's some code to get you on your way, using the ComponentOne PDF controls, that I wrote to convert my XAML control to PDF:
Async Function XAMLtoPDF(myXAMLcontrol As Control) As Task(Of Boolean)
Dim pdf As C1PdfDocument
pdf = New C1PdfDocument(PaperKind.Letter)
Dim lTB As New List(Of Object)
pdf.PageSize = New Size(myXAMLcontrol.ActualWidth, myXAMLcontrol.ActualHeight)
FindTextBlocks(myXAMLcontrol, lTB)
For x = 0 To lTB.Count - 1
If TypeOf lTB(x) Is TextBlock Then
Dim TB As TextBlock = lTB(x)
Dim obj As FrameworkElement = TB
Dim angle As Double = 0
Do While obj IsNot Nothing
Dim renderxform As Transform = obj.RenderTransform
If TypeOf renderxform Is TransformGroup Then
Dim tg As TransformGroup = CType(renderxform, TransformGroup)
For Each t As Transform In tg.Children
If TypeOf t Is RotateTransform Then
angle -= CType(t, RotateTransform).Angle
End If
Next
ElseIf TypeOf renderxform Is RotateTransform Then
angle -= CType(renderxform, RotateTransform).Angle
End If
obj = obj.Parent
Loop
Dim myfont As Font
Select Case TB.FontStyle
Case FontStyle.Normal
If TB.FontWeight.Weight = FontWeights.Bold.Weight Then
myfont = New Font(TB.FontFamily.Source, TB.FontSize, PdfFontStyle.Bold)
Else
myfont = New Font(TB.FontFamily.Source, TB.FontSize, PdfFontStyle.Regular)
End If
Case Else 'FontStyle.Oblique, FontStyle.Italic '
myfont = New Font(TB.FontFamily.Source, TB.FontSize, PdfFontStyle.Italic)
End Select
Dim ttv As GeneralTransform = TB.TransformToVisual(myXAMLcontrol)
Dim ScreenCoords As Point = ttv.TransformPoint(New Point(0, 0))
Dim myWidth As Double, myHeight As Double
If TB.TextWrapping = TextWrapping.NoWrap Then
myWidth = pdf.MeasureString(TB.Text, myfont).Width
myHeight = pdf.MeasureString(TB.Text, myfont).Height
Else
myWidth = TB.ActualWidth + 10 'Admittedly, 10 is a kluge factor to make wrapping match'
myHeight = pdf.MeasureString(TB.Text, myfont, myWidth).Height
End If
Dim rc As New Rect(ScreenCoords.X, ScreenCoords.Y, myWidth, myHeight)
If angle Then
Dim fmt As New StringFormat()
fmt.Angle = angle
pdf.DrawString(TB.Text, myfont, CType(TB.Foreground, SolidColorBrush).Color, rc, fmt)
Else
pdf.DrawString(TB.Text, myfont, CType(TB.Foreground, SolidColorBrush).Color, rc)
End If
ElseIf TypeOf lTB(x) Is Border Then
Dim BDR As Border = lTB(x)
Dim ttv As GeneralTransform = BDR.TransformToVisual(myXAMLcontrol)
Dim ScreenCoords As Point = ttv.TransformPoint(New Point(0, 0))
Dim pts() As Point = {
New Point(ScreenCoords.X, ScreenCoords.Y),
New Point(ScreenCoords.X + BDR.ActualWidth, ScreenCoords.Y),
New Point(ScreenCoords.X + BDR.ActualWidth, ScreenCoords.Y + BDR.ActualHeight),
New Point(ScreenCoords.X, ScreenCoords.Y + BDR.ActualHeight)}
Dim Clr As Color = CType(BDR.BorderBrush, SolidColorBrush).Color
If BDR.BorderThickness.Top Then pdf.DrawLine(New Pen(Clr, BDR.BorderThickness.Top), pts(0), pts(1))
If BDR.BorderThickness.Right Then pdf.DrawLine(New Pen(Clr, BDR.BorderThickness.Right), pts(1), pts(2))
If BDR.BorderThickness.Bottom Then pdf.DrawLine(New Pen(Clr, BDR.BorderThickness.Bottom), pts(2), pts(3))
If BDR.BorderThickness.Left Then pdf.DrawLine(New Pen(Clr, BDR.BorderThickness.Left), pts(3), pts(0))
ElseIf TypeOf lTB(x) Is Rectangle Then
Dim Rect As Rectangle = lTB(x)
Dim ttv As GeneralTransform = Rect.TransformToVisual(myXAMLcontrol)
Dim ScreenCoords As Point = ttv.TransformPoint(New Point(0, 0))
Dim pts() As Point = {
New Point(ScreenCoords.X + Rect.Margin.Left, ScreenCoords.Y + Rect.Margin.Top),
New Point(ScreenCoords.X + Rect.ActualWidth - Rect.Margin.Right, ScreenCoords.Y + Rect.Margin.Top),
New Point(ScreenCoords.X + Rect.ActualWidth - Rect.Margin.Right, ScreenCoords.Y + Rect.ActualHeight - Rect.Margin.Bottom),
New Point(ScreenCoords.X + Rect.Margin.Left, ScreenCoords.Y + Rect.ActualHeight - Rect.Margin.Bottom)}
Dim MyPen1 As New Pen(CType(Rect.Stroke, SolidColorBrush).Color, Rect.StrokeThickness)
MyPen1.DashStyle = DashStyle.Custom
MyPen1.DashPattern = Rect.StrokeDashArray.ToArray
Dim MyPen2 As New Pen(CType(Rect.Stroke, SolidColorBrush).Color, Rect.StrokeThickness)
MyPen2.DashStyle = DashStyle.Custom
MyPen2.DashPattern = Rect.StrokeDashArray.ToArray
pdf.DrawLine(MyPen2, pts(0), pts(1))
pdf.DrawLine(MyPen1, pts(1), pts(2))
pdf.DrawLine(MyPen2, pts(2), pts(3))
pdf.DrawLine(MyPen1, pts(3), pts(0))
End If
Next
Dim file As StorageFile = Await ThisApp.AppStorageFolder.CreateFileAsync("Temp.PDF", Windows.Storage.CreationCollisionOption.ReplaceExisting)
Await pdf.SaveAsync(file)
Return True
End Function
Private Sub FindTextBlocks(uiElement As Object, foundOnes As IList(Of Object))
If TypeOf uiElement Is TextBlock Then
Dim uiElementAsTextBlock = DirectCast(uiElement, TextBlock)
If uiElementAsTextBlock.Visibility = Visibility.Visible Then
foundOnes.Add(uiElementAsTextBlock)
End If
ElseIf TypeOf uiElement Is Panel Then
Dim uiElementAsCollection = DirectCast(uiElement, Panel)
If uiElementAsCollection.Visibility = Visibility.Visible Then
For Each element In uiElementAsCollection.Children
FindTextBlocks(element, foundOnes)
Next
End If
ElseIf TypeOf uiElement Is UserControl Then
Dim uiElementAsUserControl = DirectCast(uiElement, UserControl)
If uiElementAsUserControl.Visibility = Visibility.Visible Then
FindTextBlocks(uiElementAsUserControl.Content, foundOnes)
End If
ElseIf TypeOf uiElement Is ContentControl Then
Dim uiElementAsContentControl = DirectCast(uiElement, ContentControl)
If uiElementAsContentControl.Visibility = Visibility.Visible Then
FindTextBlocks(uiElementAsContentControl.Content, foundOnes)
End If
ElseIf TypeOf uiElement Is Border Then
Dim uiElementAsBorder = DirectCast(uiElement, Border)
If uiElementAsBorder.Visibility = Visibility.Visible Then
foundOnes.Add(uiElementAsBorder)
FindTextBlocks(uiElementAsBorder.Child, foundOnes)
End If
ElseIf TypeOf uiElement Is Rectangle Then
Dim uiElementAsRectangle = DirectCast(uiElement, Rectangle)
foundOnes.Add(uiElementAsRectangle)
End If
End Sub
Actual results:
这篇关于在 UWP 应用中将 XAML 转换为 PDF的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!