A Different Use of the Abort Procedure

When a program assumes responsibility for banding, the GDI module uses the abort procedure somewhat differently than it does otherwise. If the Print Manager isn't loaded, the GDI module frequently calls the abort procedure with an nCode parameter of 0 while processing the NEXTBAND Escape call, just as it does when processing NEWFRAME. However, if the Print Manager is loaded (the more normal case), the GDI module calls the abort procedure only if it runs out of disk space. The nCode parameter is SP_OUTOFDISK.

This arrangement presents a problem. Unless the GDI module runs out of disk space, the user can't switch to another program until the application currently printing is finished. Moreover, although the printing dialog box is displayed, the user can't cancel the print job, because the dialog box can't get messages until the abort procedure is called. The solution to this problem is fairly simple. Your printing routine can call the abort procedure itself between the GDI drawing functions that make up the page. Although the operation of Windows isn't as smooth as when the GDI module calls the abort procedure, this approach at least allows the user to cancel the print job or move on to another task.

Don't call the abort procedure directly. Instead, use the pointer returned from MakeProcInstance. For instance, if your abort procedure is called AbortProc and the pointer returned from MakeProcInstance is called lpfnAbortProc, you can call AbortProc using:

(*lpfnAbortProc) (hdcPrn, 0) ;

The PRINT4 program, shown in Figure 15-13, adds banding to the printing logic in PRINT3. PRINT4 also requires the PRINT.RC file in Figure 15-8 and—like all our PRINT programs—the PRINT.C file in Figure 15-5.

PRINT4.MAK

#----------------------

# PRINT4.MAK make file

#----------------------

print4.exe : print.obj print4.obj print4.def print.res

link print4 print, /align:16, NUL, /nod slibcew libw, print4

rc print.res print4.exe

print.obj : print.c

cl -c -Gsw -Ow -W2 -Zp print.c

print4.obj : print4.c

cl -c -Gsw -Ow -W2 -Zp print4.c

print.res : print.rc

rc -r print.rc

PRINT4.C

/*---------------------------------------

PRINT4.C -- Printing with Banding

(c) Charles Petzold, 1990

---------------------------------------*/

#include <windows.h>

HDC GetPrinterDC (void) ; // in PRINT.C

typedef BOOL (FAR PASCAL * ABORTPROC) (HDC, short) ;

HANDLE hInst ;

char szAppName [] = "Print4" ;

char szCaption [] = "Print Program 4 (Banding)" ;

BOOL bUserAbort ;

HWND hDlgPrint ;

BOOL FAR PASCAL PrintDlgProc (HWND hDlg, WORD message, WORD wParam, LONG lParam)

{

switch (message)

{

case WM_INITDIALOG :

SetWindowText (hDlg, szAppName) ;

EnableMenuItem (GetSystemMenu (hDlg, FALSE), SC_CLOSE,

MF_GRAYED) ;

return TRUE ;

case WM_COMMAND :

bUserAbort = TRUE ;

EnableWindow (GetParent (hDlg), TRUE) ;

DestroyWindow (hDlg) ;

hDlgPrint = 0 ;

return TRUE ;

}

return FALSE ;

}

BOOL FAR PASCAL AbortProc (HDC hdcPrn, short nCode)

{

MSG msg ;

while (!bUserAbort && PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))

{

if (!hDlgPrint || !IsDialogMessage (hDlgPrint, &msg))

{

TranslateMessage (&msg) ;

DispatchMessage (&msg) ;

}

}

return !bUserAbort ;

}

BOOL PrintMyPage (HWND hwnd)

