Feb 5, 2011

Paginated Printing of WPF Visuals

I was considerably frustrated when recently researching paginated printing of WPF controls. I own two books on WPF. One mentions pagination but only when discussing FlowDocuments. That's almost useless if you have a large visual that you want to print in a paginated manner.
I found one Stackflow discussion that pointed me in the right direction. There were 3 instructions. Something like:
  1. Set scrollviewer to enabled in both directions.
  2. Implement IDocumentPaginator
  3. Implement the NextPage among others to transform the printed visual and return the page
Sometimes you get really succinct instructions that are exactly what you need from stack, sometimes they're misleading and you have to investigate. In this case I found there is no such interface as IDocumentPaginator. So that was not much help. What I really needed to do was subclass the DocumentPaginator abstract class and do the transforms.

Secondly the scrollviewer instruction is about giving the contained visual element infinite space. So if you have a clipped element and want to print the whole thing, you need to add the scrollviewer container so that scrolling can be enabled in both directions so that its ActualSize is the real size of the control. My control already existed in a scrollviewer so I was already 1/3 done.

Here's the other 2/3.

class ProgramPaginator : DocumentPaginator
{
    private FrameworkElement Element;
    private ProgramPaginator()
    {
    }

    public ProgramPaginator(FrameworkElement element)
    {
        Element = element;
    }

    public override DocumentPage GetPage(int pageNumber)
    {

        Element.RenderTransform = new TranslateTransform(-PageSize.Width * (pageNumber % Columns), -PageSize.Height * (pageNumber / Columns));

        Size elementSize = new Size( 
            Element.ActualWidth, 
            Element.ActualHeight); 
        Element.Measure(elementSize); 
        Element.Arrange(new Rect(new Point(0, 0), elementSize));

        var page = new DocumentPage(Element);
        Element.RenderTransform = null;

        return page;
    }

    public override bool IsPageCountValid
    {
        get { return true; }
    }

    public int Columns
    {
        get
        {
            return (int) Math.Ceiling(Element.ActualWidth/PageSize.Width);
        }
    }
    public int Rows
    {
        get
        {
            return (int)Math.Ceiling(Element.ActualHeight / PageSize.Height);
        }
    }

    public override int PageCount
    {
        get
        {
            return Columns * Rows;
        }
    }

    public override Size PageSize
    {
        set; get;
    }

    public override IDocumentPaginatorSource Source
    {
        get { return null; }
    }
}

This class doesn't handle margins or any fancy printing controls. You have to set the page size (usually like this)

internal void Print()
{
    var paginator = new ProgramPaginator(this_grid);
    var dlg = new PrintDialog();
    if ((bool) dlg.ShowDialog())
    {
        paginator.PageSize = new Size(dlg.PrintableAreaWidth, dlg.PrintableAreaHeight);
        dlg.PrintDocument(paginator, "Program");
    }            
}       

Pass in a framework element and print. It's really handy to cover scrollviewers that can grow to unlimited dimensions.

3 comments:

  1. Hello, do you have a complete tutorial / blog article for this? I can't get it working properly (pages won't get paginated)

    ReplyDelete
  2. pagination works horizontally only.

    ReplyDelete
  3. To make the landscape work it's enough to modify the page in method GetPage as follows: "var page = new DocumentPage(Element, PageSize, new Rect(0,0,0,0), new Rect(0,0,0,0));"

    ReplyDelete