************************************************** *-- Class Library: c:\program files\microsoft visual studio\vfp98\dirprt.vcx ************************************************** ************************************************** *-- Class: DirectPrintOutput (c:\program files\microsoft visual studio\vfp98\dirprtclass.prg) *-- ParentClass: Line *-- BaseClass: Line *-- Time Stamp: 08/07/01 09:39:08 PM * * *!* This class provides direct printing to a Windows printer, Network printer UNC or defined port *!* without the interpretation/intervention of the Windows GDI, and without the normal Win behavior *!* of resetting the printer at the start of the spool job, standard Windows page treatment, or the *!* automatic skip to top of the next page at the end of the spooled output. This permits *!* direct, uninterpreted writing to a printer on a spool job by spool job basis, although it is *!* written to via a method of the object rather than via the VFP printer command set. It also *!* provides a method to spool a file to a printer without the GDI interpreting it's content, *!* letting you specify if the spooled file is deleted automatically after it is printed. *!* Please note that you are responsible for ALL printer control handling; if you want to force a new line, you have *!* to issue a CR/LF pair explicitly when writing output to the printer; similarly, you have to issue form feeds to *!* force skip to top of next page, any necessary PRINTER CONTROL codes selecting fonts or page orientation, and *!* generating ANY graphical image strings to include graphical content, which includes writing text for those *!* fonts which Windows usually translates automatically when using the GDI functions OR handling preparation *!* OF the print image. Using the direct, uninterpreted output will not let you rely on normal Windows GDI *!* services. *!* This class relies heavily on the direct spooler manipulation functions which are delivered through the Windows *!* spooler driver, WINSPOOL.DRV. The details of these functions are contained in the MSDN Platform SDK docs, and *!* the constants and detailed structure typedefs can be found in WINSPOOL.H in VC++'s \Include folder. A few other *!* common API calls have been used; these can be found in the Platform SDK and WINBASE.H in VC++. I've also used *!* the heap management services of my CLSHeap class; you'll need to include CLSHEAP.PRG in your program so that *!* it can be added as required to your SET PROCEDURE list during runtime. I leverage both the heap management *!* methods OF the heap class and a few of the data conversion UDFs IN the CLSHeap.PRG file. *!* CLASS Interface: *!* PUBLIC Properties: *!* cPrinterName R/O NAME OF LAST PRINTER OPENED FOR OUTPUT; NULL indicates none open by class *!* cDocName R/O NAME OF the CURRENT spool DOCUMENT being written TO; NULL indicates none active *!* nJobID R/O NUMBER OF the CURRENT spool job being written TO; NULL indicates none active *!* cErrorMsg R/O TEXT OF errors encountered while performing last method; NULL indicates none, *!* may contain multiple lines when several errors occurred. In most cases, if *!* several errors are given, the errors list conditions which, while NOT routine, *!* are still permitted, though change the state of the active object in *!* effect prior to executing the method invoked. These errors are reported, and *!* any action taken to correct the condition as well as it's result are noted, *!* but the requested method is processed, with the noted errors interpreted as *!* warnings which will be ignored. As an example, it's possible to issue a *!* DocOpen(), write into it, and then issue another DocOpen(), with a warning *!* issued that there was already an Open document, and it was closed before the *!* new document is opened. The DocOpen() suceeds, but the warning is recorded *!* in the cErrorMsg property on completion, the DocOpen() reports success. *!* PUBLIC methods: *!* INIT At invocation; adds CLSHeap to the PROCEDURE LIST as necessary, instantiates *!* a heap OBJECT, and defines the API calls required for the class to execute. *!* If unable to create a heap object, the method returns .F. and the object does *!* not instantiate. *!* RELEASE Closes any open printer or document, releases the oHeap object, and invokes *!* the DESTROY() method *!* PrinterOpen PARAMETERS: *!* cPrinterName - String naming a WINDOWS PRINTER, a defined LOCAL DEVICE, OR *!* the UNC OF a network printer. This parameter is required. *!* Opens the specified print device for use in creating or spooling documents *!* * returns Logical *!* PrinterIsOpen reports if a print device is open, as indicated by it's nhPrinter property *!* * returns Logical *!* DocIsOpen reports IF a spool document is open, AS indicated by it's nJobID property *!* * returns Logical *!* DocOpen PARAMETERS: *!* cDocName - String naming the spool job in the spooler. If omitted, an *!* arbitrary name is assigned using SYS(2015). This parameter is optional *!* Opens a new spool document for output. A printer must be open to succeed. *!* * returns Logical *!* DocClose If a document is presently open for spooling, Closes it *!* PrinterClose IF a printer is open, Closes it. If a document is open for spooling when *!* invoked, a DocClose() is issued *!* DocWrite PARAMETERS: *!* cOutput - The string to be output to the current spool document. It must *!* be a CHARACTER type. This parameter is required. *!* Writes the requested output without interpretation to the current spool *!* document. A spool document must be open for it to succeed. *!* * returns NULL IF failure *!* * returns a STRING in the form "xxx - yyy" where xxx is the length of the output *!* string passed, and yyy is the length of the data written to the spool document. *!* The ONLY TIME this may differ is if the spool file runs out of disk space, or if *!* the STRING is in UniCode and the printer only accepts ANSI. *!* ForcePage Forces the spooler to indicate closing of current page, and starts a new one. *!* The spooler does NOT perform the standard GDI page initialization, it is *!* offered to permit a spooled document to be restarted from a specified point - *!* if a print error occurs while writing to the print andND no page marks are *!* present in the document, the spooler would be forced to restart printing at the *!* beginning of the document. A spool document must be open for it to succeed. *!* * returns Logical *!* SpoolFile PARAMETERS: *!* cSourceFile - Name of file to spool. This must be a character string naming *!* a file that already exists and is NOT open. This parameter is required *!* lDelete - indicates if the source file is to be deleted once spooled. If *!* omitted or .F. the file is NOT deleted. This parameter is optional. *!* Spools specified file for printing on the open printer. A printer must be *!* open for it to succeed. *!* * returns NULL on failure *!* * returns Numeric spooled JobID if success *!* This has been tested under Win98, WinME, NT 4.0 and Win2K with VFP 5, 6 and VFP7 RC1. The class is released as-is in *!* SOURCE form, for PUBLIC USE. Copyright 2001, Ed Rauh. Inclusion of this class in other developers' work requires *!* written acknowledgement of the author and Copyright in both source code and product documentation. With such attribution, *!* the developer may include and use this code in their software without payment of royalties. *!* This code is released as-is; I DO NOT guarentee that it operates in all environments. I will respond to bug reports *!* or suggestions for enhancements as time permits. I released this as Source code so that others can extend, enhance *!* and modify as they desire. As far as code alterations, if you change the code AND it breaks, you own both pieces. IF *!* you make improvements to the code, OR fix bugs in the source, Please drop me a line describing your fixes and/or mods - *!* if I include them in an upgrade, I'll attribute them to you, and if not, I'll let you know my issues, AND you'll be *!* free to release your own version of the class, with appropriate attribution.' *!* Send email to edr@esolserv.com OR erauh@snet.net *!* Example usage: *!* SET PROC TO DIRPRTCLASS.PRG ADDITIVE *!* * Note - CLSHeap.PRG needs to be in your SET PROC list, or part of your project *!* oDirPrt = CREATEOBJ('DirectPrintOutput') *!* WITH oDirPrt *!* .PrinterOpen('LPT1:') && Arg can be a Win Printer name, port, or a print queue - works with both MSNetwork *!* && and NetWare queues. It also works with CAPTUREd or NET USEd printer ports *!* IF .PrinterIsOpen() *!* .DocOpen('MySpoolJob') *!* .DocWrite('This is sample text that prints after the first close' + CHR(13) + CHR(10) + CHR(9) + 'and this next line is indented one tab stop') *!* .DocWrite(' this continues the line') *!* .DocWrite(CHR(12)+'And this form feeds') *!* .DocWrite(' without a Windows page mark') *!* .SpoolFile('MyFileToDeleteAfterItPrints.TMP', .T.) && Spool file and delete after printed *!* && the spooled file prints first because the first document you're working is still open *!* .DocWrite(CHR(13) + CHR(10) + CHR(10) + 'this is still written to the first spool job' + CHR(13) + CHR(10)) *!* .DocWrite('*** still the same print document ***' + CHR(13)) && this line will get overwritten since CR w/o LF *!* .DocWrite(CHR(9) + 'You can embed any control sequence' + CHR(10) + CHR(10)) *!* .DocClose() *!* .SpoolFile('AnotherFileToPrinterWithoutDeleting') *!* .DocOpen() && assign a random job name *!* .DocWrite('<*** and note that the next print job picks up right where you left off') *!* .PrinterClose() && close both current job and the printer *!* ENDIF *!* ENDWITH DEFINE CLASS DirectPrintOutput AS LINE HEIGHT = 0 VISIBLE = .F. WIDTH = 0 NAME = "directprintoutput" *-- current print handle PROTECTED nhPrinter nhPrinter = NULL *-- Flag to indicate if currently executing an internal method PROTECTED lInternal lInternal = .T. *-- Heap object instance PROTECTED oHeap oHeap = NULL *-- Name of printer now open cPrinterName = NULL *-- name of document currently being spooled cDocName = NULL *-- JobID of active spool document nJobID = NULL *-- Error messages reported for most recent method invoked cErrorMsg = NULL PROTECTED HEIGHT HEIGHT = 0 PROTECTED WIDTH WIDTH = 0 PROTECTED BASECLASS PROTECTED BORDERCOLOR PROTECTED BORDERSTYLE PROTECTED BORDERWIDTH PROTECTED CLICK PROTECTED COLORSOURCE PROTECTED DBLCLICK PROTECTED DRAG PROTECTED DRAGDROP PROTECTED DRAGICON PROTECTED DRAGMODE PROTECTED DRAGOVER PROTECTED DRAWMODE PROTECTED LINESLANT PROTECTED MIDDLECLICK PROTECTED MOUSEDOWN PROTECTED MOUSEICON PROTECTED MOUSEMOVE PROTECTED MOUSEPOINTER PROTECTED MOUSEUP PROTECTED MOUSEWHEEL PROTECTED MOVE PROTECTED OLECOMPLETEDRAG PROTECTED OLEDRAG PROTECTED OLEDRAGDROP PROTECTED OLEDRAGMODE PROTECTED OLEDRAGOVER PROTECTED OLEDRAGPICTURE PROTECTED OLEDROPEFFECTS PROTECTED OLEDROPHASDATA PROTECTED OLEDROPMODE PROTECTED OLEGIVEFEEDBACK PROTECTED OLESETDATA PROTECTED OLESTARTDRAG PROTECTED RIGHTCLICK PROTECTED UIENABLE PROTECTED VISIBLE VISIBLE = .F. PROTECTED ZORDER lInternal = .F. PROCEDURE cPrinterName_Assign * Permit property to be altered by internal class processes, based on * the current state of the lInternal Property LPARAMETERS vNewVal IF THIS.lInternal THIS.cPrinterName = m.vNewVal ENDIF ENDPROC PROCEDURE cDocName_Assign * Permit property to be altered by internal class processes, based on * the current state of the lInternal Property LPARAMETERS vNewVal IF THIS.lInternal THIS.cDocName = m.vNewVal ENDIF ENDPROC PROCEDURE nJobID_Assign * Permit property to be altered by internal class processes, based on * the current state of the lInternal Property LPARAMETERS vNewVal IF THIS.lInternal THIS.nJobID = m.vNewVal ENDIF ENDPROC PROCEDURE cErrorMsg_Assign * Permit property to be altered by internal class processes, based on * the current state of the lInternal Property LPARAMETERS vNewVal IF THIS.lInternal THIS.cErrorMsg = m.vNewVal ENDIF ENDPROC *-- Unload class instance PROCEDURE RELEASE WITH THIS .lInternal = .T. IF .PrinterIsOpen() .PrinterClose() ENDIF .oHeap = NULL .DESTROY() ENDWITH ENDPROC *-- Set error message content PROTECTED PROCEDURE AddError LPARAMETER tcErrorMsg LOCAL lIntState WITH THIS lIntState = .lInternal && preserve internal execution state .lInternal = .T. DO CASE CASE ISNULL(tcErrorMsg) && Clear all entries from cErrorMsg property .cErrorMsg = NULL CASE ISNULL(.cErrorMsg) && No prior content - assign directly to property .cErrorMsg = tcErrorMsg OTHERWISE && Property contains entries - append CR/LF at end, followed by new message .cErrorMsg = .cErrorMsg + CHR(13) + CHR(10) + tcErrorMsg ENDCASE .lInternal = lIntState ENDWITH ENDPROC PROCEDURE INIT WITH THIS IF ! 'CLSHEAP' $ UPPER(SET('PROCEDURE')) SET PROCEDURE TO CLSHeap ADDITIVE ENDIF .oHeap = CREATEOBJ('Heap') DECLARE INTEGER OpenPrinter IN WINSPOOL.DRV ; STRING @ pPrinterName, ; INTEGER @ phPrinter, ; INTEGER pDefault DECLARE INTEGER StartDocPrinter IN WINSPOOL.DRV ; INTEGER hPrinter, ; INTEGER LEVEL, ; STRING @ pDOC_INFO_1 DECLARE INTEGER StartPagePrinter IN WINSPOOL.DRV ; INTEGER hPrinter DECLARE INTEGER WritePrinter IN WINSPOOL.DRV ; INTEGER hPrinter, ; STRING @ pBuf, ; INTEGER cdBuf, ; INTEGER @ pWritten DECLARE INTEGER EndPagePrinter IN WINSPOOL.DRV ; INTEGER hPrinter DECLARE INTEGER EndDocPrinter IN WINSPOOL.DRV ; INTEGER hPrinter DECLARE INTEGER ClosePrinter IN WINSPOOL.DRV ; INTEGER hPrinter DECLARE INTEGER GetLastError IN WIN32API DECLARE INTEGER AddJob IN WINSPOOL.DRV ; INTEGER hPrinter, ; INTEGER LEVEL, ; INTEGER pData, ; INTEGER cdBuf, ; INTEGER @ pcbNeeded DECLARE INTEGER ScheduleJob IN WINSPOOL.DRV ; INTEGER hPrinter, ; INTEGER JobID DECLARE INTEGER MoveFile IN WIN32API ; STRING @ lpszExisting, ; STRING @ lpszNew IF ISNULL(.oHeap) OR TYPE('.oHeap') # 'O' .oHeap = NULL ENDIF RETURN ! ISNULL(.oHeap) ENDWITH ENDPROC *-- Report if a printer handle is active PROCEDURE PrinterIsOpen RETURN ! ISNULL(THIS.nhPrinter) ENDPROC *-- Indicate if currently spooling a document PROCEDURE DocIsOpen RETURN ! ISNULL(THIS.nJobID) ENDPROC *-- Open a printer for spooling PROCEDURE PrinterOpen LPARAMETER tcPrinter LOCAL lIntState WITH THIS lIntState = .lInternal .lInternal = .T. IF ! lIntState .AddError(NULL) ENDIF IF .PrinterIsOpen() .AddError('PrinterOpen - Already open; forcing close') .PrinterClose() ENDIF IF TYPE('tcPrinter') # 'C' .AddError('PrinterOpen - invalid printer parameter specified') ELSE LOCAL nResult, nHandle nHandle = 0 nResult = OpenPrinter(tcPrinter,@nHandle,0) IF nResult = 0 .AddError('OpenPrinter API Error ' + TRANSFORM(GetLastError())) ELSE .nhPrinter = nHandle .cPrinterName = tcPrinter ENDIF ENDIF .lInternal = lIntState RETURN .PrinterIsOpen() ENDWITH ENDPROC *-- Start a new spool document for the open printer PROCEDURE DocOpen LPARAMETER tcDocName LOCAL lIntState WITH THIS lIntState = .lInternal .lInternal = .T. IF ! lIntState .AddError(NULL) ENDIF IF ! .PrinterIsOpen() .AddError('DocOpen - no printer open to create Doc') .lInternal = lIntState RETURN .F. ENDIF IF .DocIsOpen() .AddError('DocOpen - Already open; forcing close') .DocClose() ENDIF IF TYPE('tcDocName') # 'C' tcDocName = SYS(2015) ENDIF LOCAL cDocInfo,nAllocPtr nAllocPtr = .oHeap.AllocString(tcDocName) cDocInfo = NumToDWORD(nAllocPtr) + REPL(CHR(0),8) .nJobID = StartDocPrinter(.nhPrinter, 1, cDocInfo) IF .nJobID # 0 StartPagePrinter(.nhPrinter) .cDocName = tcDocName ELSE .AddError('StartDocPrinter API # ' + TRANSFORM(GetLastError())) .nJobID = NULL ENDIF .oHeap.DeAlloc(nAllocPtr) .lInternal = lIntState RETURN .DocIsOpen() ENDWITH ENDPROC *-- Close active document and release to spooler PROCEDURE DocClose LOCAL lIntState WITH THIS lIntState = .lInternal .lInternal = .T. IF ! lIntState .AddError(NULL) ENDIF IF .DocIsOpen() =EndPagePrinter(.nhPrinter) =EndDocPrinter(.nhPrinter) .nJobID = NULL .cDocName = NULL ENDIF .lInternal = lIntState ENDWITH ENDPROC *-- Close active printer handle PROCEDURE PrinterClose LOCAL lIntState WITH THIS lIntState = .lInternal .lInternal = .T. IF ! lIntState .AddError(NULL) ENDIF IF .DocIsOpen() .DocClose() ENDIF IF .PrinterIsOpen() =ClosePrinter(.nhPrinter) .nhPrinter = NULL .cPrinterName = NULL ENDIF .lInternal = lIntState ENDWITH ENDPROC *-- write string directly to currently open document PROCEDURE DocWrite LPARAMETER tcOutput LOCAL lIntState WITH THIS lIntState = .lInternal .lInternal = .T. IF ! lIntState .AddError(NULL) ENDIF LOCAL nResult, nWritten nResult = 0 nWritten = NULL IF ! .DocIsOpen() .AddError('DocWrite - no open document') ELSE nWritten = 0 nResult = WritePrinter(.nhPrinter,tcOutput,LEN(tcOutput),@nWritten) IF nWritten = 0 .AddError('WritePrinter API # ' + TRANSFORM(nResult)) ENDIF ENDIF .lInternal = lIntState RETURN IIF(ISNULL(nWritten),NULL,TRANSFORM(LEN(tcOutput))+'-'+TRANSFORM(nWritten)) ENDWITH ENDPROC *-- mark end of page of active doc and start another PROCEDURE ForcePage LOCAL lIntState WITH THIS lIntState = .lInternal .lInternal = .T. IF ! lIntState .AddError(NULL) ENDIF IF .DocIsOpen() =EndPagePrinter(.nhPrinter) =StartPagePrinter(.nhPrinter) ELSE .AddError('ForcePage - no document open') ENDIF .lInternal = lIntState RETURN .DocIsOpen() ENDWITH ENDPROC *-- Spool a file on active printer, optionally deleting noriginal file after printing it PROCEDURE SpoolFile LPARAMETERS tcSource, tlMove LOCAL lIntState, lOK lOK = .T. WITH THIS lIntState = .lInternal .lInternal = .T. IF ! lIntState .AddError(NULL) ENDIF DO CASE CASE ! .PrinterIsOpen() .AddError('SpoolFile - no printer open') lOK = .F. CASE TYPE('tcSource') # 'C' OR ! FILE(FULLPATH(tcSource)) .AddError('SpoolFile - illegal file name parameter') lOK = .F. CASE TYPE('tlMove') # 'L' .AddError('SpoolFile - illegal deletion parameter') lOK = .F. OTHERWISE LOCAL nAllocPtr, nBufLen, cDestFile, cBufStru, nJobID nBufLen = 0 nJobID = 0 nAllocPtr = .oHeap.AllocBLOB(REPL(CHR(0),301)) =AddJob(.nhPrinter, 1, nAllocPtr, 300, @nBufLen) cBufStru = LEFT(.oHeap.CopyFrom(nAllocPtr),8) cDestFile = GetMemString(DWORDToNum(cBufStru)) nJobID = DWORDToNum(RIGHT(cBufStru,4)) .oHeap.DeAlloc(nAllocPtr) IF tlMove =MoveFile(FULLPATH(tcSource),cDestFile) ELSE COPY FILE (FULLPATH(tcSource)) TO (cDestFile) ENDIF =ScheduleJob(.nhPrinter,nJobID) ENDCASE .lInternal = lIntState RETURN IIF(lOK,nJobID,NULL) ENDWITH ENDPROC ENDDEFINE * *-- EndDefine: directprintoutput **************************************************