将BlockUIContainer打印到XpsDocument / FixedDocument

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

背景

我有一个生成的FlowDocument ,其中包含一些文本段落,其中一些Rectangle元素填充了来自资源字典的DrawingBrushes和带有自定义控件的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 。 我可以看到,这在调试器中以break-pointed为单位时设置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(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为您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; } 

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

有几点需要注意:

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

ForceRenderFlowDocument

 private static string ForceRenderFlowDocumentXaml = @"  "; 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(); 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 

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

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