Showing posts with label frameworkelement. Show all posts
Showing posts with label frameworkelement. Show all posts

Mar 23, 2011

Paginated Printing of WPF Visuals - II

Last time I discussed paginated printing of wpf visuals, I mentioned that the pagination didn't support a whole lot of options.

There was no support for margins or headers for example. The print was edge-to-edge which is OK when you're a developer and you're going to throw the paper out right after printing, but not so good if you are the end user and need to put the paper in a binder, or if you've dropped the whole mess on the floor and need to put it back in order.

The easy way to put more visuals with the visuals you have rendered is to use a ContainerVisual object and stack some visuals together. With my previous entry there was a problem because it transformed and rendered the original Visual directly to print. This meant that the Visual could not be part of ContainerVisual as it already had a visual parent.

To solve that problem, I rendered the Visual to bitmap before print. That way I could use ContainerVisual with the bitmap and stack up any number of visuals in the container. Here's the class.

class FrameworkVisualPrint : DocumentPaginator
{
    private readonly FrameworkElement Element;
    private const double XMargin = 1 * 96;
    private const double YMargin = 1 * 96;
    public String Header { set; get; }

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

    private const double ScaleFactor = 0.5;

    private DrawingVisual GetHeader(int pageNumber)
    {
        var header = new DrawingVisual();

        using (var dc = header.RenderOpen())
        {
            var text = new FormattedText("Page " + (pageNumber + 1) + Environment.NewLine +
                Header, CultureInfo.CurrentCulture, FlowDirection.LeftToRight, 
                new Typeface("Arial"), 8, Brushes.Black);
            dc.DrawText(text, new Point(XMargin, 0.5 * 96));
        }

        return header;
    }

    public override DocumentPage GetPage(int pageNumber)
    {
        var tgroup = new TransformGroup();
        tgroup.Children.Add(new ScaleTransform(ScaleFactor, ScaleFactor));
        Element.RenderTransform = tgroup;
        tgroup.Children.Add(new TranslateTransform(-PageSize.Width * (pageNumber % Columns) 
            + XMargin, -PageSize.Height * (pageNumber / Columns) + YMargin));
        Element.RenderTransform = tgroup;

        try
        {
            var area = new Rect(
                new Point((PageSize.Width * (pageNumber % Columns)) / ScaleFactor,
                          (PageSize.Height * (pageNumber / Columns)) / ScaleFactor),
                new Size(PageSize.Width / ScaleFactor,
                         pageSize.Height / ScaleFactor));

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

            // Make a bitmap from the transformed Element
            var bitmap = PngBitmap(Element, (int)area.Width, (int)area.Height);
            var vis = new DrawingVisual();

            // Draw the bitmap into a DrawingVisual object
            DrawingContext dc = vis.RenderOpen();
            dc.DrawImage(bitmap, new Rect(0, 0, area.Width, area.Height));
            dc.Close();

            // Grab a header to apply (with page numbers)
            var header = GetHeader(pageNumber);
            // Container for all the new stuff.
            var cVisual = new ContainerVisual();

            cVisual.Children.Add(header);
            cVisual.Children.Add(vis);
            return new DocumentPage((cVisual));

        }
        finally
        {
            Element.RenderTransform = null;
            Element.Clip = null;
        }

    }

    private RenderTargetBitmap PngBitmap(Visual visual, int width, int height)
    {
        // Render
        var rtb =
            new RenderTargetBitmap(
                (int)(width / ScaleFactor),
                (int)(height / ScaleFactor),
                96.0 / ScaleFactor,
                96.0 / ScaleFactor,
                PixelFormats.Default);
        rtb.Render(visual);


        return rtb;
    }

    private Size pageSize;
    public override Size PageSize
    {
        set
        {
            pageSize = value;
            pageSize.Width -= XMargin;
            pageSize.Height -= YMargin;
        }
        get { return pageSize; }
    }
}

The class isn't actually complete. See the previous article for the initial version. This is just new and changed methods.

The important bits are commented.
  • Make a bitmap from the Visual
  • Draw the bitmap to a new DrawingVisual Object
  • Stick the DrawingVisual and any other visuals needed in a ContainerVisual
  • Bob's your uncle!

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.