You cannot use a single FlowDocument
for generating large documents because you will run out of memory. However if it is possible to generate your output as a sequence of FlowDocument
or as an extremely tall ItemsControl
, it is possible.
I've found the easiest way to do this is to subclass DocumentPaginator
and pass an instance of my subclass to XpsDocumentWriter.Write
:
var document = new XpsDocument(...);
var writer = XpsDocument.CreateXpsDocumentWriter(xpsDocument);
writer.Write(new WidgetPaginator { Widget = widgetBeingPrinted, PageSize = ... });
The WidgetPaginator
class itself is quite simple:
class WidgetPaginator : DocumentPaginator, IDocumentPaginatorSource
{
Size _pageSize;
public Widget Widget { get; set; }
public override Size PageSize { get { return _pageSize; } set { _pageSize = value; } }
public override bool IsPageCountValid { return true; }
public override IDocumentPaginatorSource Source { return this; }
public override DocumentPaginator DocumentPaginator { return this; }
public override int PageCount
{
get
{
return ...; // Compute page count
}
}
public override DocumentPage GetPaget(int pageNumber)
{
var visual = ...; // Compute page visual
Rect box = new Rect(0,0,_pageSize.With, _pageSize.Height);
return new DocumentPage(visual, _pageSize, box, box);
}
Of course you still have to write the code that actually creates the pages.
If you want to use a series of FlowDocuments to create your document
If you're using a sequence of FlowDocuments
to lay out your document one section at a time instead of all at once, your custom paginator can work in two passes:
- The first pass occurs as the paginator is constructed. It creates a
FlowDocument
for each section, then gets a DocumentPaginator
to retrieve the number of pages. Each section's FlowDocument
is discarded after the pages are counted.
- The second pass occurs during actual document output: If the number passed to
GetPage()
is in the most recent FlowDocument
created, GetPage()
simply calls that document's paginator to get the appropriate page. Otherwise it discards that FlowDocument and creates a FlowDocument
for the new section, gets its paginator, then calls GetPage()
on the paginator.
This strategy allows you to continue to use FlowDocuments
as you have been, as long as you can break the data into "sections" each with its own document. Your custom paginator then effectively treats all the individual FlowDocuments as one big document. This is similar to Word's "Master Document" feature.
If you can render your data as a sequence of vertically-stacked visuals
In this case, the same technique can be used. During the first pass, all visuals are generated in order and measured to see how many will fit on a page. A data structure is built to indicate which range of visuals (by index or whatever) are found on a given page. During this process each time a page fills up, the next visual is placed on a new page. Headers and footers would be handled in the obvious way.
During the actual document generation, the GetPage()
method is implemented to regenerate the visuals previously decided to be on a given page and combine them using a vertical DockPanel or other panel of your choice.
I've found this technique more flexible in the long run because you don't have to deal with the limitations of FlowDocument
.