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$

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

The EnumPrinterForms

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 an error occurs in first before any execution:
what is : EnumPrinterForms.fxp ?

ooo = NEWOBJECT("EnumPrinterForms", "EnumPrinterForms.fxp")

Put the <b>EnumPrinterForms</b> class (the second code block) into <b>EnumPrinterForms.prg</b>. After that you will be able to play with test code (the first code block)

I use this code its generating a error ' winapisupport.fxp ' not found .. how can i solve this problem

The links to <a href="/content/pages/visual-foxpro/windows-api-support-class">Windows API support class</a> and <a href="/content/pages/visual-foxpro/retrieving-windows-system-error-message">Retrieving Windows system error message</a> are included above the code.

Thanks Sergey

All problem are solved. How can I change a printer form ID of a form is it possible?


Hi Hyder,

You cannot change a printer form ID in Windows.

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

Hi Deyvis,

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>

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

Thanks, Willson. I fixed it.

Thanks a lot again Sergey !
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?