{

static char szSpMsg [] = "Print4: Printing" ;

static char szText [] = "Hello, Printer!" ;

ABORTPROC lpfnAbortProc ;

BOOL bError = FALSE ;

DWORD dwExtent ;

FARPROC lpfnPrintDlgProc ;

HDC hdcPrn ;

POINT ptExtent ;

RECT rect ;

short xPage, yPage ;

if (NULL == (hdcPrn = GetPrinterDC ()))

return TRUE ;

xPage = GetDeviceCaps (hdcPrn, HORZRES) ;

yPage = GetDeviceCaps (hdcPrn, VERTRES) ;

EnableWindow (hwnd, FALSE) ;

bUserAbort = FALSE ;

lpfnPrintDlgProc = MakeProcInstance (PrintDlgProc, hInst) ;

hDlgPrint = CreateDialog (hInst, "PrintDlgBox", hwnd, lpfnPrintDlgProc) ;

lpfnAbortProc = MakeProcInstance (AbortProc, hInst) ;

Escape (hdcPrn, SETABORTPROC, 0, (LPSTR) lpfnAbortProc, NULL) ;

if (Escape (hdcPrn, STARTDOC, sizeof szSpMsg - 1, szSpMsg, NULL) > 0 &&

Escape (hdcPrn, NEXTBAND, 0, NULL, (LPSTR) &rect) > 0)

{

while (!IsRectEmpty (&rect) && !bUserAbort)

{

(*lpfnAbortProc) (hdcPrn, 0) ;

Rectangle (hdcPrn, rect.left, rect.top, rect.right,

rect.bottom) ;

(*lpfnAbortProc) (hdcPrn, 0) ;

MoveTo (hdcPrn, 0, 0) ;

LineTo (hdcPrn, xPage, yPage) ;

(*lpfnAbortProc) (hdcPrn, 0) ;

MoveTo (hdcPrn, xPage, 0) ;

LineTo (hdcPrn, 0, yPage) ;

SaveDC (hdcPrn) ;

SetMapMode (hdcPrn, MM_ISOTROPIC) ;

SetWindowExt (hdcPrn, 1000, 1000) ;

SetViewportExt (hdcPrn, xPage / 2, -yPage / 2) ;

SetViewportOrg (hdcPrn, xPage / 2, yPage / 2) ;

(*lpfnAbortProc) (hdcPrn, 0) ;

Ellipse (hdcPrn, -500, 500, 500, -500) ;

(*lpfnAbortProc) (hdcPrn, 0) ;

dwExtent = GetTextExtent (hdcPrn, szText, sizeof szText - 1) ;

ptExtent = MAKEPOINT (dwExtent) ;

TextOut (hdcPrn, -ptExtent.x / 2, ptExtent.y / 2, szText,

sizeof szText - 1) ;

RestoreDC (hdcPrn, -1) ;

(*lpfnAbortProc) (hdcPrn, 0) ;

if (Escape (hdcPrn, NEXTBAND, 0, NULL, (LPSTR) &rect) < 0)

{

bError = TRUE ;

break ;

}

}

}

else

bError = TRUE ;

if (!bError)

{

if (bUserAbort)

Escape (hdcPrn, ABORTDOC, 0, NULL, NULL) ;

else

Escape (hdcPrn, ENDDOC, 0, NULL, NULL) ;

}

if (!bUserAbort)

{

EnableWindow (hwnd, TRUE) ;

DestroyWindow (hDlgPrint) ;

}

FreeProcInstance (lpfnPrintDlgProc) ;

FreeProcInstance (lpfnAbortProc) ;

DeleteDC (hdcPrn) ;

return bError || bUserAbort ;

}

PRINT4.DEF

;-----------------------------------

; PRINT4.DEF module definition file

;-----------------------------------

NAME PRINT4

DESCRIPTION 'Printing Program No. 4 (c) Charles Petzold, 1990'

EXETYPE WINDOWS

STUB 'WINSTUB.EXE'

CODE PRELOAD MOVEABLE DISCARDABLE

DATA PRELOAD MOVEABLE MULTIPLE

HEAPSIZE 1024

STACKSIZE 8192

EXPORTS WndProc

AbortProc

PrintDlgProc

PRINT4 differs from PRINT3 in only a few particulars. In order for AbortProc to be called while the program is printing, the GDI drawing routines have been moved into PrintMyPage. You'll notice that the Rectangle function prints the rectangle for each band rather than a rectangle on the border of the entire page. This allows you to see where the bands are for a particular printer. The structure of the printing operation looks like this:

if (Escape (hdcPrn, STARTDOC, sizeof szSpMsg - 1, szSpMsg, NULL) > 0 &&

Escape (hdcPrn, NEXTBAND, 0, NULL, (LPSTR) &rect) > 0)

{

while (!IsRectEmpty (&rect) && !bUserAbort)

{

[make GDI calls and call abort procedure]

if (Escape (hdcPrn, NEXTBAND, 0, NULL, (LPSTR) &rect) < 0)

{

bError = TRUE ;

break ;

}

}

}

else

bError = TRUE ;

The while loop for the band proceeds only if the rectangle isn't empty and if the user hasn't canceled the print job from the dialog box. PRINT4 has to check the return value from each NEXTBAND Escape call and set bError if Escape returns a negative value. If no Escape call returns an error, then the print job must either be ended with the ENDDOC Escape call or be aborted with the ABORTDOC Escape call. If the user cancels printing during the NEXTBAND loop, then the print job must be aborted using the ABORTDOC call. The code to do this is as follows:

if (!bError)

{

if (bUserAbort)

Escape (hdcPrn, ABORTDOC, 0, NULL, NULL) ;

else

Escape (hdcPrn, ENDDOC, 0, NULL, NULL) ;

}