How to: Programmatically Print XPS Files

You can use one overload of the AddJob method to print XML Paper Specification (XPS) files without opening a PrintDialog or, in principle, any user interface (UI) at all.

You can also print XML Paper Specification (XPS) files using the many Write and WriteAsync methods of the XpsDocumentWriter. For more about this, Printing an XPS Document.

Another way of printing XML Paper Specification (XPS) is to use thePrintDocument or PrintVisual methods of the PrintDialog control. See How to: Invoke a Print Dialog.

Example

The main steps to using the three-parameter AddJob(String, String, Boolean) method are as follows. The example below gives details.

  1. Determine if the printer is an XPSDrv printer. (See Printing Overview for more about XPSDrv.)

  2. If the printer is not an XPSDrv printer, set the thread's apartment to single thread.

  3. Instantiate a print server and print queue object.

  4. Call the method, specifying a job name, the file to be printed, and a Boolean flag indicating whether or not the printer is an XPSDrv printer.

The example below shows how to batch print all XPS files in a directory. Although the application prompts the user to specify the directory, the three-parameter AddJob(String, String, Boolean) method does not require a user interface (UI). It can be used in any code path where you have an XPS file name and path that you can pass to it.

The three-parameter AddJob(String, String, Boolean) overload of AddJob must run in a single thread apartment whenever the Boolean parameter is false, which it must be when a non-XPSDrv printer is being used. However, the default apartment state for Microsoft .NET is multiple thread. This default must be reversed since the example assumes a non-XPSDrv printer.

There are two ways to change the default. One way is to simply add the STAThreadAttribute (that is, "[System.STAThreadAttribute()]") just above the first line of the application's Main method (usually "static void Main(string[] args)"). However, many applications require that the Main method have a multi-threaded apartment state, so there is a second method: put the call to AddJob(String, String, Boolean) in a separate thread whose apartment state is set to STA with SetApartmentState. The example below uses this second technique.

Accordingly, the example begins by instantiating a Thread object and passing it a PrintXPS method as the ThreadStart parameter. (The PrintXPS method is defined later in the example.) Next the thread is set to a single thread apartment. The only remaining code of the Main method starts the new thread.

The meat of the example is in the static BatchXPSPrinter.PrintXPS method. After creating a print server and queue, the method prompts the user for a directory containing XPS files. After validating the existence of the directory and the presence of *.xps files in it, the method adds each such file to the print queue. The example assumes that the printer is non-XPSDrv, so we are passing false to the last parameter of AddJob(String, String, Boolean) method. For this reason, the method will validate the XPS markup in the file before it attempts to convert it to the printer's page description language. If the validation fails, an exception is thrown. The example code will catch the exception, notify the user about it, and then go on to process the next XPS file.

    Friend Class Program
        <System.MTAThreadAttribute()>
        Shared Sub Main(ByVal args() As String) ' Added for clarity, but this line is redundant because MTA is the default.
            ' Create the secondary thread and pass the printing method for 
            ' the constructor's ThreadStart delegate parameter. The BatchXPSPrinter
            ' class is defined below.
            Dim printingThread As New Thread(AddressOf BatchXPSPrinter.PrintXPS)

            ' Set the thread that will use PrintQueue.AddJob to single threading.
            printingThread.SetApartmentState(ApartmentState.STA)

            ' Start the printing thread. The method passed to the Thread 
            ' constructor will execute.
            printingThread.Start()

        End Sub 'end Main

    End Class 'end Program class

    Public Class BatchXPSPrinter
        Public Shared Sub PrintXPS()
            ' Create print server and print queue.
            Dim localPrintServer As New LocalPrintServer()
            Dim defaultPrintQueue As PrintQueue = LocalPrintServer.GetDefaultPrintQueue()

            ' Prompt user to identify the directory, and then create the directory object.
            Console.Write("Enter the directory containing the XPS files: ")
            Dim directoryPath As String = Console.ReadLine()
            Dim dir As New DirectoryInfo(directoryPath)

            ' If the user mistyped, end the thread and return to the Main thread.
            If Not dir.Exists Then
                Console.WriteLine("There is no such directory.")
            Else
                ' If there are no XPS files in the directory, end the thread 
                ' and return to the Main thread.
                If dir.GetFiles("*.xps").Length = 0 Then
                    Console.WriteLine("There are no XPS files in the directory.")
                Else
                    Console.WriteLine(vbLf & "Jobs will now be added to the print queue.")
                    Console.WriteLine("If the queue is not paused and the printer is working, jobs will begin printing.")

                    ' Batch process all XPS files in the directory.
                    For Each f As FileInfo In dir.GetFiles("*.xps")
                        Dim nextFile As String = directoryPath & "\" & f.Name
                        Console.WriteLine("Adding {0} to queue.", nextFile)

                        Try
                            ' Print the Xps file while providing XPS validation and progress notifications.
                            Dim xpsPrintJob As PrintSystemJobInfo = defaultPrintQueue.AddJob(f.Name, nextFile, False)
                        Catch e As PrintJobException
                            Console.WriteLine(vbLf & vbTab & "{0} could not be added to the print queue.", f.Name)
                            If e.InnerException.Message = "File contains corrupted data." Then
                                Console.WriteLine(vbTab & "It is not a valid XPS file. Use the isXPS Conformance Tool to debug it.")
                            End If
                            Console.WriteLine(vbTab & "Continuing with next XPS file." & vbLf)
                        End Try

                    Next f ' end for each XPS file

                End If 'end if there are no XPS files in the directory

            End If 'end if the directory does not exist

            Console.WriteLine("Press Enter to end program.")
            Console.ReadLine()

        End Sub ' end PrintXPS method

    End Class ' end BatchXPSPrinter class
