Introduction
I was searching on how to print using WPF for my application. The requirement is
that there’s already a background image where text will be placed accordingly.
The image is based on an actual form, like a purchase order form. The document
can also be previewed.
So while searching, I found out that there are several ways to print documents using
WPF. Let’s start with the easy ones.
Printing a Visual
A Visual is an object that provides rendering support in WPF. Basically, these are
the objects that we usually see in a WPF application, like user interface controls,
images, ellipses, etc. Below is the screenshot of a simple application that prints
all the contents of the window.

The following code listing shows the event handler for the Print button’s Click event.
private void PrintBtn_Click(object sender, RoutedEventArgs e)
{
PrintDialog printDialog = new PrintDialog();
if (printDialog.ShowDialog() == true)
{
printDialog.PrintVisual(grid, "My First Print Job");
}
}
The PrintDialog control is shown when the Print button is clicked.

The PrintDialog lets the user choose a printer, set printer preferences, and other
print options. If the user clicks the Print button then the PrintVisual method
is executed. What the PrintVisual method does is create a print job based on
a Visual object and add it to the print queue. It takes in 2 parameters: the
Visual object to print and the print job description. Here, the Visual object
is the grid containing the other Visual objects. Even the Print button gets printed
since it is included in the grid. The print job description is just a name to
identify the print job in the printer’s user interface.
It is not necessary to show the Print dialog. This, however, removes the capabilities
of the user to change some settings before printing. Default settings can be
changed using code by changing properties of the PrintDialog, like the print
ticket.
I think one use of this approach is by using it in a simple drawing application.
Since this approach is very simple to use, it comes with some noticeable limitations.
First, it does not support pagination. If the Visual is too long for a single
page, then some content won’t get printed. Another one is that there is not much
control over the page margins. The default behavior is that a Visual gets printed
starting at the top-left corner of the page. Note that not all printers can print
near the edges of the paper so it is possible that some content won’t get printed.
One workaround for this is setting a margin of the Visual before printing. The
following figure shows the output.

Printing a Document
The PrintDialog provides another method for printing, the PrintDocument method. This
method sends a DocumentPaginator object to the print queue. The DocumentPaginator
class, according to MSDN, provides an abstract base class that supports creation
of multiple-page elements from a single document. So unlike the previous approach,
this one supports pagination.
Generally, there are two ways to get a DocumentPaginator object.
· A DocumentPaginator can be obtained from a WPF document. A document is divided into
two categories: FlowDocument and FixedDocument. A FlowDocument is a document
that supports flowing of content. It means that the content can be rearranged to some extent depending on what the
user prefers for his viewing pleasure. Meanwhile, a FixedDocument refers to a document with fixed content and commonly known
as a What You See Is What You Get (WYSIWYG) document.
· We can create an object derived from DocumentPaginator because the DocumentPaginator
can’t be instantiated directly since it is abstract. There are two existing derived
classes but one is abstract and one is only used for adding annotations.
To demonstrate how to use the PrintDocument method, let’s create an application that
prints a FlowDocument.

Here is the corresponding XAML code for the figure above, although I’ve resized the
window in the figure.
<Window
x:Class="PrintDocument.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Print Document"
Width="500"
Height="500">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Menu
Height="22">
<MenuItem
Header="File">
<MenuItem
Header="Print"
Click="PrintMenu_Click"/>
</MenuItem>
</Menu>
<FlowDocumentReader
Grid.Row="1">
<FlowDocument
x:Name="flowDocument"
IsOptimalParagraphEnabled="true"
IsHyphenationEnabled="true"
IsColumnWidthFlexible="false"
ColumnWidth="Auto"
PagePadding="Auto" >
<Paragraph>
WPF Printing
</Paragraph>
<!-- REMOVED OTHER PARAGRAPHS FOR READABILITY -->
</FlowDocument>
</FlowDocumentReader>
</Grid>
</Window>
The following code listing shows the event handler for the Print menu item’s Click
event.
private void PrintMenu_Click(object sender, RoutedEventArgs e)
{
PrintDialog printDialog = new PrintDialog();
if (printDialog.ShowDialog() == true)
{
printDialog.PrintDocument(((IDocumentPaginatorSource)flowDocument).DocumentPaginator, "Flow Document Print Job");
}
}
Notice that using the PrintDocument method is almost the same as using the PrintVisual
method. To get the DocumentPaginator object for a FlowDocument, we have to cast
it to an IDocumentPaginatorSource first. If we are using a FixedDocument, we
can get the DocumentPaginator object through the DocumentPaginator property.
Now let’s look at the print output.

