community.borland.com

Article #15188: How to Print in Windows

 Technical Information Database

     FAQ: TI188D - How to Print in Windows
Category: Printing
Platform: All-32Bit
 Product: Delphi4.x,   

Description:

Printing in Windows is achieved with a device control associated
with the printing device described in the WIN.INI.  Before you
start printing in your program, you will need to create a device
control associated with your printer.

To do this, you will call the function GetProfileString as
follows.

            GetMem(Info, 80);
            GetProfileString('windows','device',',,', Info, 80);

Where Info is defined to be of type PChar.  The resulting ASCIIZ
string will be placed in Info.  Unfortunately, CreateDC requires
three parameters, one for the driver, the printer type, and the
port.  If GetProfileString is successful, Info contains all the
information required by CreateDC but contained in one string
separated by semi-colons.  These items must be parsed out of the
string Info before CreateDC can be called.  The following
function is helpful in this task.

      function GetItem(var S:PChar): PChar;
      var
        P: PChar;
        I: Integer;
      begin
        i:=0;
        while (S[I]<>',') and (S[I]<>#0) do
           inc(I);
        S[I]:=#0;
        GetItem:=S;

        if S[0]<>#0 then S:=   ;
      end;

The function GetItem does not copy the strings but rather turns
the larger string into a structure containing smaller component
strings.  Now separating the driver, printer type, and port
information is fairly straight forward.  Assuming that the PChar
variables Driver, PrinterType, Port and TInfo have been
predeclared, you would use the following assignments.

      TInfo:=Info;
      PrinterType:=GetItem(Info);
      Driver:=GetItem(Info);
      Port:=GetItem(Info);

The variable TInfo is necessary for a subsequent FreeMem call to
free up the memory originally allocated for Info.

At last, you may now call CreateDC with the appropriate
parameters (assuming DC was previously declared to be of type
HDC).

      DC:=CreateDC(Driver, PrinterType, Port, Nil);

If for some reason CreateDC is not successful, it will return a
value of zero.

Now you are ready to start using the Escape function.  Escape is
a very special function as it allows access to device
capabilities that are not supported by the Windows GDI.  The
second parameter to Escape represents the particular escape
function to be used.  Two fundamental escape functions always
used when printing in Windows are STARTDOC and ENDDOC. These
functions do any setup and shutdown required by the printer.
Another important escape function is NEWFRAME.  NEWFRAME forces a
buffer flush and causes either a form feed operation or a page
eject operation depending upon the type of printer used.

To print some text, the following code illustrates the process
involved.

      procedure Print;
      var
        TInfo, Info: PChar;
        Driver, PrinterType, Port: PChar;
        DC: HDC;

      begin
        GetMem(Info, 80);
        GetProfileString('windows', 'device', ',,', Info, 80);

        TInfo:=Info;
        Driver:=GetItem(Info);
        PrinterType:=GetItem(Info);
        Port:=GetItem(Info);
        DC:=CreateDC(PrinterType, Driver, Port, Nil);
        FreeMem(TInfo, 80);

        if Escape(DC, _STARTDOC, 8,
                  StrNew('Doc Name'), nil)<0then
        begin
           MessageBox(Window, 'Printing can not be started', nil,
                      mb_Ok or mb_IconStop);
           exit;
        end;

        if Escape(DC, NEWFRAME, 0, nil, nil)<0 then
        begin
           MessageBox(Window, 'Paper can not be advanced', nil,
             mb_Ok or mb_IconStop);
           exit;
        end;

        TextOut(DC, 10, 10, 'This text is being printed', 26);

        if Escape(DC, NEWFRAME, 0, nil, nil)<0 then
        begin
           MessageBox(Window, 'Paper can not be advanced', nil,
             mb_Ok or mb_IconStop);
           exit;
        end;

        if Escape(DC, ENDDOC, 0, nil, nil)<0 then
        begin
           MessageBox(Window, 'Error Printing', nil,
             mb_Ok or mb_IconStop);
           exit;
        end;
      end;

Notice that if Escape fails that it returns a value less than
zero.  The actual errors are governed by the spooler constants
sp_Error, sp_OutOfDisk, sp_OutOfMemory, and sp_UserAbort.  It is
recommended that a NEWFRAME call precede and follow the intended
output to ensure that the printer is working with a fresh piece
of paper and flushes its buffer when printing ends.

The coordinates used in TextOut are arbitrary.  Exactly lines can
be calculated from the values returned by GetTextMetrics as you
would for normal output to the screen.  See Chapter 4 of the
Windows Reference Guide of TPW for a complete description of the
TTextMetric type.  The tmHeight, tmAveCharWidth, and
tmMaxCharWidth fields will prove to be the most useful when
determining line lengths and the number of characters per page.
Use GetDeviceCaps to determine the maximum horizontal and
vertical resolutions of your printer when calculating these
values.

Since device controls are used, printing graphics is a simple
extension of what is done to display graphics on screen.  Here's
a procedure that copies a bitmap to the printer using the
techniques that have been discussed so far.

procedure HardCopy(HWindow: HWnd; BitMap: HBitMap);
var
  DC, ScreenDC, MemDC: HDC;
  BM: TBitMap;
  OldBitMap: HBitMap;
  Info, TInfo: PChar;
  Driver, PrinterType, Port: PChar;
begin
  GetMem(Info, 80);
  GetProfileString('windows', 'device', ',,', Info, 80);
  TInfo:=Info;
  Driver:=GetInfo(Info);
  PrinterType:=GetItem(Info);
  Port:=GetItem(Info);

  DC:=CreateDC(PrinterType, Driver, Port, Nil);
  Escape(DC, STARTDOC, 8, StrNew('HardCopy'), nil);
  Escape(DC, NEWFRAME, 0, nil, nil);

  ScreenDC:=GetDC(HWindow);
  MemDC:=CreateCompatibleDC(DC);
  ReleaseDC(HWindow, ScreenDC);

  OldBitMap:=SelectObject(MemDC, BitMap);
  GetObject(BitMap, sizeof(BM), @ BM);
  BitBlt(DC, 0,0, BM.bmWidth, BM.bmHeight,
     MemDC, 0, 0, SRCCOPY);

  Escape(DC, NEWFRAME, 0, nil, nil);
  Escape(DC, ENDDOC, 0, nil, nil);

  SelectObject(MemDC, OldBitMap);
  DeleteDC(MemDC);
  DeleteDC(DC);
end;

For clarity, this routine does not perform error checking in the
manner of the previous example so use it with appropriate
caution.

As with BitBlt, you may use any of the other GDI functions with a
DC associated with the printer.  The only difficulty that you may
run into in this regard is the capabilities of the printer
itself. To determine if a printer can handle bitmaps, use the
GetDeviceCaps function as follows.

            Value:=GetDeviceCaps(DC, RASTERCAPS);
            Value:=Value and rc_BitBlt;

Assuming, DC to be associate with a printer device and Value to
be declared as an integer, if the resulting contents of Value is
not zero then the device has the ability to display bitmaps.  See
Device Capabilities under Chapter 1 of TPW's Windows Reference
Guide for more on what GetDeviceCaps can do.

Frequently, programs give user's the ability to change the
setting of the printer.  However, printers often have greatly
different capabilities and it is impossible to determine what
printers will be capable of in the future.  Therefore, no one
dialog is appropriate to change printer setting.  Because of
these facts, Windows requires the manufacturers of printers to
supply drivers and incorporate into these drivers a dialog used
to alter printer settings.

There are two functions supported by the Windows API to call up
this dialog DeviceMode and ExtDeviceMode.  These functions do not
exist in the API but rather are loaded from the driver directly.
Before you can call on these functions you must obtain a driver
handle.  Here's how this would be done knowing what we know so
far.

  GetMem(FullDriver,13);
  GetProfileString('windows', 'device', ',,', Info, 80);
  TInfo:=Info;
  PrinterType:=GetItem(Info);
  Driver:=GetItem(Info);
  Port:=GetItem(Info);
  StrLCat(StrCopy(FullDriver,Driver),'.DRV',12);
  DriverHandle:=LoadLibrary(FullDriver);

PrinterType, Driver, Port, Info, and TInfo you have seen above.
FullDriver is declared to be PChar and DriverHandle is a THandle.
FullDriver is necessary to provide the file extension to Driver
as the function LoadLibrary requires a complete file name.  All
printer drivers have the file extension .DRV.

ExtDeviceMode is a more advanced form of DeviceMode.  It not only
provides the dialog to allow user's change settings but also
gives the means for the program to moniter and change the device
settings.  DeviceMode only provides the user dialog capability.
However, ExtDeviceMode was introduced in version 3.0 of Windows
and not all printer drivers provide a ExtDeviceMode function.
But, all printer driver provide a DeviceMode function.  So if
ExtDeviceMode is not present, DeviceMode will be.  The following
code is appropriate for displaying the printer's dialog.

      P:=GetProcAddress(DriverHandle, 'ExtDeviceMode');
      if P<>nil then
      begin
        ExtDeviceMode:=TExtDeviceMode(P);
        Size:=ExtDeviceMode(Window, DriverHandle,
           OutModeRec^, PrinterType, Port,
           InModeRec^, nil, 0);
        GetMem(OutModeRec, Size);
        GetMem(InModeRec, Size);
        ExtDeviceMode(Window, DriverHandle,
           OutModeRec^, PrinterType, Port,
           InModeRec^, nil, dm_Prompt);
        FreeMem(OutModeRec, Size);
        FreeMem(InModeRec, Size);
      end
      else begin
        P:=GetProcAddress(DriverHandle, 'DeviceMode');
        DeviceMode:=TDeviceMode(P);
        DeviceMode(Window, DriverHandle, PrinterType, Port);
      end;
      FreeLibrary(DriverHandle);

P is declared to be of type TFarProc; ExtDeviceMode is of type
TExtDeviceMode which is supplied by the WinTypes unit (the same
applies for DeviceMode and TDeviceMode).  Both OutModeRec and
InModeRec are declared to be of type PDevMode. These two pointers
control the input and output to ExtDeviceMode where required.

Notice that if the results of GetProcAddress(DriverHandle,
'ExtDeviceHandle'); are nil then ExtDeviceHandle cannot be
provided by the printer.  In this case, the code proceeds to the
simple DeviceMode call.

Before ExtDeviceMode can be called to prompt the user, it is a
good idea to call ExtDeviceMode with its mode parameter (the 8th
parameter) set to zero.  The result will be the amount of space
required for it input and output, TDevMode parameters.  This is
important as these records may not be the same size as the
TDevMode type.  After memory is allocated for these records, the
data fields of the input record may then be initialized (see
Chapter 4 of TPW's Windows Reference guide for more on TDevMode).

Now ExtDeviceMode is called with the mode paramter set to
dm_Prompt which displays and activates the printer's dialog.
This constant can be combined with the other constants dm_Copy,
dm_Modify, and dm_Update with Turbo's bitwise OR operator. (See
Chapter 1 of TPW's Windows Reference Guide for information on
these dm_ Device mode selections).

Another important feature for printing is the Abort Dialog.  An
Abort Dialog is a dialog displayed when printing is taking place
and gives the user of the program an opprotunity to halt printing
before it is finished.

The first thing to do, is create a dialog suitable for the task.
Something like this .DLG file would serve (in a true DLG file all
data for statement would appear on a single line).  Though you
may prefer to use a resource toolkit of some sort to build your
dialog into a resource used by your program.

ABORTBOX DIALOG DISCARDABLE LOADONCALL PURE MOVEABLE 10, 37, 187,
  67
STYLE WS_POPUP | WS_VISIBLE | WS_CAPTION | 0x80L
CAPTION "Printing in Progress"
BEGIN
  CONTROL "Escape" 100, "BUTTON", WS_CHILD | WS_VISIBLE |
     WS_TABSTOP, 73, 37, 41, 16
  CONTROL "Press Escape to Stop Printing" 102, "STATIC", WS_CHILD
     | WS_VISIBLE, 44, 12, 99, 11
END

Next, using the Object Windows Library, you would define an
object type to govern your Abort Dialog.  Its actuall definition
needs to be no more than something like the following.

var
  Abort: Boolean;
  AbortWindow: HWindow;
type
  PAbortDialog = ^TAbortDialog;
  TAbortDialog = object(TDlgWindow)
     procedure SetUpWindow; virtual;
     procedure WMCommand(var Msg: TMessage);
       virtual wm_First + wm_Command;
  end;

procedure TAbortDialog.SetUpWindow;
begin
  Abort:=false;
  SetFocus(HWindow);
  AbortWindow:=HWindow;
end;

procedure TAbortDialog.WMCommand(var Msg: TMessage);
{Will be entered whenever a button, return key or escape key is
pressed.}
begin
  Abort:=true;
end;

As any button press, return key press, or escape key press will
abort printing only a WMCommand method is needed.  InitDialog is
necessary to set the focus to this dialog and to set the field
Abort.

Abort is used by the dialog to determine if printing has been
aborted.  To prevent interrupting printing while the dialog is
displayed, a Call Back function is required to be installed.
Most such functions call PeekMessage which checks the application
queue for a message.  If there are no messages, it returns and
gives control to Windows.  Here's a simple example of such a
function.

function AbortCallBack(DC: HDC; Code: Integer): Bool; export;
var
  Msg: TMsg;
begin
  While (not Abort) and PeekMessage(Msg, 0, 0, 0, pm_Remove) do
  if not IsDialogMessage(AbortWindow, Msg) then
  begin
   TranslateMessage(Msg);
   DispatchMessage(Msg);
  end;
  if Abort then AbortCallBack:=false else AbortCallBack:=true;
end;

PeekMessage yields control to Windows when a message is not
available.  If a message is received and it doesn't belong to the
dialog, it is sent along using the customary TranslateMessage and
DispatchMessage functions.  After it checks to see if the user
has aborted and if so, AbortCallBack returns false.  Otherwise,
it simply returns true which indicates printing may continue.

Now you have all that you need to install you Abort Dialog.
Before, your call to Escape for the STARTDOC,  the following code
should be executed (assuming that you have previously declared a
variable AbortDialog of type PAbortDialog and a variable called
AbortCallBackProc of type TFarProc to hold the address of the
callback function).

      AbortDialog:=New(PAbortDialog,
        Init(Application^.MainWindow, 'ABORTBOX'));
        AbortDialog:=PAbortDialog(Application^.
                                MakeWindow(AbortDialog));
        AbortCallBackProc:=MakeProcInstance(@ AbortCallBack,
        HInstance);
      Escape(DC, SETABORTPROC, 0, AbortCallBackProc, nil);

Now you would call Escape with the appropriate STARTDOC,
NEWFRAME, and ENDDOC parameters as illustrated before. However,
it is vital to check for errors after a NEWFRAME escape call.
For printing actually occurs during the NEWFRAME.  If an error or
user abort occurs Escape will return a value less than zero.  If
this happens do not allow the ENDDOC escape call take place; for
the GDI automatically terminates the operation before returning
the error value.  Also, if an error other than a user abort
occurs, use AbortDialog^.CloseWindow to remove the dialog from
view.  If the user cancles the print operation, Windows removes
the dialog automatically.



Reference:

 

7/29/99 2:39:59 PM
 

Last Modified: 01-SEP-99