Enhance the Printers Collection
Filling some of the most-requested holes is only a few API calls away.
by Karl E. Peterson
Technology Toolbox: VB6, VB5
Classic VB offers an interesting and
sometimes useful Printers collection. Still, those Printer objects never
seem to provide the key functionalities you desire—or so I've gathered
from working on this magazine's Q&A column for years. In response, I
put together an assortment of the dozen-odd Printer object properties
and methods you've most requested—including queue positioning, job
cancel/pause/resume, printer purge/pause/resume, and default
get/set—then built a little demo around them (download
a complete list here).
The demo shows you how you can duplicate in your code most of the capabilities presented by the Windows Explorer Printers folder and Printer windows that open when you double-click on any of the individual printers (see Figure 1). I'll show you some of the neater ideas in this column, then you can download the whole demo.
DeviceName probably represents the most valuable property offered by VB's Printer objects. This string, with a few API calls, lets you perform most any printer operation you can think of. My demo app creates a collection of CPrinterInfo objects by instantiating one object for each Printer in the Printers collection and passing it Printer's DeviceName as a signal to initialize it. Each CPrinterInfo object exposes a PRINTER_INFO_2 structure, as returned by GetPrinter, along with a few other custom properties and methods.
You must first obtain a printer handle (hPrn) by passing a DeviceName to the OpenPrinter API. You can call GetPrinter with an hPrn to retrieve scads of information about that printer. Usually you make the first call to GetPrinter without passing a buffer to be filled. GetPrinter returns the buffer size needed, then a second call to GetPrinter fills the buffer. Then you can start interpreting the results (see Listing 1). Note that you might have a simpler case where you need only a single item, such as the number of jobs currently spooling to a given printer (see Listing 2).
Pause and Purge Print Queues
Public Property Get CanAdminister() _ As Boolean Dim hPrn As Long Dim pd As PRINTER_DEFAULTS ' Try to open printer, requesting ' administrative privileges. pd.pDesiredAccess = _ PRINTER_ACCESS_ADMINISTER Call OpenPrinter(m_DevName, _ hPrn, pd) CanAdminister = (hPrn <> 0) Call ClosePrinter(hPrn) End Property
Occasionally, an app might need to discover which printer is currently the system default, or even to set the default. Classic VB's Printers collection doesn't offer this capability despite hints that it does. When your app starts, the Printer object points to the current system default printer and continues to do so even if the user changes that default. But once you select a default printer for your app, you can't reset this pointer to the system default printer.
A Microsoft Knowledge Base article (Q246772) details three ways to set and three more to retrieve the system default printer: one each for Windows 9x, Windows NT4 or before, and Windows 2000 and beyond (see Resources). Under Windows 9x, calling EnumPrinters with PRINTER_ENUM_DEFAULT at level 5 provides a quick answer by returning a pointer to the string sought. Curiously, the PRINTER_ENUM_DEFAULT flag is available only in Win9x environments. In NT4, you read the mapped WINini values ("device" key from the "[Windows]" section) from the Registry using GetProfileString. And in Windows 2000, the GetDefaultPrinter API provides a dedicated answer (see Listing B).
Setting the default system printer can be similarly convoluted. Two OSs provide a clear method, however. In Win9x, you get hPrn with OpenPrinter, using GetPrinter to retrieve a PRINTER_INFO_2 structure. Pick the Attribute element from the structure and use PRINTER_ATTRIBUTE_DEFAULT to toggle the default bit on. Once you set the Attribute value, store it in the structure and pass it back to the system with a call to SetPrinter (see Listing C). By comparison, Windows 2000/XP offer a nice boring call to the SetDefaultPrinter API.
In NT4, however, you set the default printer by passing WriteProfileString (key "device" in section "[Windows]") a value of the form "printer name,driver name,port." Unfortunately, no API directly supports finding the correct values for those last two elements. However, NT maps what was once the "[PrinterPorts]" section of WIN.ini to a key in the Registry, from which you can extract the info needed for any installed printer (see Listing C). And if you aren't running under Win2K or better, you also must alert every other running app of this change, using SendMessage to send WM_SETTINGCHANGE to the special handle HWND_BROADCAST.
Under VB5, you'll also need to call GetProfileString to ensure you have a complete Printers collection. Request all the keys in the "[PrinterPorts]" section and parse the null-delimited list that returns.
The demo code is bulky—the class modules alone take up nearly 100K. But you'll find more cool stuff there, including how to modify individual job properties (such as their position in the queue) and how to interpret the status bytes of a job or printer to be human-readable.
About the Author