Add and delete custom printer forms

To distribute applications that use reports with custom paper size you have to create custom printer forms on every destination PC that runs Windows 2000 or later. The MS KB article Q157172 explains how it can be done manually. However there's a way to do that programmatically using Windows API.

The code is based on the working code posted by Jim Livermore on UT and work of George Tasker and late Ed Rauh.

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.

lcPrinterName = "Adobe PDF"
 
&& All sizes in inches
ooo = NEWOBJECT("AddPrinterForm", "AddPrinterForm.fxp")
IF NOT ooo.AddForm("MyCustomForm1", 5,7, lcPrinterName)
	? ooo.cErrorMessage
	? ooo.cApiErrorMessage 
  && Error
ENDIF
 
&&!*	* Delete just created form
&&!*	ooo = NEWOBJECT("AddPrinterForm", "AddPrinterForm.fxp")
&&!*	IF NOT ooo.DeleteForm("MyCustomForm1", lcPrinterName)
&&!*		? ooo.cErrorMessage
&&!*		? ooo.cApiErrorMessage 
&&!*	  * Error
&&!*	ENDIF
 
 
&& All sizes in cm
ooo = NEWOBJECT("AddPrinterForm", "AddPrinterForm.fxp", "", "Metric")
IF NOT ooo.AddForm("MyCustomForm2", 15,17, lcPrinterName)
  && Error
ENDIF
 
&&!*	* Delete just created form
&&!*	ooo = NEWOBJECT("AddPrinterForm", "AddPrinterForm.fxp")
&&!*	IF NOT ooo.DeleteForm("MyCustomForm2", lcPrinterName)
&&!*		? ooo.cErrorMessage
&&!*		? ooo.cApiErrorMessage 
&&!*	  * Error
&&!*	ENDIF
 
&&-------------------------------------------------------------------------------------------
 
