Printing support has been introduced in Silverlight 4. This
means that any part of the visual tree can be printed in a simple
way via API calls. This article gives an overview of how to execute
basic printing, looks at what happens when printing complex objects
(e.g. charts) and describes how to auto scale elements to fit the
printed page.
Printing Basics
The API calls to make printing happen in Silverlight couldn't
really be much easier. To print, one has to create a PrintDocument
class (found in the System.Windows.Printing) class. After this
object created, one has to subscribe to the PrintPage event and
call the Print method(). In the PrintPage event the visual tree
element to be printed has to be passed. So the simplest printing
scenario looks like this:
UIElement elementToPrint; // The element to be printed
PrintDocument doc = new PrintDocument(); // Create the PrintDocument object that will do the printing
doc.PrintPage += (s, args) =>
{
// Set the element that needs to be printed.
// As soon as this is set, printing starts
args.PageVisual = elementToPrint;
}
Printing events
Three events are available when printing, all of which are
events of the PrintDocument object. These are:
- BeginPrint: this event fires right when the
printing has started, after the user has been prompted the print
dialogue and selected the printer to use. So this event does not
fire before the printing is started, but rather at the exact
beginning of it
- PrintPage: fired before each page is printed.
In this event one can specify the exact contents of the next page
to be printed
- EndPrint: fired when printing has ended or the
user has cancelled the printing.
It's important to note that the PrintPage event is
fired for every printed page and it's the developers responsibility
to specify the contents of the next printed page, Silverlight
doesn't do this task. It's safe to say therefore that the current
printing API is quite low level:
this means lots of freedom for specifying the printed content, but
also implies extra development needed for extra features like multi
page printing and pagination.
Printing a chart
There are a couple examples on various blogs that demonstrate
printing simple elements like labels and grids. However to test the
functionality of the printing API I decided to see what happens
when printing charts.
To test this I've constructed charts with the help of the Silverlight
Toolkit. So I've created four different charts and tested them
being printed by adding printing support similar to as described
above (the source code is available at the end of the post). I've
also implemented printing the whole page itself (the only
difference in printing a single chart or the whole page is which
element the PageVisual is pointing at). Try the example for
yourself:
The results were as I expected: the charts had the same look and
size after printing: 
Auto scaling charts to fit the page
Printing worked fine, however when printing a single chart it
kept it's original, small size which didn't look too good on a
printed page:

