Silverlight 4's Printing Support

Silverlight Logo

As i've discussed with a number of my customers (with apologies to Siebrand) I never thought we needed printing. Printing on the Internet has a durable solution especially when it comes to to line-of-business applications: PDF. There are a plethora of report writing solutions that work and so using XAML to print has always been an unnecessary feature to me. But as most of you know the Silverlight team ignored me and listened to all of you and introduced a printing stack to Silverlight 4.

How It Works

The Silverlight 4 printing support allows you to specify a XAML tree to print. Overall its pretty simple. It all starts with the PrintDocument class. This class exposes several events that are used to call back to ask you about how to print individual pages. First, let's start with the simple PrintDocument creation:

PrintDocument doc = new PrintDocument();
doc.DocumentName = "Sample Print";
doc.StartPrint += new EventHandler<StartPrintEventArgs>(doc_StartPrint);
doc.PrintPage += new EventHandler<PrintPageEventArgs>(doc_PrintPage);
doc.Print();

When creating a new PrintDocument, there is a simple pattern: set the document name, handle the events and start the print process. The document name is the name that shows up in the spooler (at least in Windows, couldn't test it on a Mac). The StartPrint/EndPrint events are called before and after and are mostly used for setup/teardown of print elements.  The Print method starts the print process and asks the user to specify a printer. After the printer is choosen, the print system calls the PrintPage event sending in printer specs determined from the printer itself:

void doc_PrintPage(object sender, PrintPageEventArgs e)
{
  // Stretch to the size of the printed page
  printSurface.Width = e.PrintableArea.Width;
  printSurface.Height = e.PrintableArea.Height;

  // Assign the XAML element to be printed
  e.PageVisual = printSurface;

  // Specify whether to call again for another page
  e.HasMorePages = false;
}

The PrintPage event passes in a PrintPageEventArgs object that contains a couple of pieces of information. The most important are the Width and Height which can be used to help size your XAML before being printed.  It also allows you to specify the PageVisual which is any UIElement-derived element to be printed. Typically this is either a single control (e.g. DataGrid) or a container for other elements. You can also specify the whole page if you're just doing page printing.  Lastly, you must specify whether there are more pages to print with the HasMorePages property.  When HasMorePages is set to true, the page is printed and the PrintPage is called again to print the second page.

Caveats

I suspect the printing system will continue to go through some changes as currently its pretty rudementary. In addition, there are some behaviors that feel like bugs (though not confirmed as bugs yet).

The most annoying of these is how no matter how large you make an element that is in your visual tree, its still clipped based on the Silverlight Control size. For example, here is a DataGrid that i've resized to the page size (using the code above) but its still only showing elements that are on the Silverlight page:

Print Sample

Click to view the PDF

Creating the page directly with code seems to work better, but the problem there is that anything complex at all requires the code to be complex.  Being able to use Blend to create them would be better. My other nit that I suspect may be addressed is the ability to specify printer settings like Orientation, Color and resolution.

Overall it works fine, but be aware that its accomplishing this via the WriteableBitmap API. This means that it is taking the elements and rasterizing them and sending the one big image to the printer. There are two side affects here:

  • The print job size is going to be large as its just a page-sized bitmap. For a typical 600x600 DPI printer, the size of the bitmap could be very large which means more memory consumption and slower printing.
  • Printing to PDF's loses any textual relationship to the PDF. That means that if you print from SL4, its a single image so users will not be able to index or search the PDF for information since the textual information is lost.

Overall, I am glad they added it...but its still an adjunct to real report writing. Its not a replacement.  When you need to take a graph and dump it to the printer quickly, this facility really helps.  Here's a link to this example:

http://wildermuth.com/downloads/printingfun.zip

 

Comments:

Gravatar

remember, when your ItemsControl is virtualized, not all elements are in the visual tree...so you may need to create a virtual print doc...

Gravatar

Tim,

Yeah, I know, but I assumed when the item was resized it would show more...but no matter what size it is, its clipped to the SL Control size. (at least AFAICT).

Gravatar

Agree, it looks really... beta, you know.

As for clipping to control's size, I fixed that with putting printing content into a Canvas. That worked for me.

There is one more annoying problem: it's rendered on printer without any transforms applied... If you set, for say, RotateTransform on your PageVisual element, it will not be refelcted on printed page.

Gravatar

FYI: Fons Sonnemans has begun to port his Windows Forms ReportDocument code to Silverlight 4 at http://bit.ly/8FVb4Y. I think this is certainly one answer: use databinding and the print API to pour your content into a hidden new UIElement region (or virtual print doc, as Tim calls it) and print that. However, this will be a challenge in MVVM scenarios. I guess you could create a virtual print UIElement region (e.g., Canvas) in code, populate it from the VM and add this coded markup to the view as hidden, but that's an MVVM triad connection alert, good buddy. The alternative is to pre-create the markup and let the View's Datacontext take care of the rest (View first), but that would be inefficient duplication of bindings (maybe not so bad for a v1 workaround). I'm sure we'll collectively figure out a best practice or two for both plain ol' MVVM (POM) and Prism/MEF scenarios in LOB apps, because it is so vital to Silverlight's success.

Gravatar

Can I exploit Excel's printing prowess or does one invents the wheel again (using MEF).ExcelViwer is a universal software in LOB apps. How does one transfer to Excel is there a Class on SL4 to achieve it.

Gravatar

I've also noticed that converting the UI you want printed into a WriteableBitmap produces unpredictable results when any UIElements have effects applied (e.g., DropShadow, etc.). Perhaps this is another argument for using a secondary, hidden data bound canvas for printing with just the data and a printable layout.

Gravatar

Thanks for posting this. Just a quick clarification: Silverlight Printing does currently allow users to specify Color/Grayscale, Orientation and Resolution(DPI)--Silverlight handles doing the grayscale conversion and the DPI conversion for you and exposes a PrintableArea with the proper aspect ratio for either landscape of portrait prints.

Gravatar

Thanks for your article.
Would there be anyway to avoid the dialog about which printer to use ? In my application, I want to control directly where something prints. Hopefully there will be a way to determine if the printer is offline or not.

Gravatar

Is there going to be a way to bypass the browser print prompt and spool it directly to the printer? We have an app that requires printing to a specific printer and it is not neccessary to ask the user each time.

Gravatar

J. Hemphill & Eisen,

Currently there is no way to do this but may be available in the release (probably only under the Elevated Permissions in OOB).

Gravatar

How would you expect a user to print when offline without this feature?

Gravatar

Josh,

You can't.

Gravatar

When we will have a free report viewer for reporting services in silverlight?

Regards

Gravatar

When we will have a free report viewer for reporting services in silverlight?

Regards

Gravatar

Shawn,

Thanks for the post. I believe you are right overall. For LOB applications an interface to Report Viewer can be a very nice solution..printing, PDF, HTML, paging, viewing, Excel.. If MSFT (or a third party) were to build such a module for Silverlight, MSFT would benefit those applying Silverlight to their business. Of course, this is no minor endeavor: transforming a portion of the visual tree to a less graphical interface such as Report Writer would have its limitations. (I took that step personally and found it tedious.) For many businesses though, the PDF solution is probably more important than a image printing solution.

Microsoft Dynamics uses the Report Viewer to its benefit.

You are entirely correct about problem of size. If you want 600 dpi, the image becomes unbearably large if that image must be stored in perpetuity because it is indeed a business-related document.

Silverlight is sometimes treated like a replacement for viewing an ASP page – except more graphic and object oriented. If one looks at Silverlight instead as being an environment that sits closer to the programs of old where the end-user generates complex content far beyond what is available on the web today, then having a generic method (Report Viewer) of saving, viewing and printing that information becomes more important.

Overall, the SL4 bitmap printing solution is at least quick and easy.

Gravatar

Why is printing support not needed in SL?
Not all printing needs are reports.
Probably the number one printed material in the world are receipts.
I'm developing a POS system and tickets are not a good candidate for a report.

Is there a big drawback from printing via SL?

Gravatar

Hi, i try to print a stackpaneL. It works perfect but it dosnt print the page 2. THe printing (even in PDDF) stop at the end of the first page. If i set hasMorePages = true. It's print in loop page1.

How can i make it ?

Gravatar

Great Article!

How to add custom Header and Footer with Silverlight 4's Printing Support?

I will appreciate your reply.

Thanks

Gravatar

I just made a print solution in Silverlight 3. Since my solution relies on taking a snapshot of the displaying visual, I wasn't surprised at getting clipped to the application size. A simple solution is resizing the application before taking the snapshot. A simple JavaScript function will do. Of course you have to use Dispatcher.BeginInvoke on your snapshot to wait for the resize to complete.

I'm considering posting a demo, but since its SL3 and moments from obsolesence, not sure if its worth the time.

Gravatar

The Second line of code doc.DocumentName = "Sample Print";
Give compilation error.

Gravatar

Silverlight did not support a0 size print

Gravatar

Great article. Is there a way to print in landscape?

Thanks.

Gravatar

usama wahab khan,

The page size shouldn't matter...its specified by the user when they pick their printer.

Gravatar

Rama,

The user needs to specify it when they are presented with the print dialog. Currently there isn't support for client-specified rotation or page size.


 



 
Save Cancel