|
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
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
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
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.
|