【问题标题】:Converting XAML to PDF in an UWP app在 UWP 应用中将 XAML 转换为 PDF
【发布时间】:2017-11-25 01:40:03
【问题描述】:

我在 XAML 页面上有一个 Canvas 元素,我在我的 UWP 应用程序中用作打印根。我正在使用诸如 PrintManager.PrintTaskRequested 和 PrintDocument.Paginate 之类的事件来准备我的报告并将其发送到打印机。

我需要以编程方式将报告导出为 PDF 文件。理想的解决方案会以某种方式利用现有的打印根(Canvas),打印到它,然后将结果转换为 PDF。之后,我就可以将 PDF 保存到文件或附加到电子邮件中。

我一直在寻找合适的解决方案,但在 UWP 中没有任何效果。例如,这篇文章提供了一个完美的解决方案,它似乎不适用于 UWP:

How to programmatically print to PDF file without prompting for filename in C# using the Microsoft Print To PDF printer that comes with Windows 10

我将不胜感激。

【问题讨论】:

  • 打印预览对话框应该在您选择打印机的下拉菜单下保存为 PDF。该选项是否不足以满足您的要求?
  • 感谢 AVK 的建议。不,“打印预览”对话框中的“保存到 PDF”选项是不够的。我需要在不涉及“打印预览”对话框的情况下以编程方式执行从 XAML Canvas 到 PDF 的所有转换。转换必须在没有用户交互的情况下在幕后完成。
  • 我认为您可以将页面更改为图片。
  • 感谢 lindexi 的回复。我已经尝试将 XAML Canvas 转换为图像,但输出质量很差。此外,我需要一份 PDF 文档而不是一张大图。
  • 你应该检查this

标签: c# xaml pdf printing uwp


【解决方案1】:

我在这个问题上苦苦挣扎了很长时间 - 在 UWP 中自动、编程的 XAML 到 PDF 的转换 - 最后找到了一个很好的解决方案。

有几个库可以在 UWP 中以编程方式创建 PDF。诀窍是 XAML 转换。我采取了以下方法:

A) 遍历 XAML 树并生成要转换的控件列表。在我的例子中,文本块和边框,但这可以扩展。

B) 声明与 XAML 的实际大小相匹配的 PDF 页面大小。

C) 遍历列表,获取控件坐标。使用 C1PDF 中的适当函数在 PDF 中创建相同的元素。此代码还检查任何 RotateTransforms 并将旋转角度应用于文本。

使用此解决方案,我能够生成与 XAML UI 完全相似的 XAML UI - 它本身代表一个打印文档 - 作为 PDF,完全可扩展并具有完美的打印呈现。

这里有一些代码可以帮助您使用 ComponentOne PDF 控件,我编写这些代码是为了将 XAML 控件转换为 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

实际结果:

【讨论】:

  • 谢谢扎克斯。看起来这就是我想要的。不过,我无法测试您的解决方案。我不久前放弃了将 XAML 转换为 PDF(这是一个有截止日期的真实项目),并使用 Xfinium 从头开始​​创建了整个 PDF。它的工作做得很好。
  • 对不起,我给你的答案太晚了,对你的项目没有帮助。 XAML 作为流文档设计环境有很多优点,其中最重要的是动画和交互性——因此能够在 XAML 中进行设计并呈现真正的 PDF 真的很棒。浏览需要用户交互来进行文件命名和打印机选择的打印机对话框对于大多数人来说是不可行的。
  • 我完全同意。我会将您的答案标记为解决方案。我可能有一天会使用它。
猜你喜欢
  • 1970-01-01
  • 2021-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-11-27
  • 1970-01-01
  • 2018-08-20
  • 1970-01-01
相关资源
最近更新 更多