How to Print a Document on a Server via the XpsPrint API
This topic will be useful to anyone who wants to submit an XPS document to the unmanaged
XpsPrint API from a .NET application. But the main goal if this article is to show how to print a word processing document from an ASP.NET or Windows Service application using Aspose.Words and the
Print a Document on a Server Problem
If developping a .NET application that needs to produce printed output, there are classes in the
System.Drawing.Printing namespace, that can help, or the WPF classes. But, as it turns out, if you develop an ASP.NET or Windows Service application, the options for printing are severely limited because Microsoft recommends against using these approaches. See the links below for more information.
The .NET Framework Printing classes are not supported from a service. This includes ASP pages, which generally run in the context of the server service.
Classes within the
System.Drawing.Printing namespace are not supported for use within a Windows service or ASP.NET application or service. Attempting to use these classes from within one of these application types may produce unexpected problems, such as diminished service performance and run-time exceptions.
The use of WPF to build Windows services is unsupported. Because WPF is a presentation technology, the Windows service requires the appropriate permissions to perform visual operations that involve user interaction. If the Windows service does not have the appropriate permissions, there may be unexpected results.
Document object provides a family of the
System.Drawing.Printing namespace. There are many customers of Aspose.Words who use this printing method in their server-side applications without any problems, but there is a way to comply with Microsoft’s recommendations and it is described in this article.
Print a Document on a Server Solution
The proper way to print documents according to Microsoft is to use the unmanaged XpsPrint API. This API is available on Windows 7, Windows Server 2008 R2 and also on Windows Vista, provided the Platform Update for Windows Vista is installed.
Since Aspose.Words can easily convert any document to XPS, we only need to write code that passes an XPS document to the XpsPrint API. The only problem is that the XpsPrint API is unmanaged and it requires some knowledge of the Platform Invoke.
Print a Document on a Server Code
We have created the
XpsPrintHelper class with the
The last parameter is a Boolean value that specifies whether the code should wait until the job is printed or return immediately after the print job has been submitted. If you choose to return immediately, then you will not be able to determine whether the document has printed successfully or not in the end. Below code sample invoke the utility class to print via XPS. You can download the template file of this example from here.
There are two overloads of the
XpsPrintHelper.Print method. The first overload takes an
Aspose.Words.Document object and saves it into a MemoryStream in the XPS format. Then it invokes the other
If you want to use this sample without Aspose.Words (for example, you already have an XPS document and just want to print it from an ASP.NET or Windows Service application), then you can just delete this method. Below code sample convert an Aspose.Words document into an XPS stream and print.
XpsPrintHelper.Print overload accepts a Stream object. The stream must contain a document in the XPS format. This method starts an XPS print job, sends the document to the XpsPrint API and then waits for the result if needed. Below code sample prints an XPS document using the XpsPrint API.
The code for the
CheckJobStatus methods as well as definitions of the
IXpsPrintJobStream interfaces is quite low-level and uses Platform Invoke and COM Interop. This code is not included in the article for brevity, but it is available in the sample download. The XpsPrint API also provides additional functionality, such as monitoring the job progress, but our
XpsPrintHelper is a very simple wrapper and does not expose this functionality. You could add this yourself if you want to.
Print a Document on a Server End Result
Printing to a Specific Tray
In Microsoft Word printer trays are defined per section and are printer specific. Therefore you can programtically change these values by using the FirstPageTray and OtherPagesTray properties of the PageSetup class for each section.
How to Print a Document with Settings and Print Preview Dialog
When working with documents you will often require printing them to a selected printer. A helpful addition is the ability to visually preview how the document will look on paper through a print preview and to choose options which define the behavior of how the document is printed.
The Aspose.Words component has no built in dialogs or forms, but implements its own version of the .NET PrintDocument class which can be passed to a PrintPreviewDialog form to print and preview a document.
Aspose.Words defines a special class called AsposeWordsPrintDocument which is a sub class of the .NET PrintDocument class. An instance of this object is passed to the PrintPreviewDialog class which defines the output to transmit to a printer.
This sample describes how to use these classes to print a document from Aspose.Words with print preview and settings dialog.
To accomplish this task the following steps are used:
- Load your document into the Document class.
- Create the Print dialog, initialize it with the default parameters and display it on the screen.
- Check the dialog result and proceed if the user accepted the printing dialog.
- Create an instance of the AsposeWordsPrintDocument class and pass the user defined settings from the print dialog to the class .
- Create the Print Preview dialog and specify the AsposeWordsPrintDocument as the target document and then show the dialog.
The first step is the creation of the Print dialog and initializing the default settings. Below example creates the print dialog.
Everything is quite simple and straightforward. AllowSomePages property enables the Pages option under print range and the other options define the default values for the custom page range. These define the default values for the page range. By default all pages in the range are selected for printing.
After initialization the dialog is displayed and the result is processed. It makes sense to proceed with the document preview only if the instance of the PrintDialog returns DialogResult.OK, as other results means that the user decided to cancel the print operation. Below code sample check if the user accepted the print settings and proceed to preview the document.
The next step involves creating an instance of the AsposeWordsPrintDocument. This is the Aspose.Words' implementation of the .NET PrintDocument class which allows you to pass an Aspose.Words Document to a printer function accepting a PrintDocument class. The printer settings from the Print dialog should be passed there as well. Below code sample creates a special Aspose.Words implementation of the .NET PrintDocument class.
Finally, an instance of the PrintPreviewDialog is created. For this example we have implemented a derived version of the PrintPreviewDialog class called ActivePrintPreviewDialog. This custom class is used to move preview dialog on top of all other windows when it is displayed. Below code sample brings the Print Preview dialog to the front.
If you don’t care about display order of the windows you could instead use the PrintPreviewDialog directly. Regardless of PrintPreviewDialog variation used, you should pass the Aspose.Words' print document to the Print Preview dialog and display it. Below example creates an overridden version of the .NET Print Preview dialog to preview the document.
To make the settings in the Print Preview dialog appear a bit better when displayed on screen, the following parameters are specified:
- ShowInTaskbar = true – displays the Print Preview dialog task in the taskbar.
- MinimizeBox = true – enables the Minimize button and related functionality.
- PrintPreviewControl.Zoom = 1 – displays the document within the Print Preview dialog at 100% zoom level.
- Document.DocumentName = docName – displays the document name in the print-related dialogs, for example, in the print status dialog box or printer queue.
- WindowState = FormWindowState.Maximized – displays the Print Preview dialog window in the maximized state.
How to Print Multiple Pages on One Sheet
When printing documents the more flexibility you have, the better. Using .NET and Aspose.Words you can easily fine-tune the printing operation to implement your own custom logic that defines the look of the document on the printed page.
This sample demonstrates how to print several document pages on a single sheet of paper. To be as realistic as possible, the sample provides capabilities as close as possible to Word itself by enabling the choice of one, two, four, six, eight, nine and sixteen pages per sheet to be printed. This can be easily modified to support other variants as well.
The approach used here is quite generic – this code implements its own version of the .NET PrintDocument class. This means the existing .NET printing infrastructure can be used such as the print and print preview dialogs which allows for a visual view of the document before printing.
Print Multiple Pages Solution
The basic steps to follow to print multiple pages on a single sheet are as follows:
- Implement the custom class MultipagePrintDocument which is a sub class of the PrintDocument class .
- Pass to the constructor an Aspose.Words Document object and an integer defining the number of the pages you want to print on one sheet.
- Override the OnBeginPrint method to read the necessary printer settings.
- Override the OnPrintPage method to define the look of each page before sending to the printer. Here you should:
- Calculate the size of the thumbnail placeholder for each page to fit on the sheet. This depends on the number of pages to appear on the sheet.
- Resize and render all the pages onto the sheet in the appropriate places using the Graphics object of the printer.
- Set the HasMorePages flag if there are more pages to print. (This will prompt the printer to continue the printing process).
- Create an instance of the newly defined MultipagePrintDocument class and initialize it with the appropriate Aspose.Words Document object.
- Call the print dialog on the MultipagePrintDocument document object.
Print Multiple Pages Code
The main idea of the implemented approach is both simple and generic. In .NET, if you want to print a specific object or establish specific control over the printing process you need to implement your own version of the PrintDocument class. This is the common approach for this sort of task, and is the same technique that the AsposeWordsPrintDocument class uses.
In this sample the MultipagePrintDocument class is derived from the PrintDocument class and provides the ability of printing several pages on one sheet of paper. Below code sample is the custom PrintDocument class.
For the purposes of illustration, it is enough to focus on a few methods only. The first of them is a constructor where you need to read and store the necessary parameters. The MultipagePrintDocument class accepts three parameters: the Aspose.Words document, the number of pages per sheet and the flag that indicates if the borders around the pages should be printed: Below code sample is the constructor of the custom PrintDocument class.
The constructor initializes the class fields and throws an exception if no document is passed. These are the fields defined in the MultipagePrintDocument class: Below code sample is the data and state fields of the custom PrintDocument class.
The first three are straight forward, while the last three are used to store current page, the last page to print and the size of the paper selected for printing. You will find how these fields are used later in the article.
The next step is to override the OnBeginPrint method. This is the best place to read necessary data from the PrinterSettings property from the PrintDocument base class. The OnBeginPrint method is called immediately after the Print method is called and before the first page of the document prints. This guarantees that there will be no other modifications to the printer settings. Below code is the overridden method OnBeginPrint, which is called before the first page of the document prints.
Here the print range and the paper size are collected for further calculations. The PrinterSettings.DefaultPageSettings.PaperSize property returns the size in portrait orientation regardless of the current user selection. For the sake of convenience, this sample takes into account the actual paper orientation and adjusts the paper size accordingly.
The last method to be overridden is the OnPrintPage . Here you can control the printing process itself. The rendering features of Aspose.Words are applied to the Graphics object from the printer’s context, which allows you to easily specify what should be printed and how exactly it should appear on paper.
One thing to note about this method is its main function. This method is the event handler which is called each time before a page prints. This means if you are going to print several pages, you need to be aware of what has already been printed and which page is the last to be printed. In the current sample the mCurrentPage field is used to capture the current printing position, while the mPageTo field specifies the last page to be printed.
The OnPrintPage method also allows you to decide if you want the printing to continue or stop. It does this by using a simple technique - if there are more pages to print the HasMorePages property needs to be set to true, where as if there are no further pages to print the property set to false. If a value of false is passed in the HasMorePages property the printing process will be stopped.
Here is the implementation of the OnPrintPage method from this sample. It renders the specified number of pages into the Graphics object with the appropriate scaling and positioning. Below code sample generates the printed page from the specified number of the document pages.
There are several points to comment on here. The first is the measuring units of the printer’s Graphics object needs to be changed to the GraphicsUnit.Point units. This is because by default the Graphics object uses the GraphicsUnit.Display units and these units have an interesting peculiarity in which the actual resolution of the display units depends on the device context. So the resolution for the display screen could be either 1/96 or 1/120 inches, while for the printer it is 1/100 inches. This context dependency can sometimes influence the calculations that rely on invariable measures. To avoid this problem, it makes better sense to use absolute units. This sample uses the point unit. One point is 1/72 of an inch. Transition to using point units removes the context dependency and makes the code more generic. The following method is implemented to convert all units to point’s measurement: Below code sample converts hundredths of inches to points.
Another important task is to calculate the size of the thumbnail placeholders on the paper sheet. Since the number of thumbnails that should be printed on one sheet of paper is known, it is necessary to find the size of the area reserved for a single thumbnail on the paper. Once this is calculated the next task is to define the positions of all the thumbnail placeholders. The following method defines how many columns and rows are required on the sheet of paper to accommodate the number of thumbnails. Below code sample defines the number of columns and rows depending on the pagesPerSheet number and the page orientation.
The GetThumbCount method returns the predefined number of columns and rows which corresponds to the layout of the document pages onto the sheet. When appropriate, it divides the long side of the paper to more parts than the short one in order to maintain the right proportions of the thumbnail placeholders. Having the correctly oriented paper size, and the appropriate number of columns and rows, makes it easy to calculate the size of the thumbnail placeholders and translate them into points measurement.
The rest of the code in the main OnPrintPage method is quite simple. The index is defined as the last page to be printed on this very sheet of paper. It will either be the index of the current page, appropriately increased by the specified number of pages per sheet, or the stored upper index from the print range, whichever one is smaller.
In the loop through the pages to be printed the position is calculated for each thumbnail, by moving across and down the sheet. Each thumbnail is rendered using the Document.RenderToSize method of the Document object. The Document.RenderToSize method is ideal for tasks like this as it allows the document to be rendered at a set size onto a Graphics object in any position you want. The Document.RenderToScale method provides similar functionality except it allows you to specify the scale of the resulting page rather than the size. In the current sample, using the Document.RenderToSize method makes more sense, because the size on the thumbnail placeholder is calculated already.
The loop then draws the borders for the page thumbnail and the thumbnail placeholder if this setting is enabled. At the end of the method the current page number is calculated and used to check if any further document pages need to be printed. This is what the HasMorePages property defines. The rest of the code in the sample focuses on previewing and printing using the implemented MultipagePrintDocument class. Below example is the usage of the MultipagePrintDocument for Previewing and Printing. You can download the template file of this example from here.
Briefly, the basic outline of the technique consists of:
- An instance of the PrintDialog class is created to allow a printer to be selected and print-related settings defined. This includes the print range, paper size and page orientation.
- If the user chooses to proceed with printing, a MultipagePrintDocument instance is created with the gathered settings. In this particular sample the printer settings are set to print four pages per sheet and to draw page borders.
- A slightly adapted version of the PrintPreviewDialog is then called and the MultipagePrintDocument document is passed to preview the resulting pages in the dialog.
(The adapted class has the sole purpose to bring the Print preview dialog on top of other windows which the PrintPreviewDialog class does not by default.)
Print Multiple Pages End Result
Running the sample will display the following results: Firstly the Print dialog:
And the Print preview dialog. The four page document is rendered to print combined into one page. And borders are rendered between pages:
How to Hide Print Progress Dialog during Printing a Document
Print Progress Dialog does not appear when printing document using Document.Print method. However, this UI appears during printing document using AsposeWordsPrintDocument.Print method. In this case, to prevent the print UI from appearing, you should specify valid printer settings and a standard print controller to this method. This example illustrates how to prevent the print UI from appearing. The Print Progress Dialog:
You can download the template file of this example from here.
How to Reduce Time of First Call of Print
Aspose.Words reads and caches some fields of PrinterSettings to reduce printing time. You can achieve this by calling AsposeWordsPrintDocument.CachePrinterSettings method. This method is called before the printing starts if it wasn't executed previously. Please note that total time of printing with and without call to this method is almost the same. The goal of this method is to reduce time of first call of Document.Print method. Following code example shows how to use this method.