[英]Printing BlockUIContainer to XpsDocument/FixedDocument
Question 题
BlockUIContainer
? BlockUIContainer
的FlowDocument? Background 背景
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
,其中包含一些文本段落,其中一些Rectangle
元素填充了来自资源字典的DrawingBrushes
和带有自定义控件的BlockUIContainer
。
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. 在任何的FlowDocument *的观察时,所述文档正确呈现控制然而 ,当文档被转换为一固定文档/ XpsDocument,没有的
Rectangle
或BlockUIContainer
元素渲染。
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. 我几乎可以肯定这是因为没有测量/安排控件,但是无法弄清楚如何在转换为XpsDocument之前强制执行。
I have walked the LogicalTree
recursively and done the following, 我已经递归地走了
LogicalTree
,完成了以下工作,
UIElement element = (UIElement)d; element.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity)); element.Arrange(new Rect(element.DesiredSize)); element.UpdateLayout();
where d
is a DependencyObject
. 其中
d
是DependencyObject
。 I can see that this sets the ActualWidth
and ActualHeight
properties when break-pointed in the debugger. 我可以看到,这在调试器中以break-pointed为单位时设置
ActualWidth
和ActualHeight
属性。
I have tried forcing the Dispatcher
to render as suggested by Will ♦ . 我已经尝试强制
Dispatcher
按照Will♦的建议进行渲染。
Code used to print the XpsDocument 用于打印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);
}
}
As you can see I'm also using a custom DocumentPaginator
named 'FixedDocumentPaginator'; 如您所见,我还使用名为'FixedDocumentPaginator'的自定义
DocumentPaginator
; 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. 但是我不会发布那些代码,因为我怀疑问题是存在的,因为当它开始在
GetPage(int pageNumber)
对文档进行分页时,所有内容都已经转换为Visual
,而且布局已经太晚了。
Edit 编辑
Hmm. 嗯。 As I typed this, a thought just occurred to me that the cloned document may not have had a
Measure/Arrange/UpdateLayout
done. 当我输入这个时,我想到克隆文档可能没有完成
Measure/Arrange/UpdateLayout
。
Question: How can I force a Measure/Update/Arrange on a FlowDocument? 问题:如何在FlowDocument上强制执行度量/更新/排列?
A possible hack that I could work would be to show the cloned document in one of the FlowDocumentViewers (perhaps off-screen). 我可以工作的一个可能的方法是在一个FlowDocumentViewers中显示克隆的文档(可能在屏幕外)。
Another possible solution that I just learnt about and haven't tried would be to call: ContextLayoutManager.From(Dispatcher.CurrentDispatcher).UpdateLayout();
我刚学到并且没有尝试过的另一个可能的解决方案是调用:
ContextLayoutManager.From(Dispatcher.CurrentDispatcher).UpdateLayout();
ContextLayoutManager
walks the logical tree for you and updates the layout. ContextLayoutManager
为您ContextLayoutManager
逻辑树并更新布局。
Code used for cloning the document 用于克隆文档的代码
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;
}
Posting this as future reference for others that are having similar rendering issues with FlowDocument/FixedDocument/XpsDocument. 将此作为与FlowDocument / FixedDocument / XpsDocument具有类似呈现问题的其他人的未来参考发布。
A few things to note: 有几点需要注意:
BlockUIContainers
are not cloned when you use the above method. BlockUIContainers
。 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). ForceRenderFlowDocument 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();
}
}
Edit: I just added window.ShowInTaskbar = false
to the method as if you were quick you could see the window appear in the taskbar. 编辑:我刚刚将
window.ShowInTaskbar = false
添加到方法中,就像你很快就可以看到窗口出现在任务栏中。
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 (eg Macromedia/Adobe Director). 用户将永远不会“看到”窗口,因为它位于屏幕上的
Int32.MaxValue
- 这是早期多媒体创作(例如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. 对于搜索和查找此问题的人,我可以告诉您, 没有其他方法可以强制文档呈现。
Visual and Logical Tree Helpers 视觉和逻辑树助手
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);
}
}
Usage 用法
#if DEBUG
Debug.WriteLine("--- Start -------");
Debug.WriteLine(VisualAndLogicalTreeHelper.WriteLogicalTree(document));
Debug.WriteLine("--- End -------");
#endif
I found this solution here , and it helped me get the printing of the FlowDocment without having to render it off screen...So I hope it can help you!! 我在这里找到了这个解决方案,它帮助我完成了FlowDocment的打印而无需将其渲染到屏幕上......所以我希望它可以帮助你!
String copyString = XamlWriter.Save(flowDocViewer.Document);
FlowDocument copy = XamlReader.Parse(copyString) as FlowDocument;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.