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:
I am expecting the content to fill the page, is it would if it were displayed in a 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.
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.