This is because when passing the chart for printing it's printed
at the same size as it is on the screen. To auto scale the printed
element to the page this size has to be adjusted to the page size.
This can be done when the PrintPage event is fired as the
print area size is only accessible at this point:
UIElement elementToPrint; // The element to be printed
PrintDocument doc = new PrintDocument();
doc.PrintPage += (s, args) =>
{
// Increase the height and width of the printable element to match the print area size
elementToPrint.Width = args.PrintableArea.Width;
elementToPrint.Height= args.PrintableArea.Height;
args.PageVisual = elementToPrint;
}
This approach works for simple elements, but unfortunately not
for complex visual trees, like charts. After the Width and Height
is set on a chart it takes some time after the changes run through
their visual tree, triggering events and re-calculating child
elements. So when PageVisual is set on the passed
arguments this change has not yet taken effect and the same small
chart is printed.
Luckily this can be worked around. The PrintPage event
is called after smaller delays for a while until
PageVisual is null. If PageVisual is null for too
long the framework will abort printing, however these repeated
calls give enough time to resize the charts and then set the new
size:
doc.PrintPage += (s, args) =>
{
// Printing only starts when args.PageVisual != null
// Only set args.PageVisual to elementToPrint once its size has been updated
elementToPrint.SizeChanged += (s1, args1) =>
{
args.PageVisual = elementToPrint;
};
}
This approach makes it possible to auto scale either the
individual charts or the whole page to the size of the printed
page. (Of course after the printing has ended the chart or page
size needs to be reset to the original, this has also been
implemented in this example). This modified example looks as
following:
Printing multiple pages
As I've mentioned before the current Silverlight printing API is
quite low level: the developer has to specify what to print on a
per page basis. Because of this it doesn't have multi page printing
support: the responsibility to implement this is up to the
developer.
Implementing multi-page printing is not simple: implementation
might need to include pagination and smart splitting up of pages.
Multi-page printing is out of scope of this article, however I'd
recommend checking out
David Poll's CollectionPrinter control that adds pretty good
multi-page templated printing support to Silverlight
applications.
Source code
The source of this application can be downloaded from here: PrintingExample.zip
You can also browse the source code online.
Mainpage.xaml:
<userControl x:Class="PrintingExample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:toolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
xmlns:controlsToolkit="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit"
xmlns:chartingToolkit="clr-namespace:System.Windows.Controls.DataVisualization.Charting;assembly=System.Windows.Controls.DataVisualization.Toolkit"
xmlns:samples="clr-namespace:System.Windows.Controls.Samples;assembly=System.Windows.Controls.Samples"
xmlns:samplesCommon="clr-namespace:System.Windows.Controls.Samples;assembly=System.Windows.Controls.Samples.Common"
mc:Ignorable="d"
d:DesignHeight="550" d:DesignWidth="700">
<userControl.Resources>
<samplesCommon:WidgetPopularityPollCollection x:Key="WidgetPopularity"/>
<samplesCommon:GizmoPopularityPollCollection x:Key="GizmoPopularity"/>
<samplesCommon:DoodadPopularityPollCollection x:Key="DoodadPopularity"/>
<!-- Style for the Grid wrapper around each sample item -->
<style x:Key="WrapperStyle" TargetType="Grid">
<setter Property="VerticalAlignment" Value="Top"/>
<setter Property="Margin" Value="1,4,4,1"/>
<setter Property="MinWidth" Value="340"/>
<setter Property="MinHeight" Value="230"/>
</style>
</userControl.Resources>
<grid Name="LayoutRoot">
<controlsToolkit:WrapPanel>
<grid Style="{StaticResource WrapperStyle}">
<stackPanel>
<chartingToolkit:Chart Title="Sales (Stacked Line)" LegendTitle="Product" x:Name="StackedLineChart">
<chartingToolkit:StackedLineSeries>
<chartingToolkit:SeriesDefinition ItemsSource="{StaticResource DoodadPopularity}" DependentValuePath="Percent" IndependentValuePath="Date" Title="Doodad"/>
<chartingToolkit:SeriesDefinition ItemsSource="{StaticResource GizmoPopularity}" DependentValuePath="Percent" IndependentValuePath="Date" Title="Gizmo"/>
<chartingToolkit:SeriesDefinition ItemsSource="{StaticResource WidgetPopularity}" DependentValuePath="Percent" IndependentValuePath="Date" Title="Widget"/>
<chartingToolkit:StackedLineSeries.DependentAxis>
<chartingToolkit:LinearAxis Orientation="Y" Minimum="0" Title="Quantity (1000s)" ShowGridLines="True"/>
</chartingToolkit:StackedLineSeries.DependentAxis>
</chartingToolkit:StackedLineSeries>
</chartingToolkit:Chart>
<button Content="Print: exact size" Name="PrintStackedLineChartButton" Click="PrintStackedLineChartButton_Click" Width="150" Height="25" Margin="0,5,0,0"/>
<button Content="Print: stretch to full page" Name="PrintStackedLineChartButtonStrech" Click="PrintStackedLineChartButtonStrech_Click" Width="150" Height="25" Margin="0,5,0,0"/>
</stackPanel>
</grid>
<grid Style="{StaticResource WrapperStyle}">
<stackPanel>
<chartingToolkit:Chart Title="Sales (100% Stacked Area)" LegendTitle="Product" x:Name="StackedAreaChart">
<chartingToolkit:Stacked100AreaSeries>
<chartingToolkit:SeriesDefinition ItemsSource="{StaticResource DoodadPopularity}" DependentValuePath="Percent" IndependentValuePath="Date" Title="Doodad"/>
<chartingToolkit:SeriesDefinition ItemsSource="{StaticResource GizmoPopularity}" DependentValuePath="Percent" IndependentValuePath="Date" Title="Gizmo"/>
<chartingToolkit:SeriesDefinition ItemsSource="{StaticResource WidgetPopularity}" DependentValuePath="Percent" IndependentValuePath="Date" Title="Widget"/>
</chartingToolkit:Stacked100AreaSeries>
</chartingToolkit:Chart>
<button Content="Print: exact size" Name="PrintStackedAreaChartButton" Click="PrintStackedAreaChartButton_Click" Width="150" Height="25" Margin="0,5,0,0"/>
<button Content="Print: stretch to full page" Name="PrintStackedAreaChartStrech" Click="PrintStackedAreaChartStrech_Click" Width="150" Height="25" Margin="0,5,0,0"/>
</stackPanel>
</grid>
<!-- Column (Multiple) -->
<grid Style="{StaticResource WrapperStyle}">
<stackPanel>
<chartingToolkit:Chart Title="Column (Multiple)" LegendTitle="Legend" x:Name="ColumnChart">
<chartingToolkit:Chart.Series>
<chartingToolkit:ColumnSeries Title="Series A">
<chartingToolkit:ColumnSeries.ItemsSource>
<toolkit:ObjectCollection>
<system:Int32>1</system:Int32>
<system:Int32>3</system:Int32>
<system:Int32>5</system:Int32>
<system:Int32>2</system:Int32>
</toolkit:ObjectCollection>
</chartingToolkit:ColumnSeries.ItemsSource>
</chartingToolkit:ColumnSeries>
<chartingToolkit:ColumnSeries Title="Series B">
<chartingToolkit:ColumnSeries.ItemsSource>
<toolkit:ObjectCollection>
<system:Int32>2</system:Int32>
<system:Int32>4</system:Int32>
<system:Int32>6</system:Int32>
<system:Int32>3</system:Int32>
</toolkit:ObjectCollection>
</chartingToolkit:ColumnSeries.ItemsSource>
</chartingToolkit:ColumnSeries>
<chartingToolkit:ColumnSeries Title="Series C">
<chartingToolkit:ColumnSeries.ItemsSource>
<toolkit:ObjectCollection>
<system:Int32>4</system:Int32>
<system:Int32>3</system:Int32>
<system:Int32>2</system:Int32>
<system:Int32>5</system:Int32>
</toolkit:ObjectCollection>
</chartingToolkit:ColumnSeries.ItemsSource>
</chartingToolkit:ColumnSeries>
</chartingToolkit:Chart.Series>
<chartingToolkit:Chart.Axes>
<chartingToolkit:LinearAxis Orientation="Y" Minimum="0" ShowGridLines="True"/>
</chartingToolkit:Chart.Axes>
</chartingToolkit:Chart>
<button Content="Print: exact size" Name="PrintColumnChartButton" Click="PrintColumnChartButton_Click" Width="150" Height="25" Margin="0,5,0,0"/>
<button Content="Print: stretch to full page" Name="PrintColumnChartButtonStrech" Click="PrintColumnChartButtonStrech_Click" Width="150" Height="25" Margin="0,5,0,0"/>
</stackPanel>
</grid>
<!-- Pie -->
<grid Style="{StaticResource WrapperStyle}">
<stackPanel>
<chartingToolkit:Chart Title="Pie" x:Name="PieChart">
<chartingToolkit:Chart.Series>
<chartingToolkit:PieSeries IndependentValueBinding="{Binding Species}" DependentValueBinding="{Binding Count}">
<chartingToolkit:PieSeries.ItemsSource>
<toolkit:ObjectCollection>
<samples:Pet Species="Dogs" Count="3"/>
<samples:Pet Species="Cats" Count="4"/>
<samples:Pet Species="Birds" Count="2"/>
<samples:Pet Species="Mice" Count="3"/>
</toolkit:ObjectCollection>
</chartingToolkit:PieSeries.ItemsSource>
</chartingToolkit:PieSeries>
</chartingToolkit:Chart.Series>
</chartingToolkit:Chart>
<button Content="Print: exact size" Name="PrintPieChartButton" Click="PrintPieChartButton_Click" Width="150" Height="25" Margin="0,5,0,0"/>
<button Content="Print: stretch to full page" Name="PrintPieChartButtonStrech" Click="PrintPieChartButtonStrech_Click" Width="150" Height="25" Margin="0,5,0,0"/>
</stackPanel>
</grid>
<stackPanel Orientation="Vertical">
<button Content="Print whole page: exact size" Name="PrintWholePageButton" Click="PrintWholePageButton_Click" Width="250" Height="25" Margin="0,5,0,0"/>
<button Content="Print whole page: stretch to full page" Name="PrintWholePageButtonStrech" Click="PrintWholePageButtonStrech_Click" Width="250" Height="25" Margin="0,5,0,0"/>
</stackPanel>
</controlsToolkit:WrapPanel>
</grid>
</userControl>
MainPage.xaml.cs:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Windows.Printing;
namespace PrintingExample
{
public partial class MainPage : UserControl
{
private FrameworkElement _elementToPrint;
private Double _elementOriginalWidth;
private Double _elementOriginalHeight;
public MainPage()
{
InitializeComponent();
}
private void Print(FrameworkElement elementToPrint, string documentName, bool stretchToFullSize)
{
PrintDocument doc = new PrintDocument();
_elementToPrint = elementToPrint;
doc.PrintPage += (s, args) =>
{
// Set element to be printed
_elementOriginalWidth = _elementToPrint.Width;
_elementOriginalHeight = _elementToPrint.Height;
if (stretchToFullSize)
{
_elementToPrint.Width = args.PrintableArea.Width;
_elementToPrint.Height = args.PrintableArea.Height;
// Printing only starts when args.PageVisual != null
// Only set args.PageVisual to elementToPrint once its size has been updated
_elementToPrint.SizeChanged += (s1, args1) =>
{
args.PageVisual = elementToPrint;
};
}
else
{
args.PageVisual = elementToPrint;
}
};
doc.BeginPrint += (s, args) =>
{
// Show that printing has begun: not implemented in this case
};
doc.EndPrint += (s, args) =>
{
// Show that printing has ended: not implemented in this case
// Reset printed element size to original
_elementToPrint.Width = _elementOriginalWidth;
_elementToPrint.Height = _elementOriginalHeight;
};
doc.Print(documentName);
}
#region Click even handlers
private void PrintColumnChartButton_Click(object sender, RoutedEventArgs e)
{
Print(ColumnChart, "Column Chart", false);
}
private void PrintColumnChartButtonStrech_Click(object sender, RoutedEventArgs e)
{
Print(ColumnChart, "Column Chart", true);
}
private void PrintStackedLineChartButton_Click(object sender, RoutedEventArgs e)
{
Print(StackedLineChart, "Stacked Line Chart", false);
}
private void PrintStackedLineChartButtonStrech_Click(object sender, RoutedEventArgs e)
{
Print(StackedLineChart, "Stacked Line Chart", true);
}
private void PrintStackedAreaChartButton_Click(object sender, RoutedEventArgs e)
{
Print(StackedAreaChart, "Stacked Area", false);
}
private void PrintStackedAreaChartStrech_Click(object sender, RoutedEventArgs e)
{
Print(StackedAreaChart, "Stacked Area", true);
}
private void PrintPieChartButton_Click(object sender, RoutedEventArgs e)
{
Print(PieChart, "Pie Chart", false);
}
private void PrintPieChartButtonStrech_Click(object sender, RoutedEventArgs e)
{
Print(PieChart, "Pie Chart", true);
}
private void PrintWholePageButton_Click(object sender, RoutedEventArgs e)
{
Print(LayoutRoot, "Whole Page", false);
}
private void PrintWholePageButtonStrech_Click(object sender, RoutedEventArgs e)
{
Print(LayoutRoot, "Whole Page", true);
}
#endregion
}
}
Interested in an performant, easy to extend, fully themeable
Silverlight / WPF charting library? Give the
free version of
Visiblox a try!