在UWP应用中将XAML转换为PDF [英] Converting XAML to PDF in an UWP app

查看:99
本文介绍了在UWP应用中将XAML转换为PDF的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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.

我需要以编程方式将报告导出为PDF文件.理想的解决方案将以某种方式利用现有的打印根目录(画布),对其进行打印,然后将结果转换为PDF.之后,我将能够将PDF保存到文件或将其附加到电子邮件.

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.

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

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:

如何使用Windows 10附带的Microsoft Print To PDF打印机以编程方式打印到PDF文件,而无需在C#中提示输入文件名

我将不胜感激.

推荐答案

我在这个问题上苦苦挣扎了很长时间-在UWP中将程序化XAML自动转换为PDF-最终找到了一个很好的解决方案.

I struggled with this issue for a long time - automatic, programatic XAML to PDF conversion in UWP - and finally figured out an excellent solution.

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

There are several libraries for creating PDFs programatically in UWP. The trick is the XAML conversion. I took the following approach:

A)遍历XAML树并生成要转换的控件列表.就我而言,是文本块和边框,但是可以扩展.

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)声明与XAML实际大小相匹配的PDF页面大小.

B) Declare a PDF page size matching your XAML's actual size.

C)浏览列表,获取控制坐标.在C1PDF中使用适当的功能在PDF中创建相同的元素.该代码还将检查所有RotateTransforms,并将旋转角度也应用于文本.

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.

使用该解决方案,我能够将XAML UI(其本身代表打印文档)生成为PDF,完全可缩放且具有完美的打印渲染效果,与XAML UI完全相似.

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.

以下是我使用ComponentOne PDF控件编写的将XAML控件转换为PDF的一些代码:

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

实际结果:

这篇关于在UWP应用中将XAML转换为PDF的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