PRINT2 is not entirely satisfactory. First, the program doesn't directly indicate when it is printing and when it is finished with printing. Only when you poke at the program with the mouse and find that it doesn't respond can you determine that it must still be processing the PrintMyPage routine. Nor does PRINT2 give the user the opportunity to cancel the print job before it shows up in the Print Manager's client area.
You're probably aware that most Windows programs give users a chance to cancel a printing operation currently in progress. A small dialog box comes up on the screen; it contains some text and a push button labeled Cancel. The program displays this dialog box during the entire time that GDI is saving the printer output in a disk file or (if the Print Manager isn't loaded) while the printer is printing. This is a modeless dialog box, and you must supply the dialog procedure. As for all dialog boxes, you include the name of the dialog procedure in the EXPORTS section of the module definition file and use MakeProcInstance to obtain a pointer to the function.
This dialog box is often called the ”abort dialog box,“ and the dialog procedure is often called the ”abort dialog procedure.“ To distinguish it more clearly from the ”abort procedure,“ I'll call this dialog procedure the ”printing dialog procedure.“ The abort procedure (with the name AbortProc) and the printing dialog procedure (which I'll name PrintDlgProc) are two separate exported functions. If you want to print in a professional Windows-like manner, you must have both of these.
These two functions interact as follows. The PeekMessage loop in AbortProc must be modified to send messages for the modeless dialog box to the dialog box window procedure. PrintDlgProc must process WM_COMMAND messages to check the status of the Cancel button. If the Cancel button is pressed, it sets a variable called bUserAbort to TRUE. The value returned from AbortProc is the inverse of bUserAbort. You'll recall that AbortProc returns TRUE to continue printing and FALSE to abort printing. In PRINT2 we always returned TRUE. Now we'll return FALSE if the user clicks the Cancel button in the printing dialog box. This logic is implemented in the PRINT3 program, shown in Figure 15-8, beginning on the following page.
PRINT3.MAK
#----------------------
# PRINT3.MAK make file
#----------------------
print3.exe : print.obj print3.obj print3.def print.res
link print3 print, /align:16, NUL, /nod slibcew libw, print3
rc print.res print3.exe
print.obj : print.c
cl -c -Gsw -Ow -W2 -Zp print.c
print3.obj : print3.c
cl -c -Gsw -Ow -W2 -Zp print3.c
print.res : print.rc
rc -r print.rc
PRINT3.C
/*---------------------------------------
PRINT3.C -- Printing with Dialog Box
(c) Charles Petzold, 1990
---------------------------------------*/
#include <windows.h>
HDC GetPrinterDC (void) ; // in PRINT.C
void PageGDICalls (HDC, short, short) ;
HANDLE hInst ;
char szAppName [] = "Print3" ;
char szCaption [] = "Print Program 3 (Dialog Box)" ;
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 szMessage [] = "Print3: Printing" ;
BOOL bError = FALSE ;
FARPROC lpfnAbortProc, lpfnPrintDlgProc ;
HDC hdcPrn ;
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 szMessage - 1, szMessage, NULL) > 0)
{
PageGDICalls (hdcPrn, xPage, yPage) ;
if (Escape (hdcPrn, NEWFRAME, 0, NULL, NULL) > 0)
Escape (hdcPrn, ENDDOC, 0, NULL, NULL) ;
else
bError = TRUE ;
}
else
bError = TRUE ;
if (!bUserAbort)
{
EnableWindow (hwnd, TRUE) ;
DestroyWindow (hDlgPrint) ;
}
FreeProcInstance (lpfnPrintDlgProc) ;
FreeProcInstance (lpfnAbortProc) ;
DeleteDC (hdcPrn) ;
return bError || bUserAbort ;
}
PRINT.RC
/*--------------------------
PRINT.RC resource script
--------------------------*/
#include <windows.h>
PrintDlgBox DIALOG 40, 40, 120, 40
STYLE WS_POPUP | WS_CAPTION | WS_SYSMENU | WS_VISIBLE
{
CTEXT "Cancel Printing", -1, 4, 6, 120, 12
DEFPUSHBUTTON "Cancel", IDCANCEL, 44, 22, 32, 14, WS_GROUP
}
PRINT3.DEF
;-----------------------------------
; PRINT3.DEF module definition file
;-----------------------------------
NAME PRINT3
DESCRIPTION 'Printing Program No. 3 (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
Two global variables are added to PRINT3: a BOOL called bUserAbort and a handle to the dialog box window called hDlgPrint. The PrintMyPage function initializes bUserAbort to FALSE, and as in PRINT2, the program's main window is disabled. PrintMyPage then calls MakeProcInstance for both AbortProc and PrintDlgProc. The pointer to AbortProc is used in the SETABORTPROC Escape call, and the pointer to PrintDlgProc is used in a CreateDialog call. The window handle returned from CreateDialog is saved in hDlgPrint.
The message loop in AbortProc now looks like this:
while (!bUserAbort && PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
{
if (!hDlgPrint || !IsDialogMessage (hDlgPrint, &msg))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
}
return !bUserAbort ;
It calls PeekMessage only if bUserAbort is FALSE, that is, if the user hasn't yet aborted the printing operation. The IsDialogMessage function is required to send the message to the modeless dialog box. As is normal with modeless dialog boxes, the handle to the dialog box window is checked before this call is made. AbortProc returns the inverse of bUserAbort. Initially, bUserAbort is FALSE, so AbortProc returns TRUE, indicating that printing is to continue. But bUserAbort could be set to TRUE in the printing dialog procedure.
The PrintDlgProc function is fairly simple. While processing WM_INITDIALOG, the function sets the window caption to the name of the program and disables the Close option on the system menu. If the user clicks the Cancel button, PrintDlgProc receives a WM_COMMAND message:
case WM_COMMAND :
bUserAbort = TRUE ;
EnableWindow (GetParent (hDlg), TRUE) ;
DestroyWindow (hDlg) ;
hDlgPrint = 0 ;
return TRUE ;
Setting bUserAbort to TRUE indicates that the user has decided to cancel the printing operation. The main window is enabled, and the dialog box is destroyed. (It is important that you perform these two actions in this order. Otherwise, some other program running under Windows will become the active program, and your program might disappear into the background.) As is normal, hDlgPrint is set to 0 to prevent IsDialogMessage from being called in the message loop.
The only time this dialog box receives messages is when AbortProc retrieves messages with PeekMessage and sends them to the dialog box window procedure with IsDialogMessage. The only time AbortProc is called is when the GDI module is processing the NEWFRAME Escape function. If GDI sees that the return value from AbortProc is FALSE, it returns control from the Escape call back to PrintMyPage. It doesn't return an error code. At that point, PrintMyPage thinks that the page is complete and calls the ENDDOC Escape function. Nothing is printed, however, because the GDI module didn't finish processing the NEWFRAME Escape call.
Some cleanup remains. If the user didn't cancel the print job from the dialog box, then the dialog box is still displayed. PrintMyPage reenables its main window and destroys the dialog box:
if (!bUserAbort)
{
EnableWindow (hwnd, TRUE) ;
DestroyWindow (hDlgPrint) ;
}
Two variables tell you what happened: bUserAbort tells you if the user aborted the print job, and bError tells you if an error occurred. You can do what you want with these variables. PrintMyPage simply performs a logical OR operation to return to WndProc:
return bError || bUserAbort ;