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.
$SAMPLECODE$
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 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
class code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 * 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)
Comments
I want to try this code but
what is : EnumPrinterForms.fxp ?
ooo = NEWOBJECT("EnumPrinterForms", "EnumPrinterForms.fxp")
I split code into 2 parts for clarity
winapisupport error
Windows API support class
Can I change a printer form ID?
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
You cannot change a printer form ID in Windows.
Custom print form ID problem
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
Here's how the problem can be worked around:
<ol>
<li> Use code from this article to retrieve Custom form ID before running a report
<li> Make a temporary copy of the report
<li> Open the copy of report as a table
<li> Update PAPERSIZE=nnn value in the Expr memo field of the first report record with new Custom form ID. Alternatively, you can put new PAPERSIZE=nnn in the Picture memo field of the same record.
<li> Close the report table
<li> Run updated temporary copy of the report.
</ol>
lcPrinter
I believe the correct reference should be, instead, the EnumPrinterForms class's own "THIS.cPrinterName".
lcPrinter Typo fixed
Thanks, code being used at FoxyPreviewer
The printing samples are all great. I'm using an adapted code from this sample in <strong>FoxyPreviewer</strong>(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?