&&AddPrinterForm.prg
DEFINE CLASS AddPrinterForm AS Custom
 
	HIDDEN cUnit, cPrinterName, nFormHeight, nFormWidth, nLeftMargin, ;
              nTopMargin, nRightMargin, nBottomMargin, nInch2mm, nCm2mm, nCoefficient, hHeap
 
	cUnit = "English"		&& inches or Metric - cm's
	cPrinterName = ""
	nFormHeight = 0
	nFormWidth = 0
	nLeftMargin = 0
	nTopMargin = 0
	nRightMargin = 0
	nBottomMargin = 0
 
	cApiErrorMessage = ""
	cErrorMessage = ""
 
	nInch2mm = 25.4
	nCm2mm = 10
	nCoefficient = This.nInch2mm * 1000
 
	hHeap = 0
 
	&& Win API support class
	oWas = NULL
 
	PROCEDURE Init(tcUnit)
	This.oWas = NEWOBJECT("WinApiSupport", "WinApiSupport.fxp")
	IF PCOUNT() = 1 
		This.cUnit = PROPER(tcUnit)
	ENDIF
	This.LoadApiDlls()
	This.hHeap = HeapCreate(0, 4096, 0)
	&& Use Windows default printer
	This.cPrinterName = SET("Printer",2)
	ENDPROC
 
	PROCEDURE cUnit_Assign(tcUnit)
	IF INLIST(tcUnit, "English", "Metric")
		This.cUnit = PROPER(tcUnit)
	ELSE
		RETURN	
	ENDIF
	&& Calculate conversion coefficient
	This.nCoefficient = IIF(PROPER(This.cUnit) = "English", ;
						This.nInch2mm, This.nCm2mm) * 1000
	ENDPROC
 
	PROCEDURE Destroy
	IF This.hHeap <> 0
		HeapDestroy(This.hHeap)
	ENDIF
 
	ENDPROC
 
	PROCEDURE SetFormMargins(tnLeft, tnTop, tnRight, tnBottom)
	WITH This
		.nLeftMargin 	= tnLeft   * .nCoefficient
		.nTopMargin 	= tnTop    * .nCoefficient
		.nRightMargin 	= tnRight  * .nCoefficient
		.nBottomMargin 	= tnBottom * .nCoefficient
	ENDWITH
	ENDPROC
 
	PROCEDURE AddForm(tcFormName, tnWidth, tnHeight, tcPrinterName)
	LOCAL lhPrinter, llOK, lcForm
 
	This.nFormWidth  = tnWidth  * This.nCoefficient
	This.nFormHeight = tnHeight * This.nCoefficient
	IF PCOUNT() > 3
		This.cPrinterName = tcPrinterName
	ENDIF
 
	This.ClearErrors()
	lhPrinter = 0
	IF OpenPrinter(This.cPrinterName, @lhPrinter, 0) = 0
		This.cErrorMessage = "Unable to get printer handle for " + This.cPrinterName 
		This.cApiErrorMessage = WinApiErrMsg(GetLastError())
		RETURN .F.
	ENDIF
 
	lnFormName = HeapAlloc(This.hHeap, 0, LEN(tcFormName) + 1)
	= SYS(2600, lnFormName, LEN(tcFormName) + 1, tcFormName + CHR(0))
 
	&& Build FORM_INFO_1 structure
	WITH This.oWas
		lcForm = .Num2Long(0) + .Num2Long(lnFormName) + ;
			.Num2Long(This.nFormWidth) + .Num2Long(This.nFormHeight) + ;
			.Num2Long(This.nLeftMargin) + .Num2Long(This.nTopMargin) + ;
			.Num2Long(This.nFormWidth - This.nRightMargin) + ;
			.Num2Long(This.nFormHeight - This.nBottomMargin)
	ENDWITH
 
	&& Finally, call the API
	IF AddForm(lhPrinter, 1, @lcForm) = 0
		This.cErrorMessage = "Unable to Add Form " + tcFormName 
		This.cApiErrorMessage = STRTRAN(WinApiErrMsg(GetLastError()), "file", "form", -1, -1, 3)
		llOK = .F.
	ELSE
		llOK = .T.
	ENDIF
	= HeapFree(This.hHeap, 0, lnFormName)
	= ClosePrinter(lhPrinter)
 
	RETURN llOK
 
	PROCEDURE ClearErrors
	This.cErrorMessage = ""
	This.cApiErrorMessage = ""
	ENDPROC
 
	PROCEDURE DeleteForm(tcFormName, tcPrinterName)
	LOCAL lhPrinter, llOK
 
	IF PCOUNT() > 1
		This.cPrinterName = tcPrinterName
	ENDIF
 
	This.ClearErrors()
	lhPrinter = 0
	IF OpenPrinter(This.cPrinterName, @lhPrinter, 0) = 0
		This.cErrorMessage = "Unable to get printer handle for " + This.cPrinterName + "."
		This.cApiErrorMessage = WinApiErrMsg(GetLastError())
		RETURN .F.
	ENDIF
 
	&& Finally, call the API
	llOK = ( DeleteForm(lhPrinter, tcFormName) <> 0 )
	IF NOT llOK 
		This.cErrorMessage = "Unable to delete Form " + tcFormName 
		This.cApiErrorMessage = STRTRAN(WinApiErrMsg(GetLastError()), "file", "form", -1, -1, 3)
	ENDIF
	= ClosePrinter(lhPrinter)
	RETURN llOK
 
	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 AddForm(thPrinter, tnLevel, tcForm)
DECLARE Long AddForm IN winspool.drv Long hPrinter, Long Level, String @pForm
RETURN AddForm(thPrinter, tnLevel, tcForm)
 
FUNCTION DeleteForm(thPrinter, tcForm)
DECLARE Long DeleteForm IN winspool.drv Long hPrinter, String  pFormName 
RETURN DeleteForm(thPrinter, tcForm)