Retrieving Printer Capabilities

In Enumerating printer forms article the DeviceCapabilities function is used to mark print forms supported by particular printer. It can be used to retrieve other printer capabilities as well.

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 and bins. It can be easily adapted to run in earlier VFP versions.

CLEAR
 
loPC = NEWOBJECT("PrinterCapabilities", "PrinterCapabilities.fxp", "", "", "English")
 
&& Enumerate forms for specified Windows printer
&&loPC.cPrinterName = "Adobe PDF"
 
loPC.cPrinterName = GETPRINTER()
 
IF NOT loPC.GetOtherCapabilities()
	? loPC.cErrorMessage
	? loPC.cApiErrorMessage
	&& Error
	RETURN .F.
ENDIF
 
? "Printer: " + loPC.cPrintername
 
IF loPC.lPrnColor
	? "Color is supported"
ENDIF	
 
IF loPC.lPrnDuplex 
	? "Duplex is supported"
ENDIF	
 
? "Max Paper Size: " + TRANSFORM(loPC.nPrnMaxPaperWidth) + " x " + TRANSFORM(loPC.nPrnMaxPaperLength) 
? "Min Paper Size: " + TRANSFORM(loPC.nPrnMinPaperWidth) + " x " + TRANSFORM(loPC.nPrnMinPaperLength) 
 
? "Max # of copies: " + TRANSFORM(loPC.nPrnMaxCopies)
 
IF loPC.nPrnOrientation > 0
	? "Landscape is supported. Rotatated " + TRANSFORM(loPC.nPrnOrientation) + " degrees"
ENDIF	
 
?
&& Bins
? "Bins"
IF NOT loPC.GetBinList()
	? loPC.cErrorMessage
	? loPC.cApiErrorMessage
	&& Error
	RETURN .F.
ENDIF
 
FOR i=1 TO loPC.oPrnBinList.Count
	loObject = loPC.oPrnBinList.Item(i)
	? i, loObject.BinID, loObject.BinName
ENDFOR
 
? 
 
 
&& Forms
? "Print Forms"
IF NOT loPC.GetFormList()
	? loPC.cErrorMessage
	? loPC.cApiErrorMessage
	&& Error
	RETURN .F.
ENDIF
 
FOR i=1 TO loPC.oPrnFormList.Count
	loObject = loPC.oPrnFormList.Item(i)
	? i, loObject.FormID, loObject.FormName, TRANSFORM(loObject.Width) + " x " + TRANSFORM(loObject.Height)
ENDFOR
 
loPC = Null
RETURN

The PrinterCapabilities class code

