Silverlight 4 introduced bitmap-based printing to Silverlight
developers. It was great for printscreen-type single-page jobs, but
when it came down to anything more than two or three pages, it just
took too long? Why? Printing in Silverlight 4 would send a bitmap
representing the entire page to the printer - a serious amount of
data.
NOTE: This post covers features not yet released in the public
Silverlight 5 beta, but planned for RTM. It's possible that APIs or
the features themselves may change before release. It's possible
that the final behavior will be different from what I write about
here. /warned
The main use-case for bitmap printing was printscreen type work
anyway, not reporting. The fact that some of us built report
writers around it was just a bonus :) Now, with Silverlight 5,
we have Postscript-based vector printing, providing both a faster
and higher resolution general printing mechanism, and a great
framework upon which others can build their own report writers.
Printing Basics
Just as it was in Silverlight 4, Printing is centered around the
PrintDocument class, found in System.Windows.Printing. This class
has three primary events: BeginPrint, EndPrint, and PrintPage,
which are your hooks into the printing system. You do setup in
BeginPrint, teardown in EndPrint, and all the actual page
production in PrintPage.
Page Markup
Here's the simple test page XAML I used for this printing
example.
<Grid x:Name="LayoutRoot" Background="White">
<Button Content="Print Bitmap"
Height="23"
HorizontalAlignment="Left"
Margin="141,79,0,0"
Name="PrintBitmap"
VerticalAlignment="Top"
Width="95"
Click="PrintBitmap_Click" />
<Button Content="Print Vector"
Height="23"
HorizontalAlignment="Left"
Margin="141,108,0,0"
Name="PrintVector"
VerticalAlignment="Top"
Width="95"
Click="PrintVector_Click" />
<Button Content="Force Vector"
Height="23"
HorizontalAlignment="Left"
Margin="141,137,0,0"
Name="PrintVectorForced"
VerticalAlignment="Top"
Width="95"
Click="PrintVectorForced_Click" />
</Grid>
The application UI looks really simple, just three buttons on a
page. This is one of my finest designs.
Next, I'll wire up an event handler for each button, and use it
to demonstrate the behavior of the three different printing
approaches.
Page Code for Basic Vector Printing
Here's the code for a basic vector print of 30 rows.
// Tests basic (not forced) vector printing
private void PrintVector_Click(object sender, RoutedEventArgs e)
{
PrintDocument doc = new PrintDocument();
doc.PrintPage += (s, ea) =>
{
StackPanel printPanel = new StackPanel();
Random rnd = new Random();
for (int i = 0; i < 30; i++)
{
TextBlock row = new TextBlock();
row.Text = "This is row " + i + " of the current page being printed in vector mode.";
printPanel.Children.Add(row);
}
ea.PageVisual = printPanel;
ea.HasMorePages = false;
};
PrinterFallbackSettings settings = new PrinterFallbackSettings();
doc.Print("Silverlight Vector Print");
}
Note that the PageVisual is assigned after the printPanel is
populated. If you assign it prior, and do not force a recalculation
of layout (in my example, the panel isn't in the visual tree, but
layout is calculated with you assign PageVisual), you'll get a
StackPanel with 30 items all piled on each other in the same row.
The easiest way to fix this is to assign the PageVisual after the
visual has all its children populated.
You could also point the PageVisual to an on-screen visual if
you desire. If you're going to do that, you'll need to unhook the
visual from the tree first, as a single element cannot have two
parents.
If you have no more pages to print other than this one, set
HasMorePages to false. If you have additional pages after this one,
set it to true.
Printer Fallback Settings and Forcing Vector Printing Mode
New in Silverlight 5 is the PrinterFallbackSettings class. This
class is used by one of the overloads of PrintDocument.Print to set
two options: ForceVector and OpacityThreshold.
In the previous example, if you had any elements that had
opacity other than 1.0, perspective transforms, or other things
PostScript doesn't understand, Silverlight would silently fall back
to bitmap-based printing.
ForceVector forces Silverlight to print in vector mode, assuming
you have a PostScript-enabled printer driver, even when
postscript-incompatible items exist in the element tree assigned to
PageVisual. You use this in tandem with OpacityThreshold. The
Opacity threshold sets the value over which Silverlight will treat
an element's opacity as 1.0 to support PostScript printing.
// tests trying to force vector printing mode
private void PrintVectorForced_Click(object sender, RoutedEventArgs e)
{
PrintDocument doc = new PrintDocument();
doc.PrintPage += (s, ea) =>
{
StackPanel printPanel = new StackPanel();
Random rnd = new Random();
for (int i = 0; i < 30; i++)
{
TextBlock row = new TextBlock();
row.Opacity = (rnd.Next(3, 10)) / 10.0;
row.Text = "This is row " + i + " of the current page being printed. Opacity is " + row.Opacity;
printPanel.Children.Add(row);
}
ea.PageVisual = printPanel;
ea.HasMorePages = false;
};
PrinterFallbackSettings settings = new PrinterFallbackSettings();
settings.ForceVector = true;
settings.OpacityThreshold = 0.5;
doc.Print("Silverlight Forced Vector Print", settings);
}
If your content or your printer doesn't support PostScript
printing, Silverlight automatically falls back to sending an
uncompressed bitmap to the printer. If your printer doesn't support
PostScript, you'll see the effect of opacity in the printed results
(some items lighter colored than others, for example) as the
fallback bitmap mode supports opacity.
Does my Printer Support PostScript?
Vector printing requires PostScript support in your printer
driver (and printer). Compressed bitmap support also requires
PostScript. Full stop.
If you're a home user, there's a reasonable chance your selected
printer driver is not set up for PostScript printing unless you're
on a Mac, or you do a lot of print publishing work. In many cases,
the printers support PostScript emulation, but the default drivers
don't include that functionality. For example, my HP LaserJet 1320
uses the HP Universal PCL5 Printing Driver which does not include
PostScript support. The heavy-duty office printers used in many
offices often have built-in firmware-level fast PostScript support,
and/or appropriate drivers installed. In most cases, you simply
need select a different driver. Your mileage will vary, but this is
something you'll want to check out before you start building
printing into your applications.
Those are the two options for printing in vector mode. Another
type of printing, introduced in Silverlight 4, is bitmap-based
printing.
Printing in Bitmap Mode
Sometimes you know you want to print in bitmap mode. Rather than
let vector mode fall back to bitmap, you can simply force bitmap
printing from the start. If you have a PostScript compatible
printer and driver, this is quite a bit faster than it was in
Silverlight 4, as the bitmap is compressed. If you don't have a
PostScript driver, it sends a plain old uncompressed bitmap just
like Silverlight 4.
// tests printing in bitmap mode
private void PrintBitmap_Click(object sender, RoutedEventArgs e)
{
PrintDocument doc = new PrintDocument();
doc.PrintPage += (s, ea) =>
{
StackPanel printPanel = new StackPanel();
Random rnd = new Random();
for (int i = 0; i < 30; i++)
{
TextBlock row = new TextBlock();
row.Opacity = (rnd.Next(3, 10)) / 10.0;
row.Text = "This is row " + i + " of the current page being printed in bitmap mode. Opacity is " + row.Opacity;
printPanel.Children.Add(row);
}
ea.PageVisual = printPanel;
ea.HasMorePages = false;
};
doc.PrintBitmap("Silverlight Bitmap Print");
}
Bitmap mode will preserve the opacity settings, as well as
ensure render transforms are printed (assuming you apply them) etc.
It's not the best approach for printing a report, but it's the
highest-fidelity approach for printing visuals when you want to do
the equivalent of a print-screen.
The resolution of the bitmap sent is set to the selected printer
resolution, typically 600dpi.
Efficient Printing
So, for the most efficient printing of multi-page reports,
you'll want to make sure you do the following:
- Have a PostScript-compatible printer with an appropriate
PostScript driver (typically ends with " PS")
- Avoid Opacity other than 1.0 in your elements to be printed (or
use the appropriate fallback settings)
- Leave out perspective transforms, 3d, and other things not
compatible with PostScript printing.
Where to Go From Here
In
Silverlight 4 in Action, and expanded to include vector
printing in Silverlight 5 in Action, as well as the codeplex
project mentioned earlier on this post, I show how to handle
multi-page printing scenarios, set up headers and footers, handle
dynamic row sizes and other typical reporting requirements. While
this isn't a replacement for a real report writer, it will be
useful for small reports and even larger ones until other sources
step up with fully-featured writers.
(There is no source code to download with this post)