2017-06-22 53 views
3

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

我需要以编程方式将报告导出为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

我希望得到任何帮助。

+0

打印预览对话框应该在选择打印机的下拉列表中保存为PDF。这种选择不足以满足您的要求吗? – AVK

+0

感谢AVK的建议。不,打印预览对话框中的保存到PDF选项是不够的。我需要以编程方式执行从XAML Canvas到PDF **的所有转换,而不涉及“打印预览”对话框。转换必须在现场进行,无需用户交互。 – ata6502

+0

我认为你可以将页面更改为图像。 – lindexi

回答

2

我在这个问题上苦苦挣扎了很长一段时间 - 在UWP中自动执行XAML进行PDF转换 - 最后找到了一个出色的解决方案。

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

A)遍历XAML树并生成要转换的控件列表。在我的情况下,文本块和边框,但这可以扩大。 B)声明与您的XAML实际大小相匹配的PDF页面大小。

C)通过列表,得到控制坐标。使用C1PDF中的相应功能在PDF中创建相同的元素。该代码还检查任何RotateTransforms,并将旋转角度应用于文本。

使用此解决方案,我能够将XAML用户界面(本身表示为打印文档)与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 control converted to a PDF

+0

谢谢Zax。看起来这是我正在寻找的东西。尽管如此,我无法测试您的解决方案。前段时间,我放弃将XAML转换为PDF(这是一个具有截止日期的真实项目),并使用Xfinium从头开始创建整个PDF。它的工作很好。 – ata6502

+0

对不起,我在你的项目中得到了一个回答太迟的帮助。 XAML作为一个流程文档设计环境具有很多优势,其中最重要的是动画和交互性 - 所以能够在XAML中设计并渲染真正的PDF是非常好的。浏览需要用户交互性的打印机对话框来进行文件命名和打印机选择对于大多数人来说是非启动器。 – zax

+0

我完全同意。我会将您的答案标记为解决方案。我可能会在一天内使用它。 – ata6502