&& PrinterCapabilities.prg
#INCLUDE PrinterCapabilities.h
DEFINE CLASS PrinterCapabilities AS Custom
	HIDDEN nInch2mm, nCm2mm, nCoefficient, aList[1]
 
	&& Printer name
	cPrinterName = ""
	&& The form attributes are stored in tenths 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 = ""
	&& Win API support class
	oWas = NULL
	&& For internal use
	DIMENSION aList[1]
 
	&& Collection of Print Forms retrieved
	oPrnFormList = Null
	&& Bin list
	oPrnBinList = Null
	&& Color support
	lPrnColor  = .F.
	&& Duplex support
	lPrnDuplex  = .F.
	&& Max paper size printer supports
    nPrnMaxPaperLength = NULL
    nPrnMaxPaperWidth  = NULL
	&& Min paper size printer supports
    nPrnMinPaperLength = NULL
    nPrnMinPaperWidth  = NULL
	&& Max Copies supportd
	nPrnMaxCopies = 1
	&& Orientation: 0-Lanscape not supported
	nPrnOrientation = 0
 
	PROCEDURE Init(tcPrinterName, tcUnit, tnRound)
 
		IF PCOUNT() >= 1 AND NOT EMPTY(tcPrinterName)
			This.cPrinterName = tcPrinterName
		ELSE
			&& Default VFP printer
			This.cPrinterName = SET("Printer",3)
		ENDIF	
		IF PCOUNT() >= 2
			This.cUnit = PROPER(tcUnit)
		ELSE	
			This.cUnit = "English"
		ENDIF
		IF PCOUNT() = 3
			This.nRound = tnRound
		ENDIF
		This.oPrnFormList = CREATEOBJECT("Collection")
		This.oPrnBinList  = CREATEOBJECT("Collection")
 
		This.oWas = NEWOBJECT("WinApiSupport", "WinApiSupport.fxp")
 
		DECLARE Long GetLastError IN kernel32
 
	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 * 10
			IF This.nRound = 0
				This.nRound = 2
			ENDIF
		CASE PROPER(This.cUnit) = "Metric"
			This.nCoefficient = This.nCm2mm * 10
		OTHERWISE
			This.cUnit = "Internal"
			This.nCoefficient = 1
		ENDCASE
	ENDPROC
 
	PROCEDURE GetOtherCapabilities()
		LOCAL lnRetVal
		&& Color 
		lnRetVal = This.GetCapability(DC_COLORDEVICE)
		IF (lnRetVal < 0)
			RETURN .F.
		ENDIF
		This.lPrnColor = (lnRetVal > 0)
		&& Duplex
		lnRetVal = This.GetCapability(DC_DUPLEX)
		IF (lnRetVal < 0)
			RETURN .F.
		ENDIF
		This.lPrnDuplex = (lnRetVal > 0)
		&& Max Paper Size
		lnRetVal = This.GetCapability(DC_MAXEXTENT)
		IF (lnRetVal <= 0)
			RETURN .F.
		ENDIF
		This.nPrnMaxPaperLength = This.ConvertDimension(BITRSHIFT(lnRetVal,16))
		This.nPrnMaxPaperWidth  = This.ConvertDimension(BITAND(lnRetVal, 0xFFFF))
		&& Min Paper Size
		lnRetVal = This.GetCapability(DC_MINEXTENT)
		IF (lnRetVal <= 0)
			RETURN .F.
		ENDIF
		This.nPrnMinPaperLength = This.ConvertDimension(BITRSHIFT(lnRetVal,16))
		This.nPrnMinPaperWidth  = This.ConvertDimension(BITAND(lnRetVal, 0xFFFF))
		&& Max # of copies
		lnRetVal = This.GetCapability(DC_COPIES)
		IF (lnRetVal < 0)
			RETURN .F.
		ENDIF
		This.nPrnMaxCopies = lnRetVal 
		&& Orientation
		lnRetVal = This.GetCapability(DC_ORIENTATION)
		IF (lnRetVal < 0)
			RETURN .F.
		ENDIF
		This.nPrnOrientation = lnRetVal 
 
		RETURN .T.	
	ENDPROC
 
	PROCEDURE GetBinList()
		LOCAL llSuccess, lnCount, laBins[1], laBinNames[1]
 
		lnCount = This.GetCapability(DC_BINS, 2)
		IF (lnCount <= 0)
			RETURN .F.
		ENDIF
		ACOPY(This.aList, laBins)
 
		lnCount = This.GetCapability(DC_BINNAMES, 24, .T.)
		IF (lnCount <= 0)
			RETURN .F.
		ENDIF
		ACOPY(This.aList, laBinNames)
 
		FOR lnIndex = 1 TO lnCount
			loBin 			= This.GetOneObj("BIN")
			loBin.BinID   	= This.oWas.Short2Num(laBins[lnIndex])
			loBin.BinName 	= laBinNames[lnIndex]
			This.oPrnBinList.Add(loBin, TRANSFORM(loBin.BinID))
		ENDFOR
 
		RETURN .T.	
	ENDPROC
 
	PROCEDURE GetFormList()
		LOCAL llSuccess, lnCount, laForms[1], laFormNames[1], laFormSizes[1]
 
		lnCount = This.GetCapability(DC_PAPERS, 2)
		IF (lnCount <= 0)
			RETURN .F.
		ENDIF
		ACOPY(This.aList, laForms)
 
		lnCount = This.GetCapability(DC_PAPERNAMES, 64, .T.)
		IF (lnCount <= 0)
			RETURN .F.
		ENDIF
		ACOPY(This.aList, laFormNames)
 
		lnCount = This.GetCapability(DC_PAPERSIZE, 8)
		IF (lnCount <= 0)
			RETURN .F.
		ENDIF
		ACOPY(This.aList, laFormSizes)
 
		FOR lnIndex = 1 TO lnCount
			loForm 			= This.GetOneObj("FORM")
			loForm.FormID   = This.oWas.Short2Num(laForms[lnIndex])
			loForm.FormName = laFormNames[lnIndex]
			loForm.Width 	= This.ConvertDimension(This.oWas.Long2Num(LEFT(laFormSizes[lnIndex],4)))
			loForm.Height	= This.ConvertDimension(This.oWas.Long2Num(RIGHT(laFormSizes[lnIndex],4)))
 
			This.oPrnFormList.Add(loForm, TRANSFORM(loForm.FormID))
		ENDFOR
 
		RETURN .T.	
	ENDPROC
 
	HIDDEN PROCEDURE GetCapability(tnCapability, tnItemSize, tlNts)
		LOCAL lnResult, lcBuffer, lnIndex, llListReturned, lnItemCount
		&& Is list returned for this capability
		llListReturned = (PCOUNT() >= 2)
		&& Get a result or # of items in the list
		lnResult = DeviceCapabilitiesResult(This.cPrinterName, "", tnCapability, 0, 0)
		DO CASE
		CASE lnResult < 0
			&& Call to DeviceCapabilities failed
			This.cErrorMessage = "Capability requested: " + TRANSFORM(tnCapability) + ;
									". DeviceCapabilities() failed."
			This.cApiErrorMessage = WinApiErrMsg(GetLastError())
			RETURN lnResult 
		CASE NOT llListReturned 	
			&& The capabilty is returned in lnResult
			RETURN lnResult 
		OTHERWISE
			lnItemCount = lnResult
		ENDCASE
 
		&& Allocate buffer for the item list
		lcBuffer = REPLICATE(CHR(0), tnItemSize * lnItemCount)
		&& Get the list
		lnResult = DeviceCapabilitiesList(This.cPrinterName, "", tnCapability, @lcBuffer, 0)
 
 
		IF lnResult < 0
			&& Call to DeviceCapabilities failed
			This.cErrorMessage = "Capability requested: " + TRANSFORM(tnCapability) + ;
									". DeviceCapabilities() failed."
			This.cApiErrorMessage = WinApiErrMsg(GetLastError())
			RETURN 0
		ENDIF
 
		&& The capabilty is returned as alist in lcBuffer. Stuff it into array property aList
		FOR lnIndex=1 To lnResult
			DIMENSION This.aList[lnIndex]
			This.aList[lnIndex] = SUBSTR(lcBuffer, (lnIndex-1) * tnItemSize + 1, tnItemSize)
			IF tlNts
				&& An item is null terminated string.
				This.aList[lnIndex] = LEFT(This.aList[lnIndex], AT(CHR(0), This.aList[lnIndex] + CHR(0))-1)
			ENDIF
		ENDFOR
		RETURN lnResult
	ENDPROC
 
	FUNCTION ConvertDimension(tnValue)
		RETURN ROUND(tnValue / This.nCoefficient, This.nRound)
	ENDFUNC
 
	&& Create an object with attributes
	PROCEDURE GetOneObj(tcType)
		LOCAL loObject
		loObject = NEWOBJECT("Empty")
		DO CASE
		CASE UPPER(tcType) == "FORM"
			ADDPROPERTY(loObject, "FormId", 0)
			ADDPROPERTY(loObject, "FormName", "")
			ADDPROPERTY(loObject, "Width", 0)
			ADDPROPERTY(loObject, "Height", 0)
		CASE UPPER(tcType) == "BIN"
			ADDPROPERTY(loObject, "BinId", 0)
			ADDPROPERTY(loObject, "BinName", "")
		ENDCASE	
		RETURN loObject
	ENDPROC
 
	PROCEDURE ClearErrors
		This.cErrorMessage = ""
		This.cApiErrorMessage = ""
	ENDPROC
 
