Introduction

Some of the API calls used in this example are only supported on Windows NT, 2000, XP and .NET server. Therefore this technique does not apply to Windows 95, Windows 98 or Windows ME.

Although VB.NET printer handling has improved immeasurably over that offered by Visual Basic 6 and the new extensions to System.Drawing.Printing have also helped, there is still a need to turn to the Windows API in order to monitor a print queue.

Get a Handle to the Printer You Want to Monitor

All of the API calls that access the printer or spooler need a printer handle. This is obtained by passing the unique printer device name to the OpenPrinter API call and must be released by the ClosePrinter API call when it is no longer needed.

Ask for the Notifications You are Interested in

To minimise the impact of a printer watch on system performance, we can specify precisely which printer events we are interested in. This is done by passing a parameter to FindFirstPrinterChangeNotification using one or more of the following values:

Specify the Information You want Returned for the Event

When an event occurs -- for example, if a job is added to the print queue -- you will probably want to get information about the job that caused that event. Again, in order to minimise the impact on the system, you specify exactly which fields you want information from. For a print job event, the possible fields are:

To inform the print spooler that you want information on these fields, you create a PRINTER_NOTIFY_OPTIONS structure that is passed to FindFirstPrinterChangeNotification and which holds a pointer to an array of PRINTER_NOTIFY_OPTIONS_TYPE, one for each of the above fields that you require. These structures are documented on MSDN.

In VB.NET, it is easy to translate these structures into classes that can be passed to the API, by being marshaled as if they were structures:

Waiting for a Notification

In the Visual Basic 6 implementation of this, a great deal of complexity was added by the fact that it is a single-threaded system. Thus, when the program was waiting for the printer notification, it was effectively locked up. In Visual Basic .NET, this is no longer necessary because it supports asynchronous events and threading.

The FindFirstPrinterChangeNotification API call returns a Windows synchronization wait handle. This can be used by the VB.NET Common Language Runtime to trigger a particular subroutine whenever that synchronisation object is signalled. This is done with the Threading.RegisteredWaitHandle object:

This returns a 32-bit number in pdwChange that indicates what event has occurred. For example, this will contain PRINTER_CHANGE_ADD_JOB when a job is added to the print queue. Additionally, it returns a pointer to data allocated by the spooler in lppPrinterNotifyInfo, which contains a PRINTER_NOTIFY_INFO structure, followed by an array of PRINTER_NOTIFY_INFO_DATA structures. Again, in VB.NET these can be represented by classes:

Comments and Discussions

Nevertheless, I have a little problem.
When a user print a document, I'd like to pause the printing and ask for the user "you will print xx pages. OK ?"
I paused the printer in the jobAdded event, but the number of pages is always set to 0. I saw in that forum that is was normal because this counter is set after the print... so too late.
I tried to use JobWritten event : I have the number of pages but it's too late to pause the printing.