Search code examples
c#pdfwpf-controlsxpsdocument

FixedPage XPS/PDF content in won't fill the page


I've been attempting this for a few hours, and now reluctantly asking for help.

I have a WPF Custom Control with a backing view model that I'd like to print to PDF (via XPS). Eventually, there will be multiple pages of these controls.

Following several examples on SO and MSDN forums, I've written code that will create the PDF file with the control in it, but the content is not sized correctly.

    var controlViewModelsToPrint = new InfoControlViewModel[] { ViewModel };

    // Assume 8.5"x11" size for now, get from print dialog later
    var xpsDPI = 96;
    var pageWidth = 8.5 * xpsDPI;
    var pageHeight = 11 * xpsDPI;

    var pageSize = new Size(pageWidth, pageHeight);

    // Document hierarchy: FixedDocument > PageContent > FixedPage > UIElement/Control

    var fixedDoc = new FixedDocument();

    foreach (var controlViewModel in controlViewModelsToPrint)
    {
        var pageContent = new PageContent();
        var fixedPage = new FixedPage();

        // The control to be printed is never displayed on screen so measure and arrage it.
        var controlView = new InfoControl(controlViewModel);

        controlView.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
        // I've read that using either this call to the dispatcher or an UpdateLayout would help - it doesn't.
        //Application.Current.Dispatcher.Invoke(new Action(() => { }), DispatcherPriority.ContextIdle);
        controlView.Arrange(new Rect(controlView.DesiredSize));

        // Add the control/page/content through the document hierarchy
        fixedPage.Children.Add(controlView);
        ((IAddChild)pageContent).AddChild(fixedPage);

        // Set gage dimensions
        fixedPage.Width = pageSize.Width;
        fixedPage.Height = pageSize.Height;

        // Running Measure/Arrage on the fixed page does not alter the result.
        fixedPage.Measure(pageSize);
        fixedPage.Arrange(new Rect(fixedPage.DesiredSize));
        fixedPage.UpdateLayout();

        fixedDoc.Pages.Add(pageContent);
    }

    PrintFixedDocument(fixedDoc);

Resulting PDF: Resulting content not filled to page I am expecting the content to fill the page, is it would if it were displayed in a WPF window: Control correctly displayed in WPF window

I have tried every combination of measuring, arranging, updating layout calls I can think of without success.

I have also tried different XPS to PDF solutions(PDFSharp, and Print FixedDocument/XPS to PDF without showing file save dialog) without success.

I have a minimal working example on GitHub here: https://github.com/AndyStagg/PDFPrintingDemo

Relivent resources:

Convert WPF (XAML) Control to XPS Document

How to calculate FixedPage dimensions

Why do I have have to use UIElement.UpdateLayout?

Edit: I've tried a few more things:

Setting the Horizontial and Vertical alignment on the fixed page

fixedPage.HorizontalAlignment = HorizontalAlignment.Stretch;
fixedPage.VerticalAlignment = VerticalAlignment.Stretch;

Setting the Horizontal and Vertical (both regular, and Content) alignment on the control

controlView.HorizontalContentAlignment = HorizontalAlignment.Stretch;
controlView.VerticalContentAlignment = VerticalAlignment.Stretch;

controlView.HorizontalAlignment = HorizontalAlignment.Stretch;
controlView.VerticalAlignment = VerticalAlignment.Stretch;

controlView.DesiredSize was 155x215, so I tried specifying the pageSize in the Arrage call

controlView.Arrange(new Rect(pageSize));

Which results in the controlView objet having the correct ActualHeight and ActualWidth, but resulting PDF is unchanged.

I've tried sending in the pageSize to the Measure call

controlView.Measure(pageSize);

No luck.

And last but not least, I tried to set the width and height on the control

controlView.Width = pageSize.Width;
controlView.Height = pageSize.Height;

Again, No Luck.

I've also tried placing the controlView as a child within a Border, ViewBox, and a Grid

var border = new Border();
var controlView = new InfoControl(control);
border.Child = controlView;

border.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
border.Arrange(new Rect(border.DesiredSize));

// Add the control/page/content through the document hierarchy
fixedPage.Children.Add(border);

as well as tried calling the Measure & Arrange calls on the child (controlView)

var grid = new Grid();
var controlView = new InfoControl(control);

grid.Children.Add(controlView);

controlView.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
controlView.Arrange(new Rect(controlView.DesiredSize));

// Add the control/page/content through the document hierarchy
fixedPage.Children.Add(grid);

I am pretty thoroughly stumped at this point.


Solution

  • After trying pretty much everything I could think of in the logic for creating the document and fixed page, I turned to the UserControl itself, and after that, it was surprisingly easy to fix.

    I used the MeasuredOverride method on the UserControl to see if the parent was a FixedPage and if it was, to return a size based on the fixed page width and height, otherwise return the base logic. That allows the control to still be used in the UI for previewing, etc..

    It sure isn't pretty, but it works. I am sure there will be some pitfalls with it, but at least I can keep going to figure out what those might be.

    protected override Size MeasureOverride(Size constraint)
    {
        if (this.Parent is FixedPage fixedPage)
        {
            return new Size(fixedPage.Width, fixedPage.Height);
        }
    
        return base.MeasureOverride(constraint);
    }
    

    The GitHub repo is updated for those in the future to view the complete solution.