将BlockUIContainer打印到XpsDocument / FixedDocument

时间:2012-02-25 19:33:51

标签: c# wpf printing flowdocument xpsdocument

问题

  1. 如何打印具有BlockUIContainer
  2. 的FlowDocument
  3. 如何在FlowDocument上强制执行度量/更新/排列?
  4. 背景

    我有一个生成的FlowDocument,其中包含一些文本段落,其中一些Rectangle元素已从资源词典填充DrawingBrushes,而BlockUIContainer则带有自定义控件。

    当文档转换为FixedDocument / XpsDocument时,在任何FlowDocument *控件 HOWEVER 中查看时,文档呈现正确,RectangleBlockUIContainer元素都不是渲染。

    我几乎可以肯定这是因为控件未被测量/排列,但无法弄清楚如何在转换为XpsDocument之前强制执行该操作。

    • 我已递归地走LogicalTree并完成以下操作,

      UIElement element = (UIElement)d;
      element.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
      element.Arrange(new Rect(element.DesiredSize));
      element.UpdateLayout();
      

      其中dDependencyObject。我可以看到,在调试器中出现断点时,这会设置ActualWidthActualHeight属性。

    • 我尝试按照Will ♦的建议强制Dispatcher进行渲染。

    用于打印XpsDocument的代码

    public class XpsDocumentConverter
    {
    
        public static XpsDocumentReference CreateXpsDocument(FlowDocument document)
        {
            // Need to clone the document so that the paginator can work
            FlowDocument clonedDocument = DocumentHelper.Clone<FlowDocument>(document);
    
            Uri uri = new Uri(String.Format("pack://temp_{0}.xps/", Guid.NewGuid().ToString("N")));
            MemoryStream ms = new MemoryStream();
    
            Package pkg = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);
            PackageStore.AddPackage(uri, pkg);
            XpsDocument xpsDocument = new XpsDocument(pkg, CompressionOption.Normal, uri.AbsoluteUri);
    
            XpsSerializationManager rsm = new XpsSerializationManager(new XpsPackagingPolicy(xpsDocument), false);
            DocumentPaginator paginator = new FixedDocumentPaginator(clonedDocument, A4PageDefinition.Default);
            rsm.SaveAsXaml(paginator);
    
            return new XpsDocumentReference(ms, xpsDocument);
        }
    
    }
    

    正如您所看到的,我也使用名为“FixedDocumentPaginator”的自定义DocumentPaginator;但是我不会发布那些代码,因为我怀疑问题是存在的,因为当它开始在GetPage(int pageNumber)中对文档进行分页时,所有内容都已转换为Visual并且布局已经太晚了。


    修改

    嗯。当我输入此内容时,我刚想到克隆文档可能没有完成Measure/Arrange/UpdateLayout

    问题:如何在FlowDocument上强制执行度量/更新/排列?

    我可以工作的一个可能的方法是在其中一个FlowDocumentViewers中显示克隆的文档(可能是在屏幕外)。

    我刚学到并且没有尝试的另一个可能的解决方案是致电:ContextLayoutManager.From(Dispatcher.CurrentDispatcher).UpdateLayout();

    ContextLayoutManager为您走逻辑树并更新布局。

    用于克隆文档的代码

    public static FlowDocument Clone(FlowDocument originalDocument)
    {
        FlowDocument clonedDocument = new FlowDocument();
        TextRange sourceDocument = new TextRange(originalDocument.ContentStart, originalDocument.ContentEnd);
        TextRange clonedDocumentRange = new TextRange(clonedDocument.ContentStart, clonedDocument.ContentEnd);
        try
        {
            using (MemoryStream ms = new MemoryStream())
            {
                sourceDocument.Save(ms, DataFormats.XamlPackage);
                clonedDocumentRange.Load(ms, DataFormats.XamlPackage);
            }
    
            clonedDocument.ColumnWidth = originalDocument.ColumnWidth;
            clonedDocument.PageWidth = originalDocument.PageWidth;
            clonedDocument.PageHeight = originalDocument.PageHeight;
            clonedDocument.PagePadding = originalDocument.PagePadding;
            clonedDocument.LineStackingStrategy = clonedDocument.LineStackingStrategy;
    
            return clonedDocument;
        }
        catch (Exception)
        {               
        }
    
        return null;
    } 
    

2 个答案:

答案 0 :(得分:10)

将此作为与FlowDocument / FixedDocument / XpsDocument具有类似渲染问题的其他人的未来参考发布。

有几点需要注意:

    使用上述方法时,不会克隆
  • BlockUIContainers。直到我使用一些辅助方法将调试窗口中的逻辑树打印出来之后,这并不是很明显(这些方法在下面发布 - 它们非常有用)。
  • 您需要在查看器中显示文档并在屏幕上简要显示。下面是我为此编写的帮助方法。

ForceRenderFlowDocument