Only 2 out of 4 pages are presented here. You can see that there are unused spaces.
This might not be the desired output because of the wasted space. This happened
because the print output of a flow document depends on what container the document
is hosted. In this example, a FlowDocumentReader is used. There are 3 controls
specifically used for viewing flow documents.
· A FlowDocumentScrollViewer has a scroll bar to allow the user to scroll up and down
the flow document continuously, much like viewing a web page.
· A FlowDocumentPageViewer is similar to the FlowDocumentReader in the example. The
flow document is shown 1 page at a time.
· A FlowDocumentReader has different viewing modes: Page Mode, Two Page Mode, and Scroll
Mode.
The following figure shows the output when a FlowDocumentScrollViewer is used.

If a FlowDocumentPageViewer is used, the output is the same as that of using a FlowDocumentReader
in Page Mode, like in the example where there are unused spaces. Meanwhile, the
output of the FlowDocumentReader depends on the current viewing mode. If the
viewing mode is Scroll Mode, then the output will be the same as when a FlowDocumentScrollViewer
is used. If the viewing mode is Page Mode, then the output will be the same as
when a FlowDocumentPageViewer is used.
Custom Printing
As said earlier, we can create our own DocumentPaginator-derived class. This gives
us more control on how a document is printed. However, it might be difficult
to do this depending on the requirements. For example, if you want to display
long text, then you should know how to split them over multiple lines and how
to split them over pages. To demonstrate how to create a custom DocumentPaginator,
let’s create a simple application that prints a list of inventory items and their
current quantity. The following code is the custom DocumentPaginator class.
/// <summary>
/// Document paginator for inventory items.
/// </summary>
public class InventoryDocumentPaginator : DocumentPaginator
{
private readonly InventoryItem[] inventoryItems;
private Size pageSize;
private int pageCount;
private int maxRowsPerPage;
/// <summary>
/// Constructor.
/// </summary>
/// <param name="inventoryItems">The list of inventory items to display.</param>
/// <param name="pageSize">The size of the page in pixels.</param>
public InventoryDocumentPaginator
(
InventoryItem[] inventoryItems,
Size pageSize
)
{
this.inventoryItems = inventoryItems;
this.pageSize = pageSize;
PaginateInventoryItems();
}
/// <summary>
/// Computes the page count based on the number of inventory items
/// and the page size.
/// </summary>
private void PaginateInventoryItems()
{
FormattedText text = Utilities.FormatText("Any Text");
maxRowsPerPage = (int)(pageSize.Height / text.Height);
pageCount = (int)Math.Ceiling((double)inventoryItems.Count() / maxRowsPerPage);
}
/// <summary>
/// Gets a range of inventory items from an array.
/// </summary>
/// <param name="array">The inventory items array.</param>
/// <param name="start">Start index.</param>
/// <param name="end">End index.</param>
/// <returns></returns>
private static InventoryItem[] GetRange(InventoryItem[] array, int start, int end)
{
List<InventoryItem> inventoryItems = new List<InventoryItem>();
for (int i = start; i < end; i++)
{
if (i >= array.Count())
{
break;
}
inventoryItems.Add(array[i]);
}
return inventoryItems.ToArray();
}
#region DocumentPaginator Members
/// <summary>
/// When overridden in a derived class, gets the DocumentPage for the
/// specified page number.
/// </summary>
/// <param name="pageNumber">
/// The zero-based page number of the document page that is needed.
/// </param>
/// <returns>
/// The DocumentPage for the specified pageNumber, or DocumentPage.Missing
/// if the page does not exist.
/// </returns>
public override DocumentPage GetPage(int pageNumber)
{
// Compute the range of inventory items to display
int start = pageNumber * maxRowsPerPage;
int end = start + maxRowsPerPage;
InventoryListPage page = new InventoryListPage(GetRange(inventoryItems, start, end), pageSize);
page.Measure(pageSize);
page.Arrange(new Rect(pageSize));
return new DocumentPage(page);
}
/// <summary>
/// When overridden in a derived class, gets a value indicating whether
/// PageCount is the total number of pages.
/// </summary>
public override bool IsPageCountValid
{
get { return true; }
}
/// <summary>
/// When overridden in a derived class, gets a count of the number of
/// pages currently formatted.
/// </summary>
public override int PageCount
{
get { return pageCount; }
}
/// <summary>
/// When overridden in a derived class, gets or sets the suggested width
/// and height of each page.
/// </summary>
public override System.Windows.Size PageSize
{
get
{
return pageSize;
}
set
{
if (pageSize.Equals(value) != true)
{
pageSize = value;
PaginateInventoryItems();
}
}
}
/// <summary>
/// When overridden in a derived class, returns the element being paginated.
/// </summary>
public override IDocumentPaginatorSource Source
{
get { return null; }
}
#endregion
}
The code in the DocumentPaginator region contains the overridden properties and a
method.
· The GetPage method returns a DocumentPage object for the specified page number. This
method is called by the printer driver to get the pages to print. To create a
DocumentPage, a Visual object must be supplied to its constructor. In the example,
an InventoryListPage UserControl is the Visual object. The list of inventory
items to display and the page size are passed to the InventoryListPage constructor.
It then handles the drawing of the inventory items by overriding the OnRender
method. We’ll take a look at this later.
An alternative to creating a UserControl is to create a DrawingVisual object and
call the RenderOpen method to get the DrawingContext, which is used to render
into the DrawingVisual. In the example, the Measure and Arrange methods are called
on the Visual object to ensure that the Visual’s layout is correct. Otherwise,
the pages will be empty. This is not necessary if the Visual is displayed in
the application’s user interface since these methods are called already.
· The IsPageCountValid property checks if the total number of pages is valid. In the
example, it always returns true since the PageCount is easy to compute. However,
there will be times that it won’t be easy to determine the PageCount, especially
in applications with complex algorithms to create the layout of every page, so
the value should be set to false. Every time the GetPage method is called by
the printer driver, the IsPageCountValid is also called. You should provide a
condition that determines if the PageCount is already valid, like when all data
have been printed. Otherwise, the printer driver will think that pagination is
still in process and will continue calling the GetPage method.
· The PageCount property returns the total number of pages. If this can’t be determined
immediately, the PageCount may return the number of pages currently formatted.
· The PageSize property is the size of the page in pixels, where a pixel is 1/96th of an inch. So if we want to print to an 8.5 x 11 in. paper, we just have to multiple
the dimensions by 96 and we’ll get 816 x 1056 pixels. Notice also that unlike
the previous properties, this property has a set accessor. If the value is changed,
then the document should be repaginated.
· The Source property returns the element being paginated, which is of type IDocumentPaginatorSource.
Personally, I can’t find much use of this property but fortunately, we can always
return a null value.
The PaginateInventoryItems computes the page count and the maximum number of rows
of inventory items that can fit within a page. To compute the number of rows,
we must also know the height of the text. This is obtained from the Height property
of a FormattedText object. The following code listing shows the FormatText utility
method, which returns a FormattedText object.
public static class Utilities
{
/// <summary>
/// Gets a FormattedText equivalent of the specified text string.
/// </summary>
/// <param name="text">The text string to format.</param>
/// <returns>FormattedText object.</returns>
public static FormattedText FormatText(string text)
{
return new FormattedText
(
text,
CultureInfo.CurrentCulture,
FlowDirection.LeftToRight,
new Typeface("Arial"),
12,
Brushes.Black
);
}
}
Instead of using constant values for arguments like the font size, you can add properties
in the custom DocumentPaginator to change these values. If this is done in the
example, the PaginateInventoryItems method should be called every time the font
size is changed.
Let’s take a look at the InventoryListPage UserControl, the Visual where the actual
drawing of content is done. The XAML code, which is default XAML generated for
a UserControl, is not shown here.
public partial class InventoryListPage : UserControl
{
private readonly InventoryItem[] inventoryItems;
private readonly Size pageSize;
public InventoryListPage
(
InventoryItem[] inventoryItems,
Size pageSize
)
{
InitializeComponent();
this.inventoryItems = inventoryItems;
this.pageSize = pageSize;
}
protected override void OnRender(DrawingContext drawingContext)
{
base.OnRender(drawingContext);
Point point = new Point(0, 0);
foreach (InventoryItem item in inventoryItems)
{
point.X = 0;
FormattedText text = Utilities.FormatText(item.Name);
drawingContext.DrawText(text, point);
point.X = pageSize.Width / 2;
text = Utilities.FormatText(item.Quantity.ToString());
drawingContext.DrawText(text, point);
point.Y += text.Height;
}
}
}
The important part here is the OnRender method. The DrawingContext object provides
various methods for drawing into the Visual. Now, the custom DocumentPaginator
can be used. The following code shows the Main method of a console application
using the DocumentPaginator.
class Program
{
[STAThread]
static void Main(string[] args)
{
List<InventoryItem> inventoryItems = new List<InventoryItem>();
// Create a list of inventory items where the quantity is random.
Random random = new Random();
for (int i = 1; i <= 100; i++)
{
inventoryItems.Add(new InventoryItem("Inventory Item " + i, random.Next(100)));
}
// 8.5 x 11 paper
Size pageSize = new Size(816, 1056);
InventoryDocumentPaginator paginator = new InventoryDocumentPaginator
(
inventoryItems.ToArray(),
pageSize
);
PrintDialog printDialog = new PrintDialog();
if (printDialog.ShowDialog() == true)
{
printDialog.PrintDocument(paginator, "Custom Paginator Print Job");
}
}
}
I think most of the code in the previous listing is self-explanatory. Now let’s take
a look at the result. It’s a bit ugly since there are no margins and too much
wasted space.

