Enumerating printer forms
Enumerating of printer forms can be done with Windows API EnumForms function. Contrary to what MSDN says, it returns the list of all printer forms on PC, not just for the specific printer. On other hand, DeviceCapabilities can return a list of supported paper sizes for the printer.
| This is sample code. Add error handling and adjust to your requirements as necessary. |
Requires Windows 2000 or later. The WinApiErrMsg function in the code below is from Retrieving Windows system error message. The Windows API support class is used to handle Windows API structures.
The code will run 'as is' in VFP8 or later because it uses Collection class to store the list of forms. It can be easily adapted to run in earlier VFP versions.
CLEAR * Form flag values #define FORM_USER 0x00000000 #define FORM_BUILTIN 0x00000001 #define FORM_PRINTER 0x00000002 ooo = NEWOBJECT("EnumPrinterForms", "EnumPrinterForms.fxp") ooo.cUnit = "English" ooo.nRound = 2 * Enumerate forms for default VFP printer lcPrinter = SET("Printer",3) * Enumerate forms for specified Windows printer *lcPrinter = "Adobe PDF" * Enumerate all print forms and get info about specific for stored in the properties of the object IF NOT ooo.GetFormList(lcPrinter, "Envelope") ? ooo.cErrorMessage ? ooo.cApiErrorMessage * Error ENDIF ? ooo.cFormName, ooo.nFormNumber CREATE CURSOR crsPrintrForms ( ; FormID I, ; FormName C(30), ; Width N(9, ooo.nRound), ; Height N(9,ooo.nRound), ; FormFlags I, ; IsSupported L) FOR i=1 TO ooo.oFormList.Count loOneForm = ooo.oFormList.Item(i) *? loOneForm.FormID, loOneForm.FormName, loOneForm.Width, loOneForm.Height, loOneForm.FormFlags INSERT INTO crsPrintrForms FROM NAME loOneForm ENDFOR GO TOP *BROWSE NOWAIT BROWSE NOWAIT FOR IsSupported ooo = Null RETURN
The EnumPrinterForms class code
* EnumPrinterForms.prg DEFINE CLASS EnumPrinterForms AS Custom HIDDEN hHeap, nInch2mm, nCm2mm, nCoefficient * Specified a Printer name for which the list of supported forms is retrieved * If empty, it would retrieve the list of forms defined on local PC cPrinterName = "" * The form attributes are stored in thousands of millimeters * It can be converted by class to inches ("English") or centimeters ("Metric") cUnit = "Internal" * Specified how to round result of conversion nRound = 0 * Conversion Coefficients nInch2mm = 25.4 nCm2mm = 10 nCoefficient = 1 * Error code and Error message returned by Win API cApiErrorMessage = "" * Error message returned by class itself (none-API error) cErrorMessage = "" hHeap = 0 * Collection of Print Forms retrieved oFormList = Null * Win API support class oWas = NULL * Store Form # for the form cFormName = "" nFormNumber = 0 PROCEDURE Init(tcUnit, tnRound) IF PCOUNT() >= 1 This.cUnit = PROPER(tcUnit) ENDIF IF PCOUNT() = 2 This.nRound = tnRound ENDIF This.oFormList = CREATEOBJECT("Collection") This.oWas = NEWOBJECT("WinApiSupport", "WinApiSupport.fxp") * Load DLLs This.LoadApiDlls() * Allocate a heap This.hHeap = HeapCreate(0, 4096*10, 0) ENDPROC PROCEDURE cUnit_Assign(tcUnit) IF INLIST(tcUnit, "English", "Metric", "Internal") This.cUnit = PROPER(tcUnit) ELSE RETURN ENDIF * Calculate conversion coefficient DO CASE CASE PROPER(This.cUnit) = "English" This.nCoefficient = This.nInch2mm * 1000 CASE PROPER(This.cUnit) = "Metric" This.nCoefficient = This.nCm2mm * 1000 OTHERWISE This.cUnit = "Internal" This.nCoefficient = 1 ENDCASE ENDPROC PROCEDURE Destroy IF This.hHeap <> 0 HeapDestroy(This.hHeap) ENDIF ENDPROC *PROCEDURE GetFormNumber(tcPrinterName, tcFormName) *ENDPROC PROCEDURE GetFormList(tcPrinterName, tcFormName) LOCAL lhPrinter, llSuccess, lnNeeded, lnNumberOfForms, lnBuffer, i, lcFormName, lcFormName IF NOT EMPTY(tcPrinterName) This.cPrinterName = tcPrinterName ENDIF IF NOT EMPTY(tcFormName) This.cFormName = tcFormName ENDIF This.ClearErrors() This.nFormNumber = 0 This.oFormList.Remove(-1) * Open a printer lhPrinter = 0 lnResult = OpenPrinter( IIF(EMPTY(This.cPrinterName),0,This.cPrinterName), @lhPrinter, 0) IF lnResult = 0 This.cErrorMessage = "Unable to get printer handle for '" + This.cPrinterName This.cApiErrorMessage = WinApiErrMsg(GetLastError()) RETURN .F. ENDIF lnNeeded = 0 lnNumberOfForms = 0 * Get the size of the buffer required to fit all forms in lnNeeded IF EnumForms(lhPrinter, 1, 0, 0, @lnNeeded, @lnNumberOfForms ) = 0 IF GetLastError() <> 122 && The buffer too small error This.cErrorMessage = "Unable to Enumerate Forms" This.cApiErrorMessage = WinApiErrMsg(GetLastError()) RETURN .F. ENDIF ENDIF * Get the list of forms lnBuffer = HeapAlloc(This.hHeap, 0, lnNeeded) llSuccess = .T. IF EnumForms(lhPrinter, 1, lnBuffer, @lnNeeded, @lnNeeded, @lnNumberOfForms ) = 0 This.cErrorMessage = "Unable to Enumerate Forms." This.cApiErrorMessage = WinApiErrMsg(GetLastError()) llSuccess = .F. ENDIF IF llSuccess * Put list of the forms into collection with Form number (i) as a key * A collection here can be replaced with an array or a cursor. FOR i=1 TO lnNumberOfForms loOneForm = This.OneFormObj() WITH loOneForm lnPointer = lnBuffer + (i-1) * 32 .FormID = i .FormFlags = This.oWas.Long2NumFromBuffer(lnPointer) .FormName = This.oWas.StrZFromBuffer(lnPointer+4) .Width = This.ConvertFormDimension(lnPointer+8) .Height = This.ConvertFormDimension(lnPointer+12) .Left = This.ConvertFormDimension(lnPointer+16) .Top = This.ConvertFormDimension(lnPointer+20) .Right = This.ConvertFormDimension(lnPointer+24) .Bottom = This.ConvertFormDimension(lnPointer+28) * Store form # for requested form IF UPPER(.FormName) == UPPER(This.cFormName ) This.nFormNumber = .FormID ENDIF ENDWITH This.oFormList.Add(loOneForm, TRANSFORM(i)) ENDFOR * Mark forms that are supported by the printer llSuccess = This.MarkSupportedForms() ENDIF = HeapFree(This.hHeap, 0, lnBuffer ) = ClosePrinter(lhPrinter) RETURN llSuccess FUNCTION ConvertFormDimension(tnPointer) RETURN ROUND(This.oWas.Long2NumFromBuffer(tnPointer) / This.nCoefficient, This.nRound) ENDFUNC PROCEDURE MarkSupportedForms #DEFINE DC_PAPERS 2 LOCAL lnCount, lcBufferPapers, lnIndex, lcStr, lnFormID, loOneForm lcBufferPapers = REPLICATE(CHR(0), 2*512) * Get the list of supported paper sizes, 2 bytes per item lnCount = DeviceCapabilities(This.cPrinterName, "", DC_PAPERS, @lcBufferPapers, 0) IF lnCount <= 0 * Call to DeviceCapabilities failed This.cErrorMessage = "DeviceCapabilities failed." This.cApiErrorMessage = WinApiErrMsg(GetLastError()) RETURN .F. ENDIF FOR lnIndex=1 To lnCount lcStr = SUBSTR(lcBufferPapers, (lnIndex-1)*2+1, 2) lnFormID = This.oWas.Short2Num(lcStr) IF NOT EMPTY(This.oFormList.Getkey(TRANSFORM(lnFormID))) loOneForm = This.oFormList.Item(TRANSFORM(lnFormID)) loOneForm.IsSupported = .T. ENDIF ENDFOR ENDPROC * Create an object with forms attributes PROCEDURE OneFormObj LOCAL loOneForm loOneForm = NEWOBJECT("Empty") ADDPROPERTY(loOneForm, "FormFlags", 0) ADDPROPERTY(loOneForm, "FormId", 0) ADDPROPERTY(loOneForm, "FormName", "") ADDPROPERTY(loOneForm, "Width", 0) ADDPROPERTY(loOneForm, "Height", 0) ADDPROPERTY(loOneForm, "Left", 0) ADDPROPERTY(loOneForm, "Top", 0) ADDPROPERTY(loOneForm, "Right", 0) ADDPROPERTY(loOneForm, "Bottom", 0) * Indicates if printer supports the form ADDPROPERTY(loOneForm, "IsSupported", .F.) RETURN loOneForm ENDPROC PROCEDURE ClearErrors This.cErrorMessage = "" This.cApiErrorMessage = "" ENDPROC HIDDEN PROCEDURE LoadApiDlls DECLARE Long HeapCreate IN WIN32API Long dwOptions, Long dwInitialSize, Long dwMaxSize DECLARE Long HeapAlloc IN WIN32API Long hHeap, Long dwFlags, Long dwBytes DECLARE Long HeapFree IN WIN32API Long hHeap, Long dwFlags, Long lpMem DECLARE HeapDestroy IN WIN32API Long hHeap DECLARE Long GetLastError IN kernel32 ENDPROC ENDDEFINE *---------------------------------------------------------------------------------------------- FUNCTION OpenPrinter(tcPrinterName, thPrinter, tcDefault) DECLARE Long OpenPrinter IN WinSpool.Drv ; String pPrinterName, Long @ phPrinter, String pDefault RETURN OpenPrinter(tcPrinterName, @thPrinter, tcDefault) FUNCTION ClosePrinter (thPrinter) DECLARE Long ClosePrinter IN WinSpool.Drv Long hPrinter RETURN ClosePrinter(thPrinter) FUNCTION EnumForms(thPrinter, tnLevel, tnForm, tnBuf, tnNeeded, tnReturned) DECLARE Long EnumForms IN winspool.drv ; Long hPrinter, Long Level, Long pForm, ; Long cbBuf, Long @pcbNeeded, Long @ pcReturned RETURN EnumForms(thPrinter, tnLevel, tnForm, tnBuf, @tnNeeded, @tnReturned) FUNCTION DeviceCapabilities(pDevice, pPort, fwCapability, pOutput, pDevMode) DECLARE Long DeviceCapabilities IN winspool.drv ; String pDevice, String pPort, Long fwCapability, ; String @pOutput, Long pDevMode RETURN DeviceCapabilities(pDevice, pPort, fwCapability, @pOutput, pDevMode)
I want to try this code but
I want to try this code but an error occurs in first before any execution:
what is : EnumPrinterForms.fxp ?
ooo = NEWOBJECT("EnumPrinterForms", "EnumPrinterForms.fxp")
I split code into 2 parts for clarity
Put the EnumPrinterForms class (the second code block) into EnumPrinterForms.prg. After that you will be able to play with test code (the first code block)
winapisupport error
I use this code its generating a error ' winapisupport.fxp ' not found .. how can i solve this problem
Windows API support class
The links to Windows API support class and Retrieving Windows system error message are included above the code.
Can I change a printer form ID?
Thanks Sergey
All problem are solved. How can I change a printer form ID of a form is it possible?
You cannot change a printer form ID
Hi Hyder,
You cannot change a printer form ID in Windows.
Custom print form ID problem
hi...
i have a problem with vfp9 reports. I'm trying to use a custom print form, but the form id may be different on each PC,.
On a pc is 131, other 148, oher 126, but the report is created at with id 148. A solution may be change the form id to 148 in each pc? Any solution for it ?
Thanks
Re: Custom print form ID problem
Hi Deyvis,
Here's how the problem can be worked around:
lcPrinter
In EnumPrinterForms.MarkSupportedForms(), there is a reference to variable "lcPrinter", which is undefined within the EnumPrinterForms class.
I believe the correct reference should be, instead, the EnumPrinterForms class's own "THIS.cPrinterName".
lcPrinter Typo fixed
Thanks, Willson. I fixed it.
Thanks, code being used at FoxyPreviewer
Thanks a lot again Sergey !
The printing samples are all great. I'm using an adapted code from this sample in FoxyPreviewer(http://foxypreviewer.codeplex.com), to get the selected "Form" dimensions.
Once again....
The Bereznikeropedia Rocks !!!
What should I do to select the format found in the printer?
What should I do to select the format found in the printer?
Post new comment