Question and Answer Database FAQ1545D.txt — Changing the printer setup between pages Category :Printing Platform :All-32Bit Product : Question: How can I change the printer setup between printed pages? Answer: This is normally accomplished by retrieving a copy of the device mode structure that is associated with the current printer, making the necessary changes to the device independent portion of the structure, and then calling the Windows API function ResetDc() in between pages. Since the ResetDc() function is disabled by Windows during the printing of a page, and the TPrinter unit does not have an event that is fired between pages, a work around is required. While it is possible to insert and modify code directly in the implementation section of TPrinter, it is not possible to make changes to the interface portion of TPrinter and still maintain stability in the VCL and other third party components. With the advent of packages, rebuilding the main VCL package is also not an ideal solution. Finally, TPrinter is one of the few VCL classes that does not lend itself to subclassing, making it difficult to add an event that would fire between pages. Before the advent of packages, the work around consisted of creating an additional unit that sat between TPrinter and the unit(s) containing your printing code. A uses statement was added to the TPrinter unit in the implementation section that pointed to the new unit (avoiding any change to TPrinter's interface section). Finally, code was added to the TPrinter unit that would call a function pointer in the new unit between each printed page. You could then hook into TPrinter by adding the new unit to the uses clause of the unit(s) you are printing from, and reassigning the function pointer to point to a function in your unit. While this may sound complex, in practice, it was a clean solution to an interesting problem, that worked well with both VCL components and custom printing code produced directly in your application. With the advent of packages, a new solution is needed, since the TPrinter unit is compiled into the main VCL system package, where rebuilding is not recommended. Here we will provide such a solution in the form of a single unit that encapsulates both the work around and the printing code. The code is designed so it will compile under all versions of Delphi and Borland's C++ Builder. Since there will be no hooks added to the TPrinter unit, this solution will only work with custom printing code provided from your application, and not work with VCL and other "non-aware" third part components that offer a print method, or code utilizing the AssignPrn() function. The solution presented allows changes to the page setup by making direct calls to the Windows API to simulate TPrinter's NewPage method, and calling the Windows API function ResetDc() between pages. Note that not all printers support the ResetDc() escape. Under 16 bit versions of Windows, testing for escape support by calling Escape(QueryEscapeSupport...) returns the correct value for the ResetDc undocumented escape code #128, but Windows 95 instead steals the call, and returns true even if the driver does not support it. The only way to test the if the ResetDc() function is supported is to make a call directly to the function itself. To avoid printing blank pages in the event of an error, the example calls the ResetDC function and passes Printer.Handle as the DC before the printing actually starts to test for support of the function. This is ok, as the call is transformed by Windows to an escape to the print driver, and the dc is not actually passed but is instead used by Windows to identify the driver. If the print driver does not support the ResetDc() escape, we will accomplish changing the printer's settings by printing each page as separate print job. Since we are bypassing TPrinter's NewPage method, and since the print job may be accomplished by printing separate pages, the PageNumber property of TPrinter will not remain accurate through out the print job and should be tracked by the application. Finally, if abort method of TPrinter must be called, it should be called in a section of code after only after a StartPage command. Note: See TDEVMODE in the Delphi 1.02 help file or DEVMODE in the 32 bit versions of Delphi and C++ Builder for other settings you can change, such as the bin, paper sizes and so forth. Example: unit Unit1; interface {$IFDEF WIN32} uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Printers; {$ELSE} uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Printers, Print; {$ENDIF} type TForm1 = class(TForm) PrintDialog1: TPrintDialog; Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } function PageSetup : bool; function NewPage : bool; public { Public declarations } PageNumber : integer; CanReset : bool; end; var Form1: TForm1; implementation {$R *.DFM} {$IFNDEF WIN32} const MAX_PATH = 144; {$ENDIF} function TForm1.PageSetup : bool; var pDevice : pChar; pDriver : pChar; pPort : pChar; hDMode : THandle; PDMode : PDEVMODE; PrnHandle : THandle; begin result := false; GetMem(pDevice, cchDeviceName); GetMem(pDriver, MAX_PATH); GetMem(pPort, MAX_PATH); Printer.GetPrinter(pDevice, pDriver, pPort, hDMode); if hDMode <> 0 then begin pDMode := GlobalLock(hDMode); if pDMode <> nil then begin {Change your printing settings here} if not odd(PageNumber) then begin pDMode^.dmFields := pDMode^.dmFields or dm_PaperSize; pDMode^.dmPaperSize := DMPAPER_LETTER; end else begin pDMode^.dmFields := pDMode^.dmFields or dm_PaperSize; pDMode^.dmPaperSize := DMPAPER_LEGAL; end; if Printer.Printing then PrnHandle := Printer.Canvas.Handle else PrnHandle := Printer.Handle; {$IFDEF WIN32} if ResetDc(PrnHandle, pDMode^) <> 0 then {$ELSE} if ResetDc(PrnHandle, pDMode) <> 0 then {$ENDIF} CanReset := true else CanReset := false; Result := true; GlobalUnlock(hDMode); end; end; FreeMem(pDevice, cchDeviceName); FreeMem(pDriver, MAX_PATH); FreeMem(pPort, MAX_PATH); end; function TForm1.NewPage : bool; begin Result := true; if CanReset then EndPage(Printer.Canvas.Handle) else Printer.EndDoc; Inc(PageNumber); if PageSetUp then begin if CanReset then begin StartPage(Printer.Canvas.Handle); Printer.Canvas.Refresh; end else Printer.BeginDoc; end else begin if CanReset then begin StartPage(Printer.Canvas.Handle); Printer.Abort; end; Result := false; end; end; procedure TForm1.Button1Click(Sender: TObject); begin if PrintDialog1.Execute then begin {Setup printing} PageNumber := 1; if not PageSetUp then begin ShowMessage('Unable to customize printer settings'); exit; end; Printer.BeginDoc; {Print page one} Printer.Canvas.TextOut(100,100, 'Test Page 1'); if not NewPage then begin ShowMessage('Unable to customize printer settings'); exit; end; {Print page two} Printer.Canvas.TextOut(100,100, 'Test Page 2'); if not NewPage then begin ShowMessage('Unable to customize printer settings'); exit; end; {Print page three} Printer.Canvas.TextOut(100,100, 'Test Page 3'); Printer.EndDoc; end; end; end. 4/2/99 11:24:05 AM
Last Modified: 01-SEP-99