How to Print in Windows

 

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.