private static string ForceRenderFlowDocumentXaml = 
@"<Window xmlns=""http://schemas.microsoft.com/netfx/2007/xaml/presentation""
          xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"">
       <FlowDocumentScrollViewer Name=""viewer""/>
  </Window>";

public static void ForceRenderFlowDocument(FlowDocument document)
{
    using (var reader = new XmlTextReader(new StringReader(ForceRenderFlowDocumentXaml)))
    {
        Window window = XamlReader.Load(reader) as Window;
        FlowDocumentScrollViewer viewer = LogicalTreeHelper.FindLogicalNode(window, "viewer") as FlowDocumentScrollViewer;
        viewer.Document = document;
        // Show the window way off-screen
        window.WindowStartupLocation = WindowStartupLocation.Manual;
        window.Top = Int32.MaxValue;
        window.Left = Int32.MaxValue;
        window.ShowInTaskbar = false;
        window.Show();
        // Ensure that dispatcher has done the layout and render passes
        Dispatcher.CurrentDispatcher.Invoke(DispatcherPriority.Loaded, new Action(() => {}));
        viewer.Document = null;
        window.Close();
    }
}

修改:我刚刚将window.ShowInTaskbar = false添加到方法中,就像您很快就可以看到窗口出现在任务栏中一样。

用户永远不会“看到”窗口,因为它位于Int32.MaxValue的屏幕外 - 这是早期多媒体创作时常见的技巧(例如Macromedia / Adob​​e Director)。

对于搜索和查找此问题的人,我可以告诉您没有其他方式强制文档呈现。

视觉和逻辑树助手

public static string WriteVisualTree(DependencyObject parent)
{
    if (parent == null)
        return "No Visual Tree Available. DependencyObject is null.";

    using (var stringWriter = new StringWriter())
    using (var indentedTextWriter = new IndentedTextWriter(stringWriter, "  "))
    {               
        WriteVisualTreeRecursive(indentedTextWriter, parent, 0);
        return stringWriter.ToString();
    }
}

private static void WriteVisualTreeRecursive(IndentedTextWriter writer, DependencyObject parent, int indentLevel)
{
    if (parent == null)
        return;

    int childCount = VisualTreeHelper.GetChildrenCount(parent);
    string typeName = parent.GetType().Name;
    string objName = parent.GetValue(FrameworkElement.NameProperty) as string;

    writer.Indent = indentLevel;
    writer.WriteLine(String.Format("[{0:000}] {1} ({2}) {3}", indentLevel, 
                                                              String.IsNullOrEmpty(objName) ? typeName : objName, 
                                                              typeName, childCount)
                    );

    for (int childIndex = 0; childIndex < childCount; ++childIndex)
        WriteVisualTreeRecursive(writer, VisualTreeHelper.GetChild(parent, childIndex), indentLevel + 1);
}

public static string WriteLogicalTree(DependencyObject parent)
{
    if (parent == null)
        return "No Logical Tree Available. DependencyObject is null.";

    using (var stringWriter = new StringWriter())
    using (var indentedTextWriter = new IndentedTextWriter(stringWriter, "  "))
    {
        WriteLogicalTreeRecursive(indentedTextWriter, parent, 0);
        return stringWriter.ToString();
    }
}

private static void WriteLogicalTreeRecursive(IndentedTextWriter writer, DependencyObject parent, int indentLevel)
{
    if (parent == null)
        return;

    var children = LogicalTreeHelper.GetChildren(parent).OfType<DependencyObject>();
    int childCount = children.Count();

    string typeName = parent.GetType().Name;
    string objName = parent.GetValue(FrameworkElement.NameProperty) as string;

    double actualWidth = (parent.GetValue(FrameworkElement.ActualWidthProperty) as double?).GetValueOrDefault();
    double actualHeight = (parent.GetValue(FrameworkElement.ActualHeightProperty) as double?).GetValueOrDefault();

    writer.Indent = indentLevel;
    writer.WriteLine(String.Format("[{0:000}] {1} ({2}) {3}", indentLevel,
                                                              String.IsNullOrEmpty(objName) ? typeName : objName,
                                                              typeName, 
                                                              childCount)
                    );

    foreach (object child in LogicalTreeHelper.GetChildren(parent))
    {
        if (child is DependencyObject)
            WriteLogicalTreeRecursive(writer, (DependencyObject)child, indentLevel + 1);
    }

}

<强>用法

#if DEBUG
    Debug.WriteLine("--- Start -------");
    Debug.WriteLine(VisualAndLogicalTreeHelper.WriteLogicalTree(document));
    Debug.WriteLine("--- End -------");
#endif

答案 1 :(得分:6)

我找到了这个解决方案here,它帮助我打印FlowDocment而无需将其渲染到屏幕上......所以我希望它可以帮到你!!

String copyString = XamlWriter.Save(flowDocViewer.Document);
FlowDocument copy = XamlReader.Parse(copyString) as FlowDocument;