本文介绍了将 BlockUIContainer 打印到 XpsDocument/FixedDocument的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题

  1. 如何打印具有 BlockUIContainer 的 FlowDocument?
  2. 如何强制对 FlowDocument 进行度量/更新/安排?
  1. How do you print a FlowDocument that have BlockUIContainer?
  2. How can I force a Measure/Update/Arrange on a FlowDocument?

背景

我有一个生成的 FlowDocument,其中包含来自资源字典的 DrawingBrushesBlockUIContainer 带有自定义控件.

I have a generated FlowDocument with paragraphs of text with a few Rectangle elements filled DrawingBrushes from a resource dictionary and BlockUIContainer with custom controls.

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

The document renders correctly when viewed in any of the FlowDocument* controls HOWEVER when the document is converted to a FixedDocument/XpsDocument, none of the Rectangle or BlockUIContainer elements render.

我几乎可以肯定这是因为控件没有被测量/排列,但是在它被转换为 XpsDocument 之前无法弄清楚如何强制它发生.

I'm almost certain it is because the control has not been measured/arranged, however cannot figure out how to force that to happen before it is converted to the XpsDocument.

  • 我递归地遍历了 LogicalTree 并完成了以下操作,

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

其中 dDependencyObject.我可以看到这在调试器中设置断点时设置了 ActualWidthActualHeight 属性.

where d is a DependencyObject. I can see that this sets the ActualWidth and ActualHeight properties when break-pointed in the debugger.

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

I have tried forcing the Dispatcher to render as suggested by 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布局太晚了.

As you can see I'm also using a custom DocumentPaginator named 'FixedDocumentPaginator'; however I will not post that code as I doubt the issue is there as by the time it starts paginating the document in GetPage(int pageNumber) everything has already been converted a Visual and it is too late for layout.

编辑

嗯.当我输入这个时,我突然想到克隆的文档可能没有完成Measure/Arrange/UpdateLayout.

Hmm. As I typed this, a thought just occurred to me that the cloned document may not have had a Measure/Arrange/UpdateLayout done.

问题:如何强制对 FlowDocument 进行度量/更新/安排?

Question: How can I force a Measure/Update/Arrange on a FlowDocument?

我可以工作的一个可能的技巧是在 FlowDocumentViewers 之一(可能在屏幕外)中显示克隆的文档.

A possible hack that I could work would be to show the cloned document in one of the FlowDocumentViewers (perhaps off-screen).

我刚刚了解到但尚未尝试的另一种可能的解决方案是调用:ContextLayoutManager.From(Dispatcher.CurrentDispatcher).UpdateLayout();

Another possible solution that I just learnt about and haven't tried would be to call: ContextLayoutManager.From(Dispatcher.CurrentDispatcher).UpdateLayout();

ContextLayoutManager 为您遍历逻辑树并更新布局.

ContextLayoutManager walks the logical tree for you and updates the layout.

用于克隆文档的代码

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;
}

推荐答案

将此作为未来参考,供其他在 FlowDocument/FixedDocument/XpsDocument 中遇到类似渲染问题的人使用.

Posting this as future reference for others that are having similar rendering issues with FlowDocument/FixedDocument/XpsDocument.

需要注意的几点:

    使用上述方法时,
  • BlockUIContainers 不会被克隆.这并不是很明显,直到我使用一些辅助方法将逻辑树打印出调试窗口(这些方法在下面发布 - 它们非常有用).
  • 您需要在查看器中显示文档并在屏幕上简要显示它.下面是我为我写的帮助方法.
  • BlockUIContainers are not cloned when you use the above method. This was not immediately obvious until I printed the logical tree out the debug window using some helper methods (these methods are posted below - they are incredibly useful).
  • You need to display the document in a viewer and briefly show it on screen. Below is the helper method that I wrote to do this for me.

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 添加到方法中,就好像您很快可以看到窗口出现在任务栏中一样.

I just added window.ShowInTaskbar = false to the method as if you were quick you could see the window appear in the taskbar.

用户永远不会看到"窗口,因为它位于 Int32.MaxValue 的屏幕外——这是早期多媒体创作(例如 Macromedia/Adobe导演).

The user will never "see" the window as it is positioned way off-screen at Int32.MaxValue - a trick that was common back in the day with early multimedia authoring (e.g. Macromedia/Adobe Director).

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

For people searching and finding this question, I can tell you that there is no other way to force the document to render.

视觉和逻辑树助手

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

这篇关于将 BlockUIContainer 打印到 XpsDocument/FixedDocument的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-23 09:21