问题
- 如何打印具有
BlockUIContainer
的FlowDocument - 如何强制对FlowDocument进行度量/更新/排列
背景
我有一个生成的FlowDocument
,其中包含一些Rectangle
元素的文本段落,这些元素填充了资源字典中的DrawingBrushes
,而BlockUIContainer
则包含自定义控件。
当在任何FlowDocument*控件中查看时,文档都会正确呈现HOWEVER当文档转换为FixedDocument/XpsDocument时,Rectangle
或BlockUIContainer
元素都不会呈现。
我几乎可以肯定,这是因为控件没有被测量/排列,但在转换为XpsDocument之前,我不知道如何强制执行。
-
我已经递归地遍历了
LogicalTree
,并完成了以下操作,UIElement element = (UIElement)d; element.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity)); element.Arrange(new Rect(element.DesiredSize)); element.UpdateLayout();
其中CCD_ 9是CCD_。我可以看到,当调试器中出现断点时,这会设置
ActualWidth
和ActualHeight
属性。 -
我已尝试强制
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进行度量/更新/排列?
我可以使用的一个可能的破解方法是在一个FlowDocumentViewer中显示克隆的文档(可能是在屏幕外)。
另一个可能的解决方案,我刚刚了解并没有尝试过,将是调用: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;
}
将其发布为其他使用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
我在这里找到了这个解决方案,它帮助我打印FlowDocment,而不必在屏幕外渲染它。。。所以我希望它能帮助你!!
String copyString = XamlWriter.Save(flowDocViewer.Document);
FlowDocument copy = XamlReader.Parse(copyString) as FlowDocument;