【问题标题】:Printing BlockUIContainer to XpsDocument/FixedDocument将 BlockUIContainer 打印到 XpsDocument/FixedDocument
【发布时间】:2012-03-15 21:10:32
【问题描述】:

问题

  1. 如何打印具有BlockUIContainer 的 FlowDocument?
  2. 如何强制对 FlowDocument 进行测量/更新/排列?

背景

我生成了一个带有文本段落的 FlowDocument,其中包含一些来自资源字典的 DrawingBrushes 填充的 Rectangle 元素和带有自定义控件的 BlockUIContainer

当文档被转换为 FixedDocument/XpsDocument 时,在任何 FlowDocument* 控件中查看时,文档都能正确呈现。但是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 属性。

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

用于打印 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;
} 

【问题讨论】:

    标签: c# wpf printing flowdocument xpsdocument


    【解决方案1】:

    将此作为将来的参考,以供其他与 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/Adobe 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
    

    【讨论】:

    • 非常感谢,我自己刚刚经历了这个地狱,你帮了很多忙=)
    • XpsDocumentReference 发生了什么?我一直尝试通过GetFixedDocumentSequence().DocumentPaginator 从 XPS 文档中拉出DocumentPaginator 来从PrintDialog 打印。我得到一个由 InvalidURI 异常导致的 Xaml 解析异常。显然,固定的文档 URI 以某种方式被破坏了。
    • @AustinMullins 看看我在stackoverflow.com/questions/9647401/… 的回答,它显示了XpsDocumentReference 的实现。自从我看 XPS 以来已经有几年了。很高兴再次访问和帮助。这将是创建开源库的好机会。
    • @AustinMullins 再次阅读该答案,我现在想起了找出无效 URI 异常所经历的痛苦。
    • 嗨@Dennis,你是如何设法用 BlockUIContainer 克隆 FlowDocument 的?
    【解决方案2】:

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

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

    【讨论】:

    • XamlWriter.Save 期间将ItemsControl 嵌入BlockUIElement 时遇到了无限递归循环。我认为问题在于 ItemsControl 的某些属性引用了它的容器,而 MarkupWriter.RecordNamespaces 函数递归地导航要保存的项目的每个属性。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-10-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多