One of the big requests people have had for Silverlight was the ability to print, so one thing that's new in the Silverlight 4 Beta is the PrintDocument class which enables applications to print anything they want! There isn't an excess of fancy bells and whistles quite yet, but the support Silverlight provides is enough to do just about everything - it's just that sometimes you might need to do a little more than you expect. :) It's the classic "crawl, walk, run" approach to feature delivery, and this is a great first step that will be a welcome addition for everyone who's been wanting to print with Silverlight.
Okay, so that's all well and good and people can print any content they want and it all looks beautiful and works perfectly, right? Pretty much, yes! Well, except for certain scenarios involving charts from the Silverlight/WPF Data Visualization assembly. :( To be clear, if you try to print a Chart
that's already in the visual tree, it works just fine and what you see really is what you get. But if you've tried to create custom content specifically for printing (for example: a different layout, maybe some page headers and footers, etc.) and that content included a Chart
instance, you probably noticed a minor issue: none of the data points showed up on the page. Darn, it sure is hard to tell what's going on without those data points...
Fortunately, a bit of mental debugging reveals what's going on here. Charting's data points fade in by default - from Opacity 0 to 1 over the course of 0.5 seconds via a Visual State Manager transition. That's cool and it works great in the visual tree - however, when you're creating print-specific visuals and immediately printing them, those data points don't have a chance to do much fading in... So they're actually all there on the printed page, but they're at Opacity
0, so you can't see them!
What to do? Well, the most convenient thing would be for the Chart
code to somehow know it was going to be printed and SkipToFill those pesky animations. But there's no official way for a control to know it's being created for printing. There are a few tricks the Chart
could use to make an educated guess, but it doesn't do so yet - and I'm not sure that's the right answer anyway. So the next most obvious thing would be to re-Template the DataPoint
s to replace the default fading Template with one that doesn't fade, but instead starts fully visible. And as you'd expect, this works just fine - the data points show up and look great!
However, that's a lot of work that you probably don't want to have to do. It would be nice if there were a middle ground - some technique that was maybe a little bit of a hack, but that was really easy to implement and worked just about all the time...
And the good news is that there is such a hack! The idea builds upon the observation that printing a Chart
from the visual tree works correctly - so let's start by popping our new, print-ready chart into the visual tree to give it a chance to get all pretty, then print it once that's done. That seems easy enough, let's try it:
// Create customized content for printing
var chart = new Chart { Title = "My Printed Chart" };
var series = new LineSeries
{
ItemsSource = (PointCollection)Resources["Items"],
DependentValuePath = "Y",
IndependentValuePath = "X",
};
chart.Series.Add(series);
var content = new Border
{
BorderBrush = new SolidColorBrush(Colors.Magenta),
BorderThickness = new Thickness(10),
Child = chart,
};
// Create container for putting content in the visual tree invisibly
var visualTreeContainer = new Grid
{
Width = 1000,
Height = 1000,
Opacity = 0,
};
// Put content in the visual tree to initialize
visualTreeContainer.Children.Add(content);
LayoutRoot.Children.Add(visualTreeContainer);
// Prepare to print
var printDocument = new PrintDocument();
printDocument.PrintPage += delegate(object obj, PrintPageEventArgs args)
{
// Unparent the content for adding to PageVisual
visualTreeContainer.Children.Remove(content);
args.PageVisual = content;
};
// Print the content
printDocument.Print();
// Remove container from the visual tree
LayoutRoot.Children.Remove(visualTreeContainer);
Note that the sample application is creating a completely new Chart
instance here (with a print-specific title and custom magenta frame) - and that the printed output looks just like it should:
[Click here to download the source code for the ChartPrinting sample for the Silverlight 4 Beta as a Visual Studio 2010 solution]
The Chart
is added to the visual tree before the print dialog is shown - so it has plenty of time for those 0.5 second animations to play to completion while the user interacts with the dialog. The only sketchy bit is that an exceedingly fast user could possibly manage to print a Chart
before the animations had run all the way to completion. Yes, well, I said it was a hack up front, didn't I? :) If you want rock-solid printing behavior, you probably ought to be re-templating the data points in order to ensure the initial visuals are exactly as you want them. But if you're just interested in printing something attractive with a minimum of fuss or inconvenience - and your users aren't hopped up on performance enhancing drugs - you'll probably be quite successful with this technique instead.
PS - The XPS Printer driver is a fantastic tool for testing printing scenarios like this one. By printing to a file and opening that file in the XPS Viewer application that comes with Windows, I was able to avoid wasting a single sheet of paper during the course of this investigation. Highly recommended!