WPF multipage reports - Part IV - Pagination

Posted 21.7.2008 by janrep

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 :-).

kick it on DotNetKicks.com

Tags: WPF, .NET