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