Finally, I'm going to finish my WPF reporting post series. If you haven't read my previous posts , I strongly recommend to read it, to have a correct context.
Today I will focus on some leftovers , the DocumentWalker and ReportPaginator classes.
A custom report pagination can be achieved by subclassing the DocumentPaginator class and overriding the GetPage method. In my implementation, I always get the page from the orginal flow document paginator:
DocumentPage page = paginator.GetPage(pageNumber);
create a visual container for new page content
ContainerVisual newpage = new ContainerVisual();
If there is a page header , add it to the container
if (pageDef.HeaderTemplate != null)
{
ContainerVisual v = getPartVisual(pageDef.HeaderTemplate, pageNumber);
v.Offset = new Vector(pageDef.Margin.Width, pageDef.Margin.Height);
newpage.Children.Add(v);
}
Resize and add the orginal page content
ContainerVisual smallerPage = new ContainerVisual();
smallerPage.Children.Add(page.Visual);
smallerPage.Offset = new Vector(pageDef.Margin.Width, pageDef.HeaderHeight + pageDef.Margin.Height + minimalOffset);
newpage.Children.Add(smallerPage);
If there is a page footer , add it to the container
if (pageDef.FooterTemplate != null)
{
ContainerVisual footer = getPartVisual(pageDef.FooterTemplate,pageNumber);
footer.Offset = new Vector(pageDef.Margin.Width, pageSize.Height - pageDef.FooterHeight - pageDef.Margin.Height-minimalOffset);
newpage.Children.Add(footer);
}
And finnally create a new DocumentPage instance
return new DocumentPage(newpage,new Size(pageSize.Width, pageSize.Height),page.BleedBox,page.ContentBox);
The other stuff in the class serves mainly as helper methods. The full source of the class:
public class ReportPaginator : DocumentPaginator
{
Size pageSize;
/// <summary>
/// Reference to a original flowdoc paginator
/// </summary>
DocumentPaginator paginator;
PageDefinition pageDef;
/// <summary>
/// Real total page count number
/// </summary>
int pageCount;
/// <summary>
/// Minimal space between page header/footer and page content
/// </summary>
int minimalOffset = 25;
/// <summary>
/// Helper method to create page header o footer from flow document template
/// </summary>
/// <param name="fd"></param>
/// <param name="pageDef"></param>
/// <returns></returns>
public static XpsDocument CreateXpsDocument(FlowDocument fd,PageDefinition pageDef)
{
MemoryStream ms = new MemoryStream();
Package pkg = Package.Open(ms, FileMode.Create, FileAccess.ReadWrite);
string pack = "pack://report.xps";
PackageStore.AddPackage(new Uri(pack), pkg);
XpsDocument doc = new XpsDocument(pkg, CompressionOption.SuperFast, pack);
XpsSerializationManager rsm = new XpsSerializationManager(new XpsPackagingPolicy(doc), false);
DocumentPaginator paginator = ((IDocumentPaginatorSource)fd).DocumentPaginator;
ReportPaginator rp = new ReportPaginator(paginator, PrintHelper.GetPageSize(), pageDef);
rsm.SaveAsXaml(rp);
return doc;
}
public ReportPaginator(DocumentPaginator paginator, Size pageSize, PageDefinition pd)
{
this.pageSize = pageSize;
this.paginator = paginator;
this.pageDef = pd;
//decrease original page
paginator.PageSize = new Size(pageSize.Width - pd.Margin.Width * 2, pageSize.Height -2*minimalOffset - pd.HeaderHeight - pd.FooterHeight - pd.Margin.Height * 2);
}
ContainerVisual getPartVisual(string template,int pageNo)
{
template = template.Replace("@PageNumber", (pageNo+1).ToString());
template = template.Replace("@PageCount", pageCount.ToString());
Section ph = ReportEngine.createReportPart<Section>(template, null);
FlowDocument tmpDoc = new FlowDocument();
tmpDoc.ColumnWidth = double.PositiveInfinity;
tmpDoc.PageWidth = paginator.PageSize.Width;
tmpDoc.Blocks.Add(ph);
DocumentPage dp = ((IDocumentPaginatorSource)tmpDoc).DocumentPaginator.GetPage(0);
return (ContainerVisual)dp.Visual;
}
/// <summary>
/// This is the most importan method , modifies the original
/// </summary>
public override DocumentPage GetPage(int pageNumber)
{
DocumentPage page = paginator.GetPage(pageNumber);
if (pageNumber == 0)
{
paginator.ComputePageCount();
pageCount = paginator.PageCount;
}
ContainerVisual newpage = new ContainerVisual();
if (pageDef.HeaderTemplate != null)
{
ContainerVisual v = getPartVisual(pageDef.HeaderTemplate, pageNumber);
v.Offset = new Vector(pageDef.Margin.Width, pageDef.Margin.Height);
newpage.Children.Add(v);
}
ContainerVisual smallerPage = new ContainerVisual();
smallerPage.Children.Add(page.Visual);
smallerPage.Offset = new Vector(pageDef.Margin.Width, pageDef.HeaderHeight + pageDef.Margin.Height + minimalOffset);
newpage.Children.Add(smallerPage);
if (pageDef.FooterTemplate != null)
{
ContainerVisual footer = getPartVisual(pageDef.FooterTemplate,pageNumber);
footer.Offset = new Vector(pageDef.Margin.Width, pageSize.Height - pageDef.FooterHeight - pageDef.Margin.Height-minimalOffset);
newpage.Children.Add(footer);
}
DocumentPage dp= new DocumentPage(newpage,new Size(pageSize.Width, pageSize.Height),page.BleedBox,page.ContentBox);
return dp;
}
#region DefaultOverrides
public override bool IsPageCountValid
{
get { return paginator.IsPageCountValid; }
}
public override int PageCount
{
get { return paginator.PageCount;}
}
public override Size PageSize
{
get {return pageSize;}
set { pageSize = value; }
}
public override IDocumentPaginatorSource Source
{
get {return paginator.Source;}
}
#endregion
}
The Document walker class enables a traversal of the flow document tree and raises an event for each node in the document tree. I used it to find all instances of a FormattedRun control in the flowdocument defintion. It is pretty straightforward, so I will just show the code itself.
/// <summary>
/// THe delegate type of the event that will be raised
/// </summary>
public delegate void DocumentVisitedEventHandler(Object sender, object visitedObject, bool start);
public class DocumentWalker
{
/// <summary>
/// This is the event to hook on.
/// </summary>
public event DocumentVisitedEventHandler VisualVisited;
/// <summary>
/// Traverses whole document
/// </summary>
/// <param name="fd"></param>
public void Walk(FlowDocument fd)
{
TraverseBlockCollection(fd.Blocks);
}
/// <summary>
/// Traverses only passed paragraph
/// </summary>
/// <param name="p"></param>
public void TraverseParagraph(Paragraph p)
{
if (p.Inlines != null && p.Inlines.Count > 0)
{
Inline il = p.Inlines.FirstInline;
while (il != null)
{
Run r = il as Run;
if (r != null)
{
VisualVisited(this, r, true);
il = il.NextInline;
continue;
}
InlineUIContainer uc = il as InlineUIContainer;
if (uc != null && uc.Child != null)
{
VisualVisited(this, uc.Child, true);
il = il.NextInline;
continue;
}
Figure fg = il as Figure;
if (fg != null)
{
TraverseBlockCollection(fg.Blocks);
}
il = il.NextInline;
}
}
}
/// <summary>
/// Traverses passed block collection
/// </summary>
/// <param name="blocks"></param>
public void TraverseBlockCollection(BlockCollection blocks)
{
foreach (Block b in blocks)
{
Paragraph p = b as Paragraph;
if (p != null)
TraverseParagraph(p);
else
{
BlockUIContainer bui = b as BlockUIContainer;
if (bui != null)
{
VisualVisited(this, bui.Child, true);
}
else
{
Section s = b as Section;
if (s != null)
TraverseBlockCollection(s.Blocks);
else
{
Table t = b as Table;
if (t != null)
{
VisualVisited(this, t, true);
foreach (TableRowGroup trg in t.RowGroups)
{
foreach (TableRow tr in trg.Rows)
{
foreach (TableCell tc in tr.Cells)
TraverseBlockCollection(tc.Blocks);
}
}
VisualVisited(this, t, false);
}
}
}
}
}
}
}
I hope these posts helped someone to use wpf as a reporting platform :-).
