Welcome to the fist part of my ‘.Net 4.0 Printing Made Easy’ series. In this part we’ll take a quick look at the .Net printing support, what it does do for us and just as important, what it doesn’t do for us.
In .Net 3.0 we were given a great new printing framework based around XPS documents. This immediately opened up a world of vector based printing where you can print your WPF vector controls and have them appear as sharp as ever, or if printing to an XPS or PDF document, your print out will be preserved in its full vector glory. What makes this even better is that you as a developer don’t have to worry about any of this, you just send your data off to one of the many methods provided and the rest is done for you. There are of course some instances where you can’t print raw vector data out and need to handled rasterised data. This too is very simple and there are a few tricks you can use to make it even easier.
Using the PrintDialog
One of the first things you’ll want to do when it comes to printing is to choose a printer. There are a few different ways of enumerating printers visible to a machine and the method that is best for you will depend on the approach you take to printing in your application. The first method and perhaps the simplest is the good old PrintDialog class defined in the System.Windows.Controls namespace (PresentationFramework.dll).
The PrintDialog object gives us a handy, pre-built dialog for choosing and customising a printer and page setup. The dialog can then be used to send your job to the selected printer with very little coding required (of course you’ll still need to code the generation of the content to be printed). Below is a very simple method that creates a new instance of a PrintDialog and displays it. The OnShowPrintDialogButtonClick method is set as the click event handler in the Window’s XAML. Those of you familiar with dialogs in .Net will be familiar with this. The result of the call to ShowDialog() will give us a Nullable<Boolean> which can be used to determine whether clicked the ‘Ok’ button or not (and the reason using the explicit quality operator).
private void OnShowPrintDialogButtonClick(object sender, EventArgs e) { PrintDialog printDialog = new PrintDialog(); if (printDialog.ShowDialog() == false) return; }
Here is the print dialog in all its glory:
It’s not much to look at but in just a few lines of code you have a complete list of available printers along with access to ceach device’s configuration settings as well as a few other treats. All of the printer and page setup data can be accessed through the dialog’s PrintTicket property which returns a PrintTicket object, defined in the System.Printing namespace (ReachFramework.dll). In a nutshell the print ticket details the settings and configuration for the print job as defined by the user from the print dialog, namely paper size, orientation etc. This print ticket will be used a lot when generating content to be printed.
Finding Available Print Queues
The PrintDialog isn’t always going to be suited to your needs, if for example you’re creating a rich print preview control a la Microsoft Word 2010 etc. For this you’ll need to provide configuration options for all the features you’ll want to support. The first step in this process is to get a list of available printers, which we do so through a PrintServer defined in the System.Printing namespace (System.Printing.dll). The PrintServer object is quite powerful and can be used for myriad print queue related activities, but for now all we need is a list of PrintQueue objects known to the print server. The code snippet below demonstrates the retrieval of a collection of printers (print queues).
private IEnumerable GetPrintQueues() { PrintServer printServer = new PrintServer(); PrintQueueCollection printQueues = printServer.GetPrintQueues(new[] { EnumeratedPrintQueueTypes.Local, EnumeratedPrintQueueTypes.Connections }); return printQueues; }
There are several overloads for the GetPrintQueues() method, the one you use will depend entirely on your situation. Here we use a method that allows us to pass in EnumeratedPrintQueueTypes values that ensure we get both local and network printers. You can take the collection of print queues and present them in the UI somehow allowing the user to select their desired printer. Like the PrintDialog, a PrintQueue also exposes a print ticket object, UserPrintTicket and DefaultPrintTicket, allowing you to access and modify printer and page settings, something you will need later when you come to provide configuration options such as selected page size and orientation etc.
Finding the User’s Default Print Queue
A nice little touch we can add here is to discover the user’s default printer and use that as the default in the app. Using the LocalPrintServer class’ static method GetDefaultPrintQueue, it’s easy to find.
private PrintQueue GetDefaultPrintQueue() { return LocalPrintServer.GetDefaultPrintQueue(); }
Using a PrintTicket
Now that you have a list of printers and the local default printer, the next step is to allow the user to change aspects of the active PrintTicket object. A print ticket is a collection of properties defining features of the print job. These include settings such as page orientation, page size etc. Many of the properties won’t be of interest to you and can safely be ignored. We’ll look at two properties here: page orientation (PageOrientation) and page size (PageMediaSize).
To get a print ticket you can either create one or use an existing one. The easiest way to get a print ticket object is through the UserPrintTicket property (or DefaultPrintTicket property if the former is null) of the active PrintQueue. In some rare instances, reading either of these properties may result in a PrintSystemException (System.Printing namespace, ReachFramework.dll) being thrown when the printer driver doesn’t provide valid values. In this instance just create a new PrintTicket object and use that. Print tickets are used again a bit later on, when it comes to sending data to the printer, something we’ll cover in part 2.
Measuring Capability with PrintCapabilities
When dealing with settings in a print ticket, you first need to know what the target print queue is capable of. There’s no point showing options for duplexing and sending those commands if the target printer doesn’t support duplexing in the first place. To gain a heads-up on the target printer’s capabilities, there exist a PrintCapabilities class in the System.Printing namespace (Reachframework.dll). This is a sealed class with read-only properties defining the features supported by a given print queue. A PrintCapabilities object can now be used to remove features which are not supported.
var pageMediaSizes = pc.PageMediaSizeCapability.OrderBy( i => i.PageMediaSizeName).Select( i => { return i.PageMediaSizeName.ToString(); }).ToList();
Putting it all together
I’ve created a simple demo app that puts these concepts together giving a practical example. The app comprises of a window with a few simple controls with a view model that defines the properties and methods described above. There’s a huge amount going on here, but hopefully it gives a good introduction to some of the common classes you’ll see when dealing with printing. The following class diagram below gives an overview of the app.
Here’s a preview of the app running.
Download the .Net 4, Visual Studio 2010 source for this application.
Next Steps
In the next part I’ll look at actually sending something to a print queue and look at the different approaches you can take to this.
[…] part 1 I will try to give a brief overview of printing in WPF and get you started with some code for […]
How to speed up printing with dot matrix printer?. when I print any pdf /rtf file through our .net windows application, it prints very slow.
please help me.
Thanks.
Hi,
Thanks for the introduction. I am indeed one of those programmers who has recoiled in horror at the idea of programming printing facilities, and have so far avoided it through my years in VB6 and upwards.
One “gotcha” I got when starting this journey was that there is another class called PrintDialog in another assembly, and that doesn’t expose a PrintTicket, causing much confusion.
This step 1 has helped me get started, only to find there’s no step 2. I’m sure I will find out what I need from other sources, but wanted to say that this page was the starting point that gave me the reassurance that this kind of thing was possible,
If there’s more on this subject, please let me know.
Glen