WPF Basic Print Rendering






4.76/5 (9 votes)
Render a control and print it
Introduction
The standard way to design printable documents in WPF is to use FlowDocuments
or FixedDocuments
. Unfortunately, making detailed layouts with either can be a bit frustrating. If you've ever used them, then you know what I mean. On the surface, they are fairly easy to grasp, for the most part it's like the rest of WPF. However, shifting things, using bindings, or setting up fairly complicated UIElement
layouts can be tricky and may not work quite right.
Background
The basic idea here is that you use a UserControl
, very friendly to use (unlike FlowDocuments)
, and design them in such a manner that they look like paper. The size of the UserControl
should be 816px X 1056px (width X height), which gives you your common 8.5 X 11 piece of computer paper. So you have to carefully choose how you implement ListViews
, TreeViews,
and the like. Getting into multipage printing can still be managed this way, albeit with a little work. Rather than "stacking" UserControls
representing each page, you can make one long UserControl
and let it get cut into 1056px vertical chunks visually.
Expanding Usage
This class is very basic and is intended to be used as a starting point for you to customize your own printing solution. It is designed to intentionally be simplistic. All the stuff you can find online can be rather mind numbing to search through. Even when you do find something, a vast majority doesn't work or is unnecessarily complicated.
The methods have been made extension methods for a FrameworkElement
. If you desire a lesser derived type, like UIElement
, then be sure to remove the references to the FrameworkElement.Name
property. This class does not implement page numbers, intelligent page breaks or header/footer support. You will have to add that stuff yourself. The page numbers shouldn't be too hard, but the intelligent page breaks may be a bit more difficult. There is an article here that you can glean some hints from (the rowPixelDeviation
method in particular) if you wish to take a stab at it.
Using the Code
Below is the class you can use to start from:
/// <summary>Provides printing capabilities.</summary>
public static class Printer
{
/// <summary>Prints the FrameworkElement.</summary>
/// <param name="fe">The FrameworkElement.</param>
public static void Print(this FrameworkElement fe)
{
PrintDialog pd = new PrintDialog();
bool? result = pd.ShowDialog();
if (!result.HasValue || !result.Value) return;
fe.Dispatcher.Invoke(new Action(() =>
{
fe.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
fe.Arrange(new Rect(fe.DesiredSize));
fe.UpdateLayout();
}), System.Windows.Threading.DispatcherPriority.Render);
int height = (int)pd.PrintableAreaHeight;
int width = (int)pd.PrintableAreaWidth;
int pages = (int)Math.Ceiling((fe.ActualHeight / height));
FixedDocument document = new FixedDocument();
for (int i = 0; i < pages; i++)
{
FixedPage page = new FixedPage();
page.Height = height;
page.Width = width;
PageContent content = new PageContent();
content.Child = page;
document.Pages.Add(content);
VisualBrush vb = new VisualBrush(fe);
vb.AlignmentX = AlignmentX.Left;
vb.AlignmentY = AlignmentY.Top;
vb.Stretch = Stretch.None;
vb.TileMode = TileMode.None;
vb.Viewbox = new Rect(0, i * height, width, (i + 1) * height);
vb.ViewboxUnits = BrushMappingMode.Absolute;
RenderOptions.SetBitmapScalingMode(vb, BitmapScalingMode.HighQuality);
Canvas canvas = new Canvas();
canvas.Background = vb;
canvas.Height = height;
canvas.Width = width;
FixedPage.SetLeft(canvas, 0);
FixedPage.SetTop(canvas, 0);
page.Children.Add(canvas);
}
pd.PrintDocument(document.DocumentPaginator,
((String.IsNullOrWhiteSpace(fe.Name) ? "Temp" : fe.Name) + " PRINT"));
}
/// <summary>Prints the FrameworkElement as a
/// continuous (no page breaks) print.</summary>
/// <param name="fe">The FrameworkElement.</param>
public static void PrintContinuous(this FrameworkElement fe)
{
PrintDialog pd = new PrintDialog();
bool? result = pd.ShowDialog();
if (!result.HasValue || !result.Value) return;
fe.Dispatcher.Invoke(new Action(() =>
{
fe.Measure(new Size(Double.PositiveInfinity, Double.PositiveInfinity));
fe.Arrange(new Rect(fe.DesiredSize));
fe.UpdateLayout();
}), System.Windows.Threading.DispatcherPriority.Render);
pd.PrintVisual(fe, ((String.IsNullOrWhiteSpace
(fe.Name) ? "Temp" : fe.Name) + " PRINT"));
}
/// <summary>Takes a one page snapshot
/// the FrameworkElement.</summary>
/// <param name="fe">The FrameworkElement.</param>
public static void Snapshot(this FrameworkElement fe)
{
PrintDialog pd = new PrintDialog();
bool? result = pd.ShowDialog();
if (!result.HasValue || !result.Value) return;
pd.PrintVisual(fe, ((String.IsNullOrWhiteSpace(fe.Name) ?
"Temp" : fe.Name) + " PRINT"));
}
}