ENDDEFINE
&&----------------------------------------------------------------------------------------------
 
&& Can be called with pOutput=0 (Null) to get item count
FUNCTION DeviceCapabilitiesResult(pDevice, pPort, fwCapability, pOutput, pDevMode)
	DECLARE Long DeviceCapabilities IN winspool.drv AS DeviceCapabilitiesResult ;
		String pDevice, String pPort, Long fwCapability, ;
		long pOutput, Long pDevMode
	RETURN DeviceCapabilitiesResult(pDevice, pPort, fwCapability, pOutput, pDevMode)
ENDFUNC
 
&& Retirns Item list into pOutput
FUNCTION DeviceCapabilitiesList(pDevice, pPort, fwCapability, pOutput, pDevMode)
	DECLARE Long DeviceCapabilities IN winspool.drv AS DeviceCapabilitiesList ;
		String pDevice, String pPort, Long fwCapability, ;
		String @pOutput, Long pDevMode
	RETURN DeviceCapabilitiesList(pDevice, pPort, fwCapability, @pOutput, pDevMode)
ENDFUNC

The PrinterCapabilities.h header file

&& PrinterCapabilities.h
 
&& Device capabilities indices 
#DEFINE DC_FIELDS           1
#DEFINE DC_PAPERS           2
#DEFINE DC_PAPERSIZE        3
#DEFINE DC_MINEXTENT        4
#DEFINE DC_MAXEXTENT        5
#DEFINE DC_BINS             6
#DEFINE DC_DUPLEX           7
#DEFINE DC_SIZE             8
#DEFINE DC_EXTRA            9
#DEFINE DC_VERSION          10
#DEFINE DC_DRIVER           11
#DEFINE DC_BINNAMES         12
#DEFINE DC_ENUMRESOLUTIONS  13
#DEFINE DC_FILEDEPENDENCIES 14
#DEFINE DC_TRUETYPE         15
#DEFINE DC_PAPERNAMES       16
#DEFINE DC_ORIENTATION      17
#DEFINE DC_COPIES           18
 
&&!*	#DEFINE DC_BINADJUST            19
&&!*	#DEFINE DC_EMF_COMPLIANT        20
&&!*	#DEFINE DC_DATATYPE_PRODUCED    21
&&!*	#DEFINE DC_COLLATE              22
&&!*	#DEFINE DC_MANUFACTURER         23
&&!*	#DEFINE DC_MODEL                24
 
#DEFINE DC_PERSONALITY          25
#DEFINE DC_PRINTRATE            26
#DEFINE DC_PRINTRATEUNIT        27
#DEFINE   PRINTRATEUNIT_PPM     1
#DEFINE   PRINTRATEUNIT_CPS     2
#DEFINE   PRINTRATEUNIT_LPM     3
#DEFINE   PRINTRATEUNIT_IPM     4
#DEFINE DC_PRINTERMEM           28
#DEFINE DC_MEDIAREADY           29
#DEFINE DC_STAPLE               30
#DEFINE DC_PRINTRATEPPM         31
#DEFINE DC_COLORDEVICE          32
#DEFINE DC_NUP                  33
#DEFINE DC_MEDIATYPENAMES       34
#DEFINE DC_MEDIATYPES           35