XPS Document Printing
In the previous example, there was no print preview functionality. This could be
done by using XML Paper Specification (XPS) documents, which will be the topic
in this section. An XPS document is a package that contains one or more fixed
documents. One advantage of using an XPS document is that it prints better because
of device and resolution independence since it is a vector-based format. It also
uses a new print path, GDI being the older one.
So to print an XPS document, there should be a printer driver that is XPS-enabled.
If a printer driver only supports the (Graphics Device Interface) GDI model,
then WPF converts the XPS content to its GDI equivalent.
To demonstrate how to create and print preview an XPS document, let’s create a simple
application according to the original requirement: there is an existing form
to use, which is an image, and lay out text accordingly. The following screenshot
show the application’s main window.

The user can enter purchase order data. After clicking on the Print button, a print
preview window will be shown containing the purchase order document filled with
data previously entered. Let’s take a look at the XAML code of the main window.
<Window
x:Class="XPSDocumentPrinting.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow"
SizeToContent="WidthAndHeight">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Label
Content="PO #:"/>
<TextBox
Height="23"
Width="150"
Grid.Column="1"
Margin="4"
Text="{Binding Number}"/>
<Label
Grid.Row="1"
Content="Supplier:"/>
<TextBox
Height="23"
Width="150"
Grid.Column="1"
Grid.Row="1"
Text="{Binding Supplier}"/>
<Button
Height="23"
Width="75"
Grid.Column="1"
Grid.Row="2"
Margin="4"
HorizontalAlignment="Right"
Content="Print"
Click="PrintBtn_Click"/>
</Grid>
</Window>
We used data binding to assign the values from the text boxes to the properties of
a PurchaseOrder object, which is set as the window’s DataContext as shown in
the code-behind file below.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new PurchaseOrder();
}
private void PrintBtn_Click(object sender, RoutedEventArgs e)
{
PrintHelper.PrintPreview(this, (FormData)DataContext);
}
}
The PurchaseOrder class is shown below. It inherits from FormData, an empty abstract
class. The FormData is a parameter to the PrintPreview method which is shown
later. This is useful if we have different forms. We don’t need to create a specific
PrintPreview method for each form.
public class PurchaseOrder : FormData
{
public int Number { get; set; }
public string Supplier { get; set; }
}
When the user clicks on the Print button, the static PrintPreview method of the PrintHelper
class is called. Let’s take a look at the PrintHelper class.
public static class PrintHelper
{
private static PageMediaSize A4PaperSize = new PageMediaSize(816, 1248);
public static void PrintPreview(Window owner, FormData data)
{
using (MemoryStream xpsStream = new MemoryStream())
{
using (Package package = Package.Open(xpsStream, FileMode.Create, FileAccess.ReadWrite))
{
string packageUriString = "memorystream://data.xps";
Uri packageUri = new Uri(packageUriString);
PackageStore.AddPackage(packageUri, package);
XpsDocument xpsDocument = new XpsDocument(package, CompressionOption.Maximum, packageUriString);
XpsDocumentWriter writer = XpsDocument.CreateXpsDocumentWriter(xpsDocument);
Form visual = new Form(data);
PrintTicket printTicket = new PrintTicket();
printTicket.PageMediaSize = A4PaperSize;
writer.Write(visual, printTicket);
FixedDocumentSequence document = xpsDocument.GetFixedDocumentSequence();
xpsDocument.Close();
PrintPreviewWindow printPreviewWnd = new PrintPreviewWindow(document);
printPreviewWnd.Owner = owner;
printPreviewWnd.ShowDialog();
PackageStore.RemovePackage(packageUri);
}
}
}
}
The basic idea is that we should create an XPSDocument object and write our Visual
onto it using an XPSDocumentWriter object. We can also write a DocumentPaginator
object instead of a Visual. This solves the problem on how to print preview the
DocumentPaginator object in the previous section.
Creating an XPSDocument requires a Package object. A Package is used to organize
objects into a single entity. Just think of a Package as a ZIP file. We can create
a Package from a file or memory stream. It is important to add the Package object
to the PackageStore and create the XPSDocument using the constructor where the
URI of the Package can be specified. Otherwise, we’ll get an exception when calling
the GetFixedDocumentSequence method. The Package should be removed after showing
the document’s print preview.
In the example, a Form UserControl is written to the XPS document. The Form object
determines what specific UserControl to use as its content based on the type
of data. This is achieved by using DataTemplate resources. The following code
listing shows how this is done.
<UserControl
x:Class="XPSDocumentPrinting.Form"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:XPSDocumentPrinting">
<UserControl.Resources>
<DataTemplate DataType="{x:Type local:PurchaseOrder}">
<local:PurchaseOrderForm/>
</DataTemplate>
</UserControl.Resources>
<ContentControl Content="{Binding}"/>
</UserControl>
The Form contains a ContentControl where the Content property is set to the Form’s
DataContext property. If the DataContext is a PurchaseOrder, then the Content
is set to an instance of a PurchaseOrderForm UserControl. The following code
listing shows how the DataContext property is set.
public partial class Form : UserControl
{
public Form(FormData data)
{
InitializeComponent();
DataContext = data;
}
}
The following code listing shows the PurchaseOrderForm’s XAML code.
<UserControl
x:Class="XPSDocumentPrinting.PurchaseOrderForm"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Grid>
<Image
Source="PurchaseOrder.png"/>
<TextBlock
Height="21"
Margin="100,19,391,0"
VerticalAlignment="Top"
Text="{Binding Number}"/>
<TextBlock
Height="21"
Margin="100,46,391,0"
VerticalAlignment="Top"
Text="{Binding Supplier}"/>
</Grid>
</UserControl>
The DataContext property of the PurchaseOrderForm is automatically set to the PurchaseOrder
object so the bindings will work. After the Visual is written on the XPS document,
we can now call GetFixedDocumentSequence method that will return a FixedDocumentSequence
object. A FixedDocumentSequence object implements IDocumentPaginatorSource, which
is the parameter needed in the PrintPreviewWindow, as shown below.

The PrintPreviewWindow just contains a DocumentViewer control, which is shown in
the following listing.
<Window
x:Class="XPSDocumentPrinting.PrintPreviewWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Print Preview"
Height="Auto"
Width="Auto"
WindowStartupLocation="CenterOwner">
<DocumentViewer
Document="{Binding}"/>
</Window>
The Document property of the DocumentViewer is set using the DataContext which is
set to the IDocumentPaginatorSource object we got from the GetFixedDocumentSequence
method.
public partial class PrintPreviewWindow : Window
{
public PrintPreviewWindow(IDocumentPaginatorSource document)
{
InitializeComponent();
DataContext = document;
}
}
WPF printing is a subject too large to be covered fully in this article. I hope this
article helps WPF developers get started with printing.