Older Version
Newer Version
JanetTerra
Jul 26, 2010
=Getting and Setting the Default Printer=
//[[user:JanetTerra]]//
[[toc]]
----
==Liberty BASIC's Native Printer Dialog==
The //printerdialog// function brings up the standard Windows printer dialog box. From the Liberty BASIC helpfile
>> PRINTERDIALOG
>> Description
>> This command opens the standard Windows Common Printer Dialog. If the user chooses a printer and accepts, the next print job will go to this printer. Accepting a printer also sets the global variables PrinterName$, PrintCollate and PrintCopies to reflect what the user chose for the Printer Name, Collate and Copies. If no printer is accepted, then PrinterName$ is set to an empty string.
Unfortunately, Liberty BASIC doesn't interact well with Windows printer dialog. Despite what information PrinterName$ holds, all documents, whether text (//ldump//) or graphics (//vga//, //svga//, //xga//) will be sent to the default printer. A work-around is to set the desired printer as default before printing the document. This work-around is based upon contributions of Stefan Pendl. All of the code used in this article has been compiled from postings made by Stefan Pendl, both at [[http://libertybasic.conforums.com|Liberty BASIC Conforums]] and at the [[http://groups.yahoo.com/group/libertybasic/|Official Liberty BASIC Support Group]]. The compiled snippets have been modified slightly for consistency with descriptive variable names, but otherwise remain intact.
The first step is to identify (get) the current default printer.
==Get Default Printer==
The information derived from the winspool.drv DLL will be placed in a struct. The struct must first be defined, here it's //pcchBuffer//, and the length of the struct element //value// set to _MAX_PATH. The call to the DLL is then made and the name of the default printer is placed into //pcchBuffer.value.struct//. Note that //byRef// is used so that the changes to //currentDefaultPrinter$// are made both locally and globally. The function itself returns a number, 0 for a failure, non-zero for success.
[[code format="vbnet"]]
GetDefaultPrinter = GetDefaultPrinter(currentDefaultPrinter$)
if GetDefaultPrinter = 0 then
print "Call failed"
else
print "DefaultPrinter = ";currentDefaultPrinter$
end if
end
function GetDefaultPrinter(byref currentDefaultPrinter$)
' Returns zero if call fails
struct pcchBuffer, value as ulong
currentDefaultPrinter$ = space$(_MAX_PATH)
pcchBuffer.value.struct = _MAX_PATH
open "winspool.drv" for dll as #winspool
calldll #winspool, "GetDefaultPrinterA", _
currentDefaultPrinter$ as ptr, _
pcchBuffer as struct, _
GetDefaultPrinter as long
close #winspool
end function
[[code]]
Stefan's code has always been accompanied with an error catching routine. Should the call not work, Liberty BASIC can identify the cause of the failure.
[[code format="vbnet"]]
GetDefaultPrinter = GetDefaultPrinter(currentDefaultPrinter$)
if GetDefaultPrinter = 0 then
print "Call failed"
else
print "DefaultPrinter = ";currentDefaultPrinter$
end if
end
function GetDefaultPrinter(byref currentDefaultPrinter$)
' Returns zero if call fails
struct pcchBuffer, value as ulong
currentDefaultPrinter$ = space$(_MAX_PATH)
pcchBuffer.value.struct = _MAX_PATH
open "winspool.drv" for dll as #winspool
calldll #winspool, "GetDefaultPrinterA", _
currentDefaultPrinter$ as ptr, _
pcchBuffer as struct, _
GetDefaultPrinter as long
if GetDefaultPrinter = 0 then
call DisplayError
else
currentDefaultPrinter$ = left$(currentDefaultPrinter$, pcchBuffer.value.struct - 1)
end if
close #winspool
end function
sub DisplayError
ErrorCode = GetLastError()
dwFlags = _FORMAT_MESSAGE_FROM_SYSTEM
nSize = 1024
lpBuffer$ = space$(nSize); chr$(0)
dwMessageID = ErrorCode
calldll #kernel32, "FormatMessageA", _
dwFlags as ulong, _
lpSource as ulong, _
dwMessageID as ulong, _
dwLanguageID as ulong, _
lpBuffer$ as ptr, _
nSize as ulong, _
Arguments as ulong, _
result as ulong
print "Error "; ErrorCode; ": "; left$(lpBuffer$, result)
end sub
function GetLastError()
calldll #kernel32, "GetLastError", _
GetLastError as ulong
end function
[[code]]
Storing the current default printer in a variable is important to allow that printer to be reassigned as default once the document has been printed.
==List All Printers==
The next step is to get a list of all available printers. This requires a little more work and three more structs. Stefan's function loops around to obtain all the printer names, until there are no names left. The printer names are concatenated, deliminated with a semicolon, in the string variable PrinterInfo$. The loop ends when error #122 (The data area passed to a system call is too small) is encounered. Once again, //byRef// is used so that PrinterInfo$ remains the same locally and globally.
Enumerating the printers requires allocating and searching blocks of memory, resulting in rather complex code. Also, listing printers available to the computer by a local (physical) connection requires different variables than listing printers available to the computer by a network or wireless connection. The EnumPrinters() function must be accessed twice, first for local printers then for network printers. The appropriate variables should be passed to the function each time.
>> Flag to List Local Printers
>> PRINTER.ENUM.LOCAL = hexdec("2")
>> Flag to List Network Printers
>> PRINTER.ENUM.CONNECTIONS = hexdec("4")
The first pass stores the retrieved information in //LocalPrinterInfo$// and the second pass stores the retrieved information in //NetworkPrinterInfo$//. These two stringes are then concatenated with a semicolon to hold all printers in //PrinterInfo$//. Finally, an array is constructed to hold the individual printer names.
[[code format="vbnet"]]
' Count and enumerate all printers
' Need to access function twice, first for local printers, second for network printers
PRINTER.ENUM.LOCAL = hexdec("2")
PRINTER.ENUM.CONNECTIONS = hexdec("4")
nLocalPrinters = EnumPrinters(PrinterInfo$, PRINTER.ENUM.LOCAL)
print "nLocalPrinters = ";nLocalPrinters
LocalPrinterInfo$ = PrinterInfo$
print "LocalPrinterInfo$ = ";LocalPrinterInfo$
print
nNetworkPrinters = EnumPrinters(PrinterInfo$, PRINTER.ENUM.CONNECTIONS)
print "nNetworkPrinters = ";nNetworkPrinters
NetworkPrinterInfo$ = PrinterInfo$
print "NetworkPrinterInfo$ = ";NetworkPrinterInfo$
print
' Add both to total and combine the 2 printer strings
nPrinters = nLocalPrinters + nNetworkPrinters
Print "nPrinters = ";nPrinters
PrinterInfo$ = LocalPrinterInfo$;";";NetworkPrinterInfo$
print "PrinterInfo$ = ";PrinterInfo$
' Place all printers in an array
dim availablePrinters$(nPrinters)
for i = 1 to nPrinters
availablePrinters$(i) = word$(PrinterInfo$, i, ";")
next i
for i = 1 to nPrinters
print i, availablePrinters$(i)
next i
end
function EnumPrinters(byref PrinterInfo$, nFlags)
' Returns the number of printers found
' Fills the submitted variable with the printer names
' Separated by semicolons (;)
open "winspool.drv" for dll as #winspool
struct pcbNeeded, value as ulong
struct pcReturned, value as ulong
struct PrinterInfo4, _
pPrinterName$ as ptr, _
pServerName$ as ptr, _
Attributes as ulong
PrinterInfo4Len = len(PrinterInfo4.struct)
Level = 4
cbBuf = PrinterInfo4Len
uFlags = _LMEM_MOVEABLE or _LMEM_ZEROINIT
calldll #kernel32, "LocalAlloc", _
uFlags as uLong, _
cbBuf as uLong, _
hMem as uLong
calldll #kernel32, "LocalLock", _
hMem as uLong, _
pBuffer as uLong
[retryEnumPrinters]
calldll #winspool, "EnumPrintersA", _
nFlags as ulong, _
PrinterName as ulong, _
Level as ulong, _
pBuffer as ulong, _
cbBuf as ulong, _
pcbNeeded as struct, _
pcReturned as struct, _
result as boolean
if result = 0 then
if GetLastError() = 122 then
cbBuf = pcbNeeded.value.struct
hOldMem = hMem
calldll #kernel32, "LocalReAlloc", _
hOldMem as ulong, _
cbBuf as ulong, _
uFlags as ulong, _
hMem as ulong
calldll #kernel32, "LocalLock", _
hMem as uLong, _
pBuffer as ulong
goto [retryEnumPrinters]
else
call DisplayError
end if
else
EnumPrinters = pcReturned.value.struct
BufferPointer = pBuffer
for count = 0 to EnumPrinters - 1
calldll #kernel32, "RtlMoveMemory", _
PrinterInfo4 as struct, _
BufferPointer as ulong, _
PrinterInfo4Len as ulong, _
result as void
BufferPointer = BufferPointer + PrinterInfo4Len
pointer = PrinterInfo4.pPrinterName$.struct
PrinterInfo$ = winstring(pointer); ";"; PrinterInfo$
next count
PrinterInfo$ = left$(PrinterInfo$, len(PrinterInfo$)-1)
end if
calldll #kernel32, "LocalFree", _
hMem as uLong, _
result as uLong
close #winspool
end function
function GetLastError()
calldll #kernel32, "GetLastError", _
GetLastError as ulong
end function
[[code]]
The final step is to designate a different printer as default.
==Set Default Printer==
The code to set a default printer is the simplest of all, just passing a valid printer name to the winspool.drv dll. Like the GetDefaultPrinter() function, the SetDefaultPrinter() function returns a 0 for failure, a non-zero for success.
[[code format="vbnet"]]
selectedDefaultPrinter$ = "My Inkjet Printer"
' Set new default printer
SetDefaultPrinter = SetDefaultPrinter(selectedDefaultPrinter$)
end
function SetDefaultPrinter(selectedDefaultPrinter$)
' Returns zero if call fails
open "winspool.drv" for dll as #winspool
calldll #winspool, "SetDefaultPrinterA",_
selectedDefaultPrinter$ as ptr,_
SetDefaultPrinter as long
close #winspool
end function
[[code]]
This is the same code, but with Stefan's error trapping included.
[[code format="vbnet"]]
selectedDefaultPrinter$ = "My Inkjet Printer"
' Set new default printer
SetDefaultPrinter = SetDefaultPrinter(selectedDefaultPrinter$)
end
function SetDefaultPrinter(selectedDefaultPrinter$)
' Returns zero if call fails
open "winspool.drv" for dll as #winspool
calldll #winspool, "SetDefaultPrinterA",_
selectedDefaultPrinter$ as ptr,_
SetDefaultPrinter as long
close #winspool
if SetDefaultPrinter = 0 then call DisplayError
end function
sub DisplayError
ErrorCode = GetLastError()
dwFlags = _FORMAT_MESSAGE_FROM_SYSTEM
nSize = 1024
lpBuffer$ = space$(nSize); chr$(0)
dwMessageID = ErrorCode
calldll #kernel32, "FormatMessageA", _
dwFlags as ulong, _
lpSource as ulong, _
dwMessageID as ulong, _
dwLanguageID as ulong, _
lpBuffer$ as ptr, _
nSize as ulong, _
Arguments as ulong, _
result as ulong
print "Error "; ErrorCode; ": "; left$(lpBuffer$, result)
end sub
function GetLastError()
calldll #kernel32, "GetLastError", _
GetLastError as ulong
end function
[[code]]
==Getting, Listing, Setting the Default Printer (Mainwindow)==
Using all three components, the programmer now has full control of getting, listing, and setting the default printer.
[[code format="vbnet"]]
' Get the original default printer
GetDefaultPrinter = GetDefaultPrinter(origDefaultPrinter$)
print "origDefaultPrinter$ = ";origDefaultPrinter$
print
' Count and enumerate all printers
' Need to access function twice, first for local printers, second for network printers
PRINTER.ENUM.LOCAL = hexdec("2")
PRINTER.ENUM.CONNECTIONS = hexdec("4")
nLocalPrinters = EnumPrinters(PrinterInfo$, PRINTER.ENUM.LOCAL)
print "nLocalPrinters = ";nLocalPrinters
LocalPrinterInfo$ = PrinterInfo$
print "LocalPrinterInfo$ = ";LocalPrinterInfo$
print
nNetworkPrinters = EnumPrinters(PrinterInfo$, PRINTER.ENUM.CONNECTIONS)
print "nNetworkPrinters = ";nNetworkPrinters
NetworkPrinterInfo$ = PrinterInfo$
print "NetworkPrinterInfo$ = ";NetworkPrinterInfo$
print
' Add both to total and combine the 2 printer strings
nPrinters = nLocalPrinters + nNetworkPrinters
Print "nPrinters = ";nPrinters
PrinterInfo$ = LocalPrinterInfo$;";";NetworkPrinterInfo$
print "PrinterInfo$ = ";PrinterInfo$
' Place all printers in an array
dim availablePrinters$(nPrinters)
for i = 1 to nPrinters
availablePrinters$(i) = word$(PrinterInfo$, i, ";")
next i
for i = 1 to nPrinters
print i, availablePrinters$(i)
next i
print
' Select another default printer
Input "Printer to set as default > ";selectedDefaultPrinter
selectedDefaultPrinter$ = availablePrinters$(selectedDefaultPrinter)
print
' Set new default printer
SetDefaultPrinter = SetDefaultPrinter(selectedDefaultPrinter$)
end
function EnumPrinters(byref PrinterInfo$, nFlags)
' Returns the number of printers found
' Fills the submitted variable with the printer names
' Separated by semicolons (;)
open "winspool.drv" for dll as #winspool
struct pcbNeeded, value as ulong
struct pcReturned, value as ulong
struct PrinterInfo4, _
pPrinterName$ as ptr, _
pServerName$ as ptr, _
Attributes as ulong
PrinterInfo4Len = len(PrinterInfo4.struct)
Level = 4
cbBuf = PrinterInfo4Len
uFlags = _LMEM_MOVEABLE or _LMEM_ZEROINIT
calldll #kernel32, "LocalAlloc", _
uFlags as uLong, _
cbBuf as uLong, _
hMem as uLong
calldll #kernel32, "LocalLock", _
hMem as uLong, _
pBuffer as uLong
[retryEnumPrinters]
calldll #winspool, "EnumPrintersA", _
nFlags as ulong, _
PrinterName as ulong, _
Level as ulong, _
pBuffer as ulong, _
cbBuf as ulong, _
pcbNeeded as struct, _
pcReturned as struct, _
result as boolean
if result = 0 then
if GetLastError() = 122 then
cbBuf = pcbNeeded.value.struct
hOldMem = hMem
calldll #kernel32, "LocalReAlloc", _
hOldMem as ulong, _
cbBuf as ulong, _
uFlags as ulong, _
hMem as ulong
calldll #kernel32, "LocalLock", _
hMem as uLong, _
pBuffer as ulong
goto [retryEnumPrinters]
else
call DisplayError
end if
else
EnumPrinters = pcReturned.value.struct
BufferPointer = pBuffer
for count = 0 to EnumPrinters - 1
calldll #kernel32, "RtlMoveMemory", _
PrinterInfo4 as struct, _
BufferPointer as ulong, _
PrinterInfo4Len as ulong, _
result as void
BufferPointer = BufferPointer + PrinterInfo4Len
pointer = PrinterInfo4.pPrinterName$.struct
PrinterInfo$ = winstring(pointer); ";"; PrinterInfo$
next count
PrinterInfo$ = left$(PrinterInfo$, len(PrinterInfo$)-1)
end if
calldll #kernel32, "LocalFree", _
hMem as uLong, _
result as uLong
close #winspool
end function
function GetDefaultPrinter(byref currentDefaultPrinter$)
' Returns zero if call fails
struct pcchBuffer, value as ulong
currentDefaultPrinter$ = space$(_MAX_PATH)
pcchBuffer.value.struct = _MAX_PATH
open "winspool.drv" for dll as #winspool
calldll #winspool, "GetDefaultPrinterA", _
currentDefaultPrinter$ as ptr, _
pcchBuffer as struct, _
GetDefaultPrinter as long
close #winspool
if GetDefaultPrinter = 0 then
call DisplayError
else
currentDefaultPrinter$ = left$(currentDefaultPrinter$, pcchBuffer.value.struct - 1)
end if
end function
function SetDefaultPrinter(selectedDefaultPrinter$)
' Returns zero if call fails
print "selectedDefaultPrinter$ = ";selectedDefaultPrinter$
open "winspool.drv" for dll as #winspool
calldll #winspool, "SetDefaultPrinterA",_
selectedDefaultPrinter$ as ptr,_
SetDefaultPrinter as long
close #winspool
Print "SetDefaultPrinter = ";SetDefaultPrinter
if SetDefaultPrinter = 0 then call DisplayError
end function
sub DisplayError
ErrorCode = GetLastError()
dwFlags = _FORMAT_MESSAGE_FROM_SYSTEM
nSize = 1024
lpBuffer$ = space$(nSize); chr$(0)
dwMessageID = ErrorCode
calldll #kernel32, "FormatMessageA", _
dwFlags as ulong, _
lpSource as ulong, _
dwMessageID as ulong, _
dwLanguageID as ulong, _
lpBuffer$ as ptr, _
nSize as ulong, _
Arguments as ulong, _
result as ulong
print "Error "; ErrorCode; ": "; left$(lpBuffer$, result)
end sub
function GetLastError()
calldll #kernel32, "GetLastError", _
GetLastError as ulong
end function
[[code]]
----