WPF FixedDocument pagination
Asked Answered
G

3

11

How do I get FixedDocument to automatically paginate? I have the following code that I can use to put prettified Panel into a DocViewer. The problem occurs when the Panel extends past a single page. Right now, we simply clip. Basically, I want to create a fairly generic way to print what the user is viewing. Is my approach reasonable?

    public void CreateReport(Panel details)
    {
        FixedDocument fixedDoc = new FixedDocument();
        PageContent pageContent = new PageContent();
        FixedPage fixedPage = new FixedPage();

        fixedPage.DataContext = this.DataContext;
        fixedPage.Margin = new Thickness(10);

        fixedPage.Children.Add(details);
        ((System.Windows.Markup.IAddChild)pageContent).AddChild(fixedPage);
        fixedDoc.Pages.Add(pageContent);

        // This makes the array of controls invisibile, then climbs the details structure
        // and makes the controls within details appropriate for the DocumentViewwer (i.e. TextBoxes are
        // non-editable, no borders, no scroll bars, etc).
        prePrintPrepare(details, fixedPage, new FrameworkElement[] { controlToMakeInvisible });

        _dv = new DocViewer();
        _dv.documentViewer1.Document = fixedDoc;
        _dv.Show();
    }
Gitlow answered 24/2, 2011 at 16:59 Comment(0)
G
6

I hate answering my own question, but, the following gave me a reasonable solution: http://social.msdn.microsoft.com/Forums/en-US/wpf/thread/bd89e0d2-73df-4b9b-9210-b8be83ff29d6/

Scott

public static class PrintHelper
{
    public static FixedDocument GetFixedDocument(FrameworkElement toPrint, PrintDialog printDialog)
    {
        PrintCapabilities capabilities = printDialog.PrintQueue.GetPrintCapabilities(printDialog.PrintTicket);
        Size pageSize = new Size(printDialog.PrintableAreaWidth, printDialog.PrintableAreaHeight);
        Size visibleSize = new Size(capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);
        FixedDocument fixedDoc = new FixedDocument();

        // If the toPrint visual is not displayed on screen we neeed to measure and arrange it.
        toPrint.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
        toPrint.Arrange(new Rect(new Point(0, 0), toPrint.DesiredSize));

        Size size = toPrint.DesiredSize;

        // Will assume for simplicity the control fits horizontally on the page.
        double yOffset = 0;
        while (yOffset < size.Height)
        {
            VisualBrush vb = new VisualBrush(toPrint);
            vb.Stretch = Stretch.None;
            vb.AlignmentX = AlignmentX.Left;
            vb.AlignmentY = AlignmentY.Top;
            vb.ViewboxUnits = BrushMappingMode.Absolute;
            vb.TileMode = TileMode.None;
            vb.Viewbox = new Rect(0, yOffset, visibleSize.Width, visibleSize.Height);

            PageContent pageContent = new PageContent();
            FixedPage page = new FixedPage();
            ((IAddChild)pageContent).AddChild(page);
            fixedDoc.Pages.Add(pageContent);
            page.Width = pageSize.Width;
            page.Height = pageSize.Height;

            Canvas canvas = new Canvas();
            FixedPage.SetLeft(canvas, capabilities.PageImageableArea.OriginWidth);
            FixedPage.SetTop(canvas, capabilities.PageImageableArea.OriginHeight);
            canvas.Width = visibleSize.Width;
            canvas.Height = visibleSize.Height;
            canvas.Background = vb;
            page.Children.Add(canvas);

            yOffset += visibleSize.Height;
        }
        return fixedDoc;
    }

    public static void ShowPrintPreview(FixedDocument fixedDoc)
    {
        Window wnd = new Window();
        DocumentViewer viewer = new DocumentViewer();
        viewer.Document = fixedDoc;
        wnd.Content = viewer;
        wnd.ShowDialog();
    }
}
Gitlow answered 24/2, 2011 at 20:10 Comment(1)
This is great! I wonder if its possible to do a page break in between items instead of cutting through something.Beaumarchais
P
3

For those who are interested in a different and maybe more "advanced" solution, go ahead and check out my repository on GitHub where I demonstrate how to create XAML reports, paginate them, then produce a printable FixedDocument from them.

The magic happens in Paginator.cs where the code goes through all the custom attached properties and uses them to determine where to set page number, which elements to show only on first page, and more...

All reports are defined as plain XAML user controls, and are converted to fixed pages after pagination is done. The benefit is that you can define your document/report in pure XAML, add a few attached properties, and the paginator code takes care of the rest.

EDIT: For those having trouble understanding the code, all you need to do is create a factory (Func) that the Paginator can use to create instances of your XAML UserControls.

