Question
- How do you print a FlowDocument that have
BlockUIContainer
? - How can I force a Measure/Update/Arrange on a 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.
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.
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.
I have walked the
LogicalTree
recursively and done the following,UIElement element = (UIElement)d; element.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity)); element.Arrange(new Rect(element.DesiredSize)); element.UpdateLayout();
where
d
is aDependencyObject
. I can see that this sets theActualWidth
andActualHeight
properties when break-pointed in the debugger.I have tried forcing the
Dispatcher
to render as suggested by Will ♦.
Code used to print the 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'; 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.
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.
Question: How can I force a Measure/Update/Arrange on a FlowDocument?
A possible hack that I could work would be to show the cloned document in one of the FlowDocumentViewers (perhaps off-screen).
Another possible solution that I just learnt about and haven't tried would be to call: ContextLayoutManager.From(Dispatcher.CurrentDispatcher).UpdateLayout();
ContextLayoutManager
walks the logical tree for you and updates the layout.
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.
A few things to note:
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).ForceRenderFlowDocument
Edit: I just added
window.ShowInTaskbar = false
to the method as if you were quick you could see the window appear in the taskbar.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.
Visual and Logical Tree Helpers
Usage
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!!