Главная страница | назад





Article #16545: Changing the printer setup between pages

 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