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 (lprint, dump) 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 - StPendl. All of the code used in this article has been compiled from postings made by - StPendl, both at Liberty BASIC Conforums and at the Official Liberty BASIC Support Group. The compiled snippets have been modified slightly for consistency with descriptive variable names, but otherwise remain intact.
The code in this article is only valid for Windows 2k/XP/Vista/2k3/2k8. This code will not work for Windows 9x/ME. For code that will work with Windows 9x/ME, see Problem with printerdialog at the Liberty BASIC Community Forum.
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.
GetDefaultPrinter = GetDefaultPrinter(currentDefaultPrinter$)if GetDefaultPrinter =0then
print "Call failed"else
print "DefaultPrinter = ";currentDefaultPrinter$
endifendfunction GetDefaultPrinter(byref currentDefaultPrinter$)' Returns zero if call fails
struct pcchBuffer, value asulong
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 aslong
close #winspool
endfunction
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.
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.
' 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 arraydim availablePrinters$(nPrinters)for i =1to nPrinters
availablePrinters$(i)= word$(PrinterInfo$, i, ";")next i
for i =1to nPrinters
print i, availablePrinters$(i)next i
endfunction 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 asulong
struct pcReturned, value asulong
struct PrinterInfo4, _
pPrinterName$ as ptr, _
pServerName$ as ptr, _
Attributes asulong
PrinterInfo4Len = len(PrinterInfo4.struct)
Level =4
cbBuf = PrinterInfo4Len
uFlags = _LMEM_MOVEABLE or _LMEM_ZEROINIT
calldll #kernel32, "LocalAlloc", _
uFlags asuLong, _
cbBuf asuLong, _
hMem asuLong
calldll #kernel32, "LocalLock", _
hMem asuLong, _
pBuffer asuLong[retryEnumPrinters]
calldll #winspool, "EnumPrintersA", _
nFlags asulong, _
PrinterName asulong, _
Level asulong, _
pBuffer asulong, _
cbBuf asulong, _
pcbNeeded as struct, _
pcReturned as struct, _
result asbooleanif result =0thenif GetLastError()=122then
cbBuf = pcbNeeded.value.struct
hOldMem = hMem
calldll #kernel32, "LocalReAlloc", _
hOldMem asulong, _
cbBuf asulong, _
uFlags asulong, _
hMem asulong
calldll #kernel32, "LocalLock", _
hMem asuLong, _
pBuffer asulonggoto[retryEnumPrinters]elsecall DisplayError
endifelse
EnumPrinters = pcReturned.value.struct
BufferPointer = pBuffer
for count =0to EnumPrinters -1
calldll #kernel32, "RtlMoveMemory", _
PrinterInfo4 as struct, _
BufferPointer asulong, _
PrinterInfo4Len asulong, _
result as void
BufferPointer = BufferPointer + PrinterInfo4Len
pointer = PrinterInfo4.pPrinterName$.struct
PrinterInfo$ = winstring(pointer); ";"; PrinterInfo$
next count
PrinterInfo$ =left$(PrinterInfo$, len(PrinterInfo$)-1)endif
calldll #kernel32, "LocalFree", _
hMem asuLong, _
result asuLong
close #winspool
endfunctionfunction GetLastError()
calldll #kernel32, "GetLastError", _
GetLastError asulongendfunction
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.
selectedDefaultPrinter$ ="My Inkjet Printer"' Set new default printer
SetDefaultPrinter = SetDefaultPrinter(selectedDefaultPrinter$)endfunction SetDefaultPrinter(selectedDefaultPrinter$)' Returns zero if call fails
open "winspool.drv"for dll as #winspool
calldll #winspool, "SetDefaultPrinterA",_
selectedDefaultPrinter$ as ptr,_
SetDefaultPrinter aslong
close #winspool
endfunction
This is the same code, but with Stefan's error trapping included.
selectedDefaultPrinter$ ="My Inkjet Printer"' Set new default printer
SetDefaultPrinter = SetDefaultPrinter(selectedDefaultPrinter$)endfunction SetDefaultPrinter(selectedDefaultPrinter$)' Returns zero if call fails
open "winspool.drv"for dll as #winspool
calldll #winspool, "SetDefaultPrinterA",_
selectedDefaultPrinter$ as ptr,_
SetDefaultPrinter aslong
close #winspool
if SetDefaultPrinter =0thencall DisplayError
endfunctionsub DisplayError
ErrorCode = GetLastError()
dwFlags = _FORMAT_MESSAGE_FROM_SYSTEM
nSize =1024
lpBuffer$ =space$(nSize); chr$(0)
dwMessageID = ErrorCode
calldll #kernel32, "FormatMessageA", _
dwFlags asulong, _
lpSource asulong, _
dwMessageID asulong, _
dwLanguageID asulong, _
lpBuffer$ as ptr, _
nSize asulong, _
Arguments asulong, _
result asulong
print "Error "; ErrorCode; ": "; left$(lpBuffer$, result)endsubfunction GetLastError()
calldll #kernel32, "GetLastError", _
GetLastError asulongendfunction
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.
' 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 arraydim availablePrinters$(nPrinters)for i =1to nPrinters
availablePrinters$(i)= word$(PrinterInfo$, i, ";")next i
for i =1to 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$)endfunction 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 asulong
struct pcReturned, value asulong
struct PrinterInfo4, _
pPrinterName$ as ptr, _
pServerName$ as ptr, _
Attributes asulong
PrinterInfo4Len = len(PrinterInfo4.struct)
Level =4
cbBuf = PrinterInfo4Len
uFlags = _LMEM_MOVEABLE or _LMEM_ZEROINIT
calldll #kernel32, "LocalAlloc", _
uFlags asuLong, _
cbBuf asuLong, _
hMem asuLong
calldll #kernel32, "LocalLock", _
hMem asuLong, _
pBuffer asuLong[retryEnumPrinters]
calldll #winspool, "EnumPrintersA", _
nFlags asulong, _
PrinterName asulong, _
Level asulong, _
pBuffer asulong, _
cbBuf asulong, _
pcbNeeded as struct, _
pcReturned as struct, _
result asbooleanif result =0thenif GetLastError()=122then
cbBuf = pcbNeeded.value.struct
hOldMem = hMem
calldll #kernel32, "LocalReAlloc", _
hOldMem asulong, _
cbBuf asulong, _
uFlags asulong, _
hMem asulong
calldll #kernel32, "LocalLock", _
hMem asuLong, _
pBuffer asulonggoto[retryEnumPrinters]elsecall DisplayError
endifelse
EnumPrinters = pcReturned.value.struct
BufferPointer = pBuffer
for count =0to EnumPrinters -1
calldll #kernel32, "RtlMoveMemory", _
PrinterInfo4 as struct, _
BufferPointer asulong, _
PrinterInfo4Len asulong, _
result as void
BufferPointer = BufferPointer + PrinterInfo4Len
pointer = PrinterInfo4.pPrinterName$.struct
PrinterInfo$ = winstring(pointer); ";"; PrinterInfo$
next count
PrinterInfo$ =left$(PrinterInfo$, len(PrinterInfo$)-1)endif
calldll #kernel32, "LocalFree", _
hMem asuLong, _
result asuLong
close #winspool
endfunctionfunction GetDefaultPrinter(byref currentDefaultPrinter$)' Returns zero if call fails
struct pcchBuffer, value asulong
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 aslong
close #winspool
if GetDefaultPrinter =0thencall DisplayError
else
currentDefaultPrinter$ =left$(currentDefaultPrinter$, pcchBuffer.value.struct-1)endifendfunctionfunction SetDefaultPrinter(selectedDefaultPrinter$)' Returns zero if call fails
open "winspool.drv"for dll as #winspool
calldll #winspool, "SetDefaultPrinterA",_
selectedDefaultPrinter$ as ptr,_
SetDefaultPrinter aslong
close #winspool
if SetDefaultPrinter =0thencall DisplayError
endfunctionsub DisplayError
ErrorCode = GetLastError()
dwFlags = _FORMAT_MESSAGE_FROM_SYSTEM
nSize =1024
lpBuffer$ =space$(nSize); chr$(0)
dwMessageID = ErrorCode
calldll #kernel32, "FormatMessageA", _
dwFlags asulong, _
lpSource asulong, _
dwMessageID asulong, _
dwLanguageID asulong, _
lpBuffer$ as ptr, _
nSize asulong, _
Arguments asulong, _
result asulong
print "Error "; ErrorCode; ": "; left$(lpBuffer$, result)endsubfunction GetLastError()
calldll #kernel32, "GetLastError", _
GetLastError asulongendfunction
Getting, Listing, Setting the Default Printer (GUI)
A demo using a dialog window to select the user's choice of printer. The program returns the original default printer as default after each print.
' Get the original default printer
GetDefaultPrinter = GetDefaultPrinter(origDefaultPrinter$)
origDefaultPrinter$ = origDefaultPrinter$
defaultPrinter$ = origDefaultPrinter$
' 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)
LocalPrinterInfo$ = PrinterInfo$
nNetworkPrinters = EnumPrinters(PrinterInfo$, PRINTER.ENUM.CONNECTIONS)
NetworkPrinterInfo$ = PrinterInfo$
' Add both to total and combine the 2 printer strings
nPrinters = nLocalPrinters + nNetworkPrinters
PrinterInfo$ = LocalPrinterInfo$;";";NetworkPrinterInfo$
' Place all printers in an arraydim availablePrinters$(nPrinters)for i =1to nPrinters
availablePrinters$(i)= word$(PrinterInfo$, i, ";")next i
WindowWidth =809
WindowHeight =600
UpperLeftX =Int((DisplayWidth - WindowWidth)/2)
UpperLeftY =Int((DisplayHeight - WindowHeight)/2)
menu #main, "&File", "&Print",[printerSelection], |,"E&xit", [quit]
Graphicbox #main.g, 1, 0, 800, 550
open "Printer Selection"for Window as #main
#main "trapclose [quit]"call screenDisplay
wait
[quit]
close #main
end[printerSelection]
WindowWidth =250
WindowHeight =150
UpperLeftX =8
UpperLeftY =8
listbox #dlg.sel, availablePrinters$(), [printScreen], 14, 20, 214, 54
button #dlg.prnt, "Print", [printScreen], UL, 14, 84, 70, 28
button #dlg.cncl, "Cancel", [closeDlg], UL, 160, 84, 70, 28
stylebits #dlg, _WS_POPUP or _WS_THICKFRAME, _WS_CAPTION, 0, 0
open "Select Printer"for dialog_modal as #dlg
#dlg "trapclose [closeDlg]"
#dlg.sel"select ";defaultPrinter$
wait
[printScreen]
#dlg.sel"selection? selPrinter$"
defaultPrinter$ = selPrinter$
' Set the default printer as the selected printer
SetDefaultPrinter = SetDefaultPrinter(selPrinter$)
#main.g"print svga"' Return the default printer to the original default printer
SetDefaultPrinter = SetDefaultPrinter(origDefaultPrinter$)[closeDlg]
close #dlg
wait
sub screenDisplay
#main.g"down; font verdana 14 bold; place 300 200"
#main.g, "\Hello World"
#main.g, "flush"endsubfunction 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 asulong
struct pcReturned, value asulong
struct PrinterInfo4, _
pPrinterName$ as ptr, _
pServerName$ as ptr, _
Attributes asulong
PrinterInfo4Len = len(PrinterInfo4.struct)
Level =4
cbBuf = PrinterInfo4Len
uFlags = _LMEM_MOVEABLE or _LMEM_ZEROINIT
calldll #kernel32, "LocalAlloc", _
uFlags asuLong, _
cbBuf asuLong, _
hMem asuLong
calldll #kernel32, "LocalLock", _
hMem asuLong, _
pBuffer asuLong[retryEnumPrinters]
calldll #winspool, "EnumPrintersA", _
nFlags asulong, _
PrinterName asulong, _
Level asulong, _
pBuffer asulong, _
cbBuf asulong, _
pcbNeeded as struct, _
pcReturned as struct, _
result asbooleanif result =0thenif GetLastError()=122then
cbBuf = pcbNeeded.value.struct
hOldMem = hMem
calldll #kernel32, "LocalReAlloc", _
hOldMem asulong, _
cbBuf asulong, _
uFlags asulong, _
hMem asulong
calldll #kernel32, "LocalLock", _
hMem asuLong, _
pBuffer asulonggoto[retryEnumPrinters]elsecall DisplayError
endifelse
EnumPrinters = pcReturned.value.struct
BufferPointer = pBuffer
for count =0to EnumPrinters -1
calldll #kernel32, "RtlMoveMemory", _
PrinterInfo4 as struct, _
BufferPointer asulong, _
PrinterInfo4Len asulong, _
result as void
BufferPointer = BufferPointer + PrinterInfo4Len
pointer = PrinterInfo4.pPrinterName$.struct
PrinterInfo$ = winstring(pointer); ";"; PrinterInfo$
next count
PrinterInfo$ =left$(PrinterInfo$, len(PrinterInfo$)-1)endif
calldll #kernel32, "LocalFree", _
hMem asuLong, _
result asuLong
close #winspool
endfunctionfunction GetDefaultPrinter(byref currentDefaultPrinter$)' Returns zero if call fails
struct pcchBuffer, value asulong
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 aslong
close #winspool
if GetDefaultPrinter =0thencall DisplayError
else
currentDefaultPrinter$ =left$(currentDefaultPrinter$, pcchBuffer.value.struct-1)endifendfunctionfunction SetDefaultPrinter(selectedDefaultPrinter$)' Returns zero if call fails
open "winspool.drv"for dll as #winspool
calldll #winspool, "SetDefaultPrinterA",_
selectedDefaultPrinter$ as ptr,_
SetDefaultPrinter aslong
close #winspool
if SetDefaultPrinter =0thencall DisplayError
endfunctionsub DisplayError
ErrorCode = GetLastError()
dwFlags = _FORMAT_MESSAGE_FROM_SYSTEM
nSize =1024
lpBuffer$ =space$(nSize); chr$(0)
dwMessageID = ErrorCode
calldll #kernel32, "FormatMessageA", _
dwFlags asulong, _
lpSource asulong, _
dwMessageID asulong, _
dwLanguageID asulong, _
lpBuffer$ as ptr, _
nSize asulong, _
Arguments asulong, _
result asulong
print "Error "; ErrorCode; ": "; left$(lpBuffer$, result)endsubfunction GetLastError()
calldll #kernel32, "GetLastError", _
GetLastError asulongendfunction
Getting and Setting the Default Printer
-Table of Contents
Liberty BASIC's Printerdialog Command
The printerdialog function brings up the standard Windows printer dialog box. From the Liberty BASIC helpfileUnfortunately, Liberty BASIC doesn't interact well with Windows printer dialog. Despite what information PrinterName$ holds, all documents, whether text (lprint, dump) 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 -
The code in this article is only valid for Windows 2k/XP/Vista/2k3/2k8. This code will not work for Windows 9x/ME. For code that will work with Windows 9x/ME, see Problem with printerdialog at the Liberty BASIC Community Forum.
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.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.
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.
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.
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.This is the same code, but with Stefan's error trapping included.
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.Getting, Listing, Setting the Default Printer (GUI)
A demo using a dialog window to select the user's choice of printer. The program returns the original default printer as default after each print.Printer Dialog Clone
-