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 } }
Tags: Charts, Printing, Silverlight, Silverlight Toolkit, Source code



[...] of the visual tree can be printed with some simple API calls (see the Printing Basics section on my Advanced Printing in Silverlight post). This post provides an example to print charts and looks at how elements can be auto scaled to fit [...]
Hi Gerg,
This is very cool. Is there an why we can show print pdfs in silverlight4.0. We have such requirement.
regards,
Soumya.