class Program
{
    [System.MTAThreadAttribute()] // Added for clarity, but this line is redundant because MTA is the default.
    static void Main(string[] args)
    {
        // Create the secondary thread and pass the printing method for 
        // the constructor's ThreadStart delegate parameter. The BatchXPSPrinter
        // class is defined below.
        Thread printingThread = new Thread(BatchXPSPrinter.PrintXPS);

        // Set the thread that will use PrintQueue.AddJob to single threading.
        printingThread.SetApartmentState(ApartmentState.STA);

        // Start the printing thread. The method passed to the Thread 
        // constructor will execute.
        printingThread.Start();

    }//end Main

}//end Program class

public class BatchXPSPrinter
{
    public static void PrintXPS()
    {
        // Create print server and print queue.
        LocalPrintServer localPrintServer = new LocalPrintServer();
        PrintQueue defaultPrintQueue = LocalPrintServer.GetDefaultPrintQueue();

        // Prompt user to identify the directory, and then create the directory object.
        Console.Write("Enter the directory containing the XPS files: ");
        String directoryPath = Console.ReadLine();
        DirectoryInfo dir = new DirectoryInfo(directoryPath);

        // If the user mistyped, end the thread and return to the Main thread.
        if (!dir.Exists)
        {
            Console.WriteLine("There is no such directory.");
        }
        else
        {
            // If there are no XPS files in the directory, end the thread 
            // and return to the Main thread.
            if (dir.GetFiles("*.xps").Length == 0)
            {
                Console.WriteLine("There are no XPS files in the directory.");
            }
            else
            {
                Console.WriteLine("\nJobs will now be added to the print queue.");
                Console.WriteLine("If the queue is not paused and the printer is working, jobs will begin printing.");

                // Batch process all XPS files in the directory.
                foreach (FileInfo f in dir.GetFiles("*.xps"))
                {
                    String nextFile = directoryPath + "\\" + f.Name;
                    Console.WriteLine("Adding {0} to queue.", nextFile);

                    try
                    {
                        // Print the Xps file while providing XPS validation and progress notifications.
                        PrintSystemJobInfo xpsPrintJob = defaultPrintQueue.AddJob(f.Name, nextFile, false);
                    }
                    catch (PrintJobException e)
                    {
                        Console.WriteLine("\n\t{0} could not be added to the print queue.", f.Name);
                        if (e.InnerException.Message == "File contains corrupted data.")
                        {
                            Console.WriteLine("\tIt is not a valid XPS file. Use the isXPS Conformance Tool to debug it.");
                        }
                        Console.WriteLine("\tContinuing with next XPS file.\n");
                    }

                }// end for each XPS file

            }//end if there are no XPS files in the directory

        }//end if the directory does not exist

        Console.WriteLine("Press Enter to end program.");
        Console.ReadLine();

    }// end PrintXPS method

}// end BatchXPSPrinter class

If you are using an XPSDrv printer, then you can set the final parameter to true. In that case, since XPS is the printer's page description language, the method will send the file to the printer without validating it or converting it to another page description language. If you are uncertain at design time whether the application will be using an XPSDrv printer, you can modify the application to have it read the IsXpsDevice property and branch according to what it finds.

Since there will initially be few XPSDrv printers available immediately after the release of Windows Vista and Microsoft .NET Framework, you may need to disguise a non-XPSDrv printer as an XPSDrv printer. To do so, add Pipelineconfig.xml to the list of files in the following registry key of the computer running your application:

HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Print\Environments\Windows NT x86\Drivers\Version-3\<PseudoXPSPrinter>\DependentFiles

where <PseudoXPSPrinter> is any print queue. The machine must then be rebooted.

This disguise will enable you to pass true as the final parameter of AddJob(String, String, Boolean) without causing an exception, but since <PseudoXPSPrinter> is not really an XPSDrv printer, only garbage will print.

Note   For simplicity, the example above uses the presence of an *.xps extension as its test that a file is XPS. However, XPS files do not have to have this extension. The isXPS.exe (isXPS Conformance Tool) is one way of testing a file for XPS validity.

See Also

Reference

PrintQueue

AddJob

ApartmentState

STAThreadAttribute

Concepts

Managed and Unmanaged Threading

isXPS.exe (isXPS Conformance Tool)

Documents in WPF

Printing Overview

Other Resources

XPS

Printing an XPS Document