So for example:

var reportFactory = () => new MyCoolUserControl();
var pages = await paginator.PaginateAsync(reportFactory, ...);

If you want to get a FixedDocument from those pages, just call

var fixedDocument = paginator.GetFixedDocumentFromPages(pages, pageSize);

The rest of the code is just some MVVM stuff (which I actually would do differently nowadays) and printing and so forth...

Practiced answered 11/2, 2020 at 7:31 Comment(4)
Finally! This is exactly what I've been trying to figure out how to do! I've been scouring the internet to figure out how to paginate xaml.Tanguay
@Tanguay I'm glad I could help! I've been where you are :)Practiced
@ShahinDohan you are a genius, this was exactly what I needed. Been trying to paginate itemcontrols with complex objects and it kept cropping - your solution worked perfectly. thank youRamsgate
@Ramsgate why thank you hehe, just a lot of grinding on my part... I'm glad it works for you just be careful that it doesn't work well with any kind of crazy wacky XAML, so try to write proper XAML with known heights/widths (so avoid infinitely growing StackPanels etc)Practiced
K
1

It's been a long time since the question has been answered.
I tried Doo Dah's answer, but the problem was that it doesn't take care of the page paddings of a flowdocument.

Therefore I wrote my own solution (Doo Dah's answer helped me to complete it):

public FixedDocument Get_Fixed_From_FlowDoc(FlowDocument flowDoc, PrintDialog printDlg)
{
    var fixedDocument = new FixedDocument();
    try
    {
        pdlgPrint = printDlg ?? new PrintDialog();

        DocumentPaginator dpPages = (DocumentPaginator)((IDocumentPaginatorSource)flowDoc).DocumentPaginator;
        dpPages.ComputePageCount();
        PrintCapabilities capabilities = pdlgPrint.PrintQueue.GetPrintCapabilities(pdlgPrint.PrintTicket);

        for (int iPages= 0; iPages < dpPages.PageCount; iPages++)
        {
            var page = dpPages.GetPage(iPages);
            var pageContent = new PageContent();
            var fixedPage = new FixedPage();

            Canvas canvas = new Canvas();

            VisualBrush vb = new VisualBrush(page.Visual);
            vb.Stretch = Stretch.None;
            vb.AlignmentX = AlignmentX.Left;
            vb.AlignmentY = AlignmentY.Top;
            vb.ViewboxUnits = BrushMappingMode.Absolute;
            vb.TileMode = TileMode.None;
            vb.Viewbox = new Rect(0, 0, capabilities.PageImageableArea.ExtentWidth, capabilities.PageImageableArea.ExtentHeight);

            FixedPage.SetLeft(canvas, 0);
            FixedPage.SetTop(canvas, 0);
            canvas.Width = capabilities.PageImageableArea.ExtentWidth;
            canvas.Height = capabilities.PageImageableArea.ExtentHeight;
            canvas.Background = vb;

            fixedPage.Children.Add(canvas);

            fixedPage.Width = pdlgPrint.PrintableAreaWidth;
            fixedPage.Height = pdlgPrint.PrintableAreaHeight;
            pageContent.Child = fixedPage;
            fixedDocument.Pages.Add(pageContent);
        }
        dv1.ShowPageBorders = true;
    }
    catch (Exception)
    {
        throw;
    }
    return fixedDocument;
}

You had to build a FlowDocument of the content you will show before and pass it to the Method.
Added the PrintDialog variable to call the Method from my preview window and can pass the current printer settings.

If you call it from your main programm, you either can pass a new PrintDialog() or null, there is no difference, because it will create a new PrintDialog if you are passing null

This worked fine for me with a Flowdocument with different types of text (header, text, font).

It should work with pictures and text mixed, or only pictures, too - it's using the visuals and not something specific from a flowdocument, therefore it should work with pagebreaks, too.

I don't tryed Shahin Dohan'S answer, because it's the same problem as so often.
It's written at MVVM and very hard to understand when another person has written it.
At my opinion it would be better to write a little example programm without mvvm and the people can adept it to mvvm or use only the code.
I understand the opportunities of mvvm, but to show someone how to works something i see only disadvantages (if you will not show a specific mvvm mechanic)

Kipper answered 15/8, 2020 at 10:40 Comment(1)
You don't have to really care about the MVVM stuff, that's there just for me. All you need is in the ReportPaginator folder. Just feed the Paginator a Func that can create instances of your XAML UserControl, which is the report itself. So: var factory = () => new MyUserControl(); then pass factory to Paginator, and that's it. I could clean up the code a bit but I don't have time for that :)Practiced

© 2022 - 2024 — McMap. All rights reserved.