Enhance the Printers Collection
Filling some of the most-requested holes is only a few API calls away.
by Karl E. Peterson

February 2002 Issue  Download the original Classic VB code from this column.

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).
Figure 1. Mine the Win32 API for Printer Object Enhancements.

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
Have you noticed that you can't pause or purge a print queue if you don't have administrative privileges? Well, neither can apps whose users lack the required status. You can address this with the third parameter to OpenPrinter, which supports a request for administrative access by setting the PRINTER_DEFAULTS pDesiredAccess element to PRINTER_ACCESS_ADMINISTER. Of course, your process must have sufficient rights, or the OpenPrinter call will fail (see Listing A). You can toggle the related UI elements of your app with a simple test:

Public Property Get CanAdminister() _
   As Boolean
   Dim hPrn As Long
   ' Try to open printer, requesting
   ' administrative privileges.
   pd.pDesiredAccess = _
   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.

Updated Code Samples
•  PrnInfo

Additional Resources
•  Desktop Developer, "Let Me Count the Ways," by Karl E. Peterson [VSM, November 2001]
•  Ask the VB Pro, "Look Into Raw Memory Contents," by Karl E. Peterson [VBPJ, February 2001]
•  Ask the VB Pro, "Spool, Shell, and Hook," by Karl E. Peterson and Phil Weber [VBPJ, February 1998]
•  "HOWTO: Retrieve and Set the Default Printer in Windows", MS Knowledge Base
•  "FIX: Printers Collection May Not Contain All Printers in the Printers Folder", MS Knowledge Base

About the Author
Karl E. Peterson is a GIS analyst with a regional transportation-planning agency and serves as a member of the Visual Studio Magazine Technical Review and Editorial Advisory boards. He's also a Microsoft MVP and a section leader on several VSM forums. Find more of Karl's VB samples at www.mvps.org/vb.