赞
踩
摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P487
从 Windows 程序中打印通常比 FORMFEED 程序所演示的要涉及更多的开销,而且会包含一些进行实际打印的 GDI 调用。现在我们要编写一个打印一页文字和图形的程序。我们将从 FORMFEED 程序中所示的方法开始,然后增加一些改进。我们将会研究这个程序的三个不同版本,分别为 PRINT1、PRINT2 和 PRINT3。为了避免很多重复的源代码,以上每个程序将使用早前所示的 GETPRNDC.C 文件以及 PRINT.C 文件中包含的函数。
- /*-------------------------------------------------------------
- PRINT.C -- Common routines for Print1, Print2, and Print3
- ---------------------------------------------------------------*/
-
- #include <windows.h>
-
- LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
- BOOL PrintMyPage(HWND);
-
- extern HINSTANCE hInst;
- extern TCHAR szAppName[];
- extern TCHAR szCaption[];
-
- int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
- PSTR szCmdLine, int iCmdShow)
- {
- HWND hwnd;
- MSG msg;
- WNDCLASS wndclass;
-
- wndclass.style = CS_HREDRAW | CS_VREDRAW;
- wndclass.lpfnWndProc = WndProc;
- wndclass.cbClsExtra = 0;
- wndclass.cbWndExtra = 0;
- wndclass.hInstance = hInstance;
- wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
- wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
- wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
- wndclass.lpszMenuName = NULL;
- wndclass.lpszClassName = szAppName;
-
- if (!RegisterClass(&wndclass))
- {
- MessageBox(NULL, TEXT("This program requires Windows NT!"),
- szAppName, MB_ICONERROR);
- return 0;
- }
-
- hInst = hInstance;
-
- hwnd = CreateWindow(szAppName, szCaption,
- WS_OVERLAPPEDWINDOW,
- CW_USEDEFAULT, CW_USEDEFAULT,
- CW_USEDEFAULT, CW_USEDEFAULT,
- NULL, NULL, hInstance, NULL);
-
- ShowWindow(hwnd, iCmdShow);
- UpdateWindow(hwnd);
-
- while (GetMessage(&msg, NULL, 0, 0))
- {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- return msg.wParam;
- }
-
- void PageGDICalls(HDC hdcPrn, int cxPage, int cyPage)
- {
- static TCHAR szTextStr[] = TEXT("Hello, Printer!");
-
- Rectangle(hdcPrn, 0, 0, cxPage, cyPage);
-
- MoveToEx(hdcPrn, 0, 0, NULL);
- LineTo(hdcPrn, cxPage, cyPage);
-
- MoveToEx(hdcPrn, cxPage, 0, NULL);
- LineTo(hdcPrn, 0, cyPage);
-
- SaveDC(hdcPrn);
-
- SetMapMode(hdcPrn, MM_ISOTROPIC);
- SetWindowExtEx(hdcPrn, 1000, 1000, NULL);
- SetViewportExtEx(hdcPrn, cxPage / 2, -cyPage / 2, NULL);
- SetViewportOrgEx(hdcPrn, cxPage / 2, cyPage / 2, NULL);
-
- Ellipse(hdcPrn, -500, 500, 500, -500);
-
- SetTextAlign(hdcPrn, TA_BASELINE | TA_CENTER);
- TextOut(hdcPrn, 0, 0, szTextStr, lstrlen(szTextStr));
- RestoreDC(hdcPrn, -1);
- }
-
- LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
- {
- static int cxClient, cyClient;
- HDC hdc;
- HMENU hMenu;
- PAINTSTRUCT ps;
-
- switch (message)
- {
- case WM_CREATE:
- hMenu = GetSystemMenu(hwnd, FALSE);
- AppendMenu(hMenu, MF_SEPARATOR, 0, NULL);
- AppendMenu(hMenu, 0, 1, TEXT("&Print"));
- return 0;
-
- case WM_SIZE:
- cxClient = LOWORD(lParam);
- cyClient = HIWORD(lParam);
- return 0;
-
- case WM_SYSCOMMAND:
- if (wParam == 1)
- {
- if (!PrintMyPage(hwnd))
- MessageBox(hwnd, TEXT("Could not print page!"),
- szAppName, MB_OK | MB_ICONEXCLAMATION);
- return 0;
- }
- break;
-
- case WM_PAINT:
- hdc = BeginPaint(hwnd, &ps);
-
- PageGDICalls(hdc, cxClient, cyClient);
-
- EndPaint(hwnd, &ps);
- return 0;
-
- case WM_DESTROY:
- PostQuitMessage(0);
- return 0;
- }
- return DefWindowProc(hwnd, message, wParam, lParam);
- }
PRINT.C 文件包含 WinMain 和 WndProc 函数,以及一个叫做 PageGDICalls 的函数。PageGDICalls 函数接受一个打印机设备环境的句柄和两个包含打印机页面宽度和高度的参数。PageGDICalls 在打印纸上画出一个包含整个页面的长方形,在页面上画两条对角线,在页面中间画一个椭圆(椭圆的直径是打印机宽度和高度中较小值的一半),并在椭圆中心画出“Hello, Printer!”。
在处理 WM_CREATE 消息时,WinProc 在系统菜单中加入了一个 Print 菜单项。选择这个菜单项会调用 PrintMyPage 函数。我们会在这个程序的三个版本中不断地改进这个函数。如果打印成功,PrintMyPage 函数会返回 TRUE,否则 PrintMyPage 函数返回 FALSE。如果 PrintMyPage 函数返回 FALSE,WinProc 会显示一个消息框告诉你相应的错误信息。
打印程序的第一个版本 PRINT1。编译 PRINT1 以后,先运行,然后从系统菜单中选择 Print。紧接着,GDI 会将打印输出存放在一个临时文件中,然后后台打印处理程序再它送给打印机。
- /*----------------------------------------
- PRINT1.C -- Bare Bones Printing
- (c) Charles Petzold, 1998
- ------------------------------------------*/
-
- #include <windows.h>
-
- HDC GetPrinterDC(void); // in GETPRNDC.C
- void PageGDICalls(HDC, int, int); // in PRINT.C
-
- HINSTANCE hInst;
- TCHAR szAppName[] = TEXT("Print1");
- TCHAR szCaption[] = TEXT("Print Program 1");
-
- BOOL PrintMyPage(HWND hwnd)
- {
- static DOCINFO di = { sizeof(DOCINFO), TEXT("Print1: Printing") };
- BOOL bSuccess = TRUE;
- HDC hdcPrn;
- int xPage, yPage;
-
- if (NULL == (hdcPrn = GetPrinterDC()))
- return FALSE;
-
- xPage = GetDeviceCaps(hdcPrn, HORZRES);
- yPage = GetDeviceCaps(hdcPrn, VERTRES);
-
- if (StartDoc(hdcPrn, &di) > 0)
- {
- if (StartPage(hdcPrn) > 0)
- {
- PageGDICalls(hdcPrn, xPage, yPage);
-
- if (EndPage(hdcPrn) > 0)
- EndDoc(hdcPrn);
- else
- bSuccess = FALSE;
- }
- }
- else
- bSuccess = FALSE;
-
- DeleteDC(hdcPrn);
- return bSuccess;
- }
让我们看一下 PRINT1.C 的代码。如果 PrintMyPage 函数拿不到打印机设备环境的句柄,它会返回 FALSE 并且 WinProc 显示一个消息框告诉你错误信息。如果 PrintMyPage 成功地拿到了打印机设备环境的句柄,它则调用 GetDeviceCaps 来确定打印纸的长度和宽度(以像素为单位):
- xPage = GetDeviceCaps(hdcPrn, HORZRES);
- yPage = GetDeviceCaps(hdcPrn, VERTRES);
这不是打印纸的实际尺寸而是打印纸的可打印区域的大小。调用 GetDeviceCaps 之后,PRINT1 程序中 PrintMyPage 函数的代码和 FORMFEED 的代码结构上基本一样。不同之处是 PRINT1 在 StartPage 和 EndPage 之间调用了 PageGDICalls。只有当 StartDoc、StartPage 和 EndPage 调用都成功后,PRINT1 才会调用 EndDoc 函数。
从应用程序中取消一个打印作业需要调用一个“异常终止过程”。异常终止过程是你程序的一个导出函数。你把这个函数的地址作为一个参数传给 SetAbortProc 函数,GDI 就会在打印过程中不停的调用这个过程,就好像在问“我还要继续打印吗?”
让我们先看一下如何能将异常终止过程加到打印逻辑中去,然后检查一下几种可能的结果。异常终止过程通常被命名为 AbortProc,它具有如下形式:
- BOOL CALLBACK AbortProc (HDC hdcPrn, int iCode)
- {
- [其他代码]
- }
在打印之前,必须调用 SetAbortProc 来注册异常终止过程:
SetAbortProc (hdcPrn, AbortProc);
你可在调用 StartDoc 函数之前调用 SetAbortProc。在打印结束后,你不需要去除异常终止过程。
在处理 EndPage 调用时(也就是把图元文件送进设备驱动程序和创建临时打印机输出文件时),GDI 频繁地调用异常终止过程。hdcPrn 参数是打印机设备环境的句柄。如果一切正常,iCode 参数为 0。如果由于 GDI 模块生成临时打印机输出文件导致磁盘空间不足,iCode 参数的值则为 SP_OUTOFDISK。
如果打印作业要持续,AbortProc 必须返回 TRUE(非零值)。如果打印作业将被终止,AbortProc 则返回 FALSE(值为 0)。异常终止过程可以简单到如下所示这样的程度:
- BOOL CALLBACK AbortProc (HDC hdcPrn, int iCode)
- {
- MSG msg;
-
- while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
- {
- TranslateMessage (&msg);
- DispatchMessage(&msg);
- }
- }
这个函数可能看起来有点奇特,实际上它看起来像是一个消息循环。消息循环在此处做些什么呢?它虽是一个消息循环,但你会注意到,这个消息循环调用的是 PeekMessage 而不是 GetMessage。我在第 5 章末尾的 RANDRECT 程序中介绍过 PeekMessage。你会想起来,不管程序的消息队列中是否有等待的消息,PeekMessage 都会将控制权返回给程序。
当 PeekMessage 返回 TRUE 时,AbortProc 函数中消息循环重复地调用 PeekMessage。TRUE 值意味着 PeekMessage 已经得到一个消息。该消息可以通过使用 TranslateMessage 和 DispatchMessage 被发送到程序的窗口过程。当程序的消息队列中没有消息时,PeekMessage 返回 FALSE,因此 AbortProc 把控制返回给 Windows。
在调用 EndPage 时,GDI 模块会调用你已设置的异常终止过程。一般情况下,iCode 参数是 0。但是如果由于其他还没有打印的临时文件导致 GDI 运行时磁盘空间不够,那么 iCode 参数则是 SP_OUTOFDISK。(通常不会检查这个值,但是如果你想这样做也可以。)然后异常终止过程进入它自己的 PeekMessage 循环以便从程序的消息队列中获取消息。
如果程序的消息队列中没有消息,PeekMessage 返回 FALSE。然后异常终止过程从消息循环中退出并且返回 TRUE 值给 GDI 模块以表示打印应该继续。GDI 模块则继续处理 EndPage 调用。
如果出错,GDI 模块则停止打印处理,因此异常终止过程的主要目的是允许用户取消打印。正因为如此,我们还需要一个显示 Cancel 按钮的对话框。让我们一步一步来。首先我们要在 PRINT2 程序中添加一个异常终止过程,然后在 PRINT3 程序中加入一个带 Cancel 按钮的对话框来使异常终止过程可用。
- BOOL CALLBACK AbortProc (HDC hdcPrn, int iCode)
- {
- MSG msg;
-
- while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
- {
- TranslateMessage (&msg);
- DispatchMessage(&msg);
- }
- }
要想实现打印,把指向异常终止过程的指针传给 Windows 即可:
SetAbortProc (hdcPrn, AbortProc);
你在调用 StartDoc 函数之前调用 SetAbortProc 就可以了。就这么简单。
等一等,我们忽视了 AbortProc 中 PeekMessage 循环的问题——这可是个大问题。AbortProc 只在程序打印中间被调用。如果你在 AbortProc 中得到一个消息并且把它发送给你自己的窗口过程,那么可能发生一些令人讨厌的事。用户可能从菜单中重新选择 Print,但是程序已经在打印例程的中间了。当程序视图打印以前的文件时,用户可能把新的文件加载到程序中。用户甚至可能退出程序。如果是这样,你程序的所有窗口都将被销毁。最终程序会从打印例程中返回,但是除了一个不再有效的窗口过程以外,它无处可去。
这件事令人困惑。这个程序显然没有考虑到这个问题。所以当你设置一个异常终止过程时,必须首先禁用程序的窗口,使其不能接收键盘和鼠标的输入。可以这样做:
EnableWindow (hwnd, FALSE);
这样就阻止了键盘和鼠标的输入进入消息队列。因此在打印时,用户不能对程序做任何事情。打印结束时,重新允许窗口接收输入即可:
EnableWindow (hwnd, TRUE);
那么你会问,既然键盘和鼠标消息不会进入消息队列,那为什么还要在 AbortProc 中调用 TranslateMessage 和 DispatchMessage 呢?严格地将,我们确实不需要 TranslateMesage(虽然我们几乎总是包括它)。但是考虑到会有 WM_PAINT 消息进入消息队列,因此我们必须使用 DispatchMessage。如果在窗口过程中 WM_PAINT 不能被 BeginPaint 和 EndPaint 正确处理,那么由于 PeekMessage 从不返回 FALSE,就会造成该消息被留在队列中并堵塞工作。
在打印时禁用窗口,程序不会进行显示输出。但是用户可以切换到另一个程序并在那个程序中做一些工作。后台打印处理程序也可以继续把输出文件送到打印机上。
PRINT2 程序把异常终止过程及其必要的支持(一个 AbortProc 函数调用和两个 EnableWindow 调用)加入到了 PRINT1 中。第一个 EnableWindow 用来禁止使用窗口,第二个 EnableWindow 用来重新启用窗口。
- /*----------------------------------------------
- PRINT2.C -- Printing with Abort Procedure
- (c) Charles Petzold, 1998
- -----------------------------------------------*/
- #include <windows.h>
-
- HDC GetPrinterDC(void); // in GETPRNDC.C
- void PageGDICalls(HDC, int, int); // in PRINT.C
-
- HINSTANCE hInst;
- TCHAR szAppName[] = TEXT("Print2");
- TCHAR szCaption[] = TEXT("Print Program 2 (Abort Procedure)");
-
- BOOL CALLBACK AbortProc(HDC hdcPrn, int iCode)
- {
- MSG msg;
-
- while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
- {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- return TRUE;
- }
-
- BOOL PrintMyPage(HWND hwnd)
- {
- static DOCINFO di = { sizeof(DOCINFO), TEXT("Print1: Printing") };
- BOOL bSuccess = TRUE;
- HDC hdcPrn;
- int xPage, yPage;
-
- if (NULL == (hdcPrn = GetPrinterDC()))
- return FALSE;
-
- xPage = GetDeviceCaps(hdcPrn, HORZRES);
- yPage = GetDeviceCaps(hdcPrn, VERTRES);
-
- EnableWindow(hwnd, FALSE);
-
- SetAbortProc(hdcPrn, AbortProc);
-
- if (StartDoc(hdcPrn, &di) > 0)
- {
- if (StartPage(hdcPrn) > 0)
- {
- PageGDICalls(hdcPrn, xPage, yPage);
-
- if (EndPage(hdcPrn) > 0)
- EndDoc(hdcPrn);
- else
- bSuccess = FALSE;
- }
- }
- else
- bSuccess = FALSE;
-
- EnableWindow(hwnd, TRUE);
- DeleteDC(hdcPrn);
- return bSuccess;
- }
PRINT2 程序并不很完美。首先它不直接显示它是否在打印以及打印何时结束。只有当你用鼠标在程序上移动并发现程序没有反应时,你才确定它还在处理 PrintMyPage 例程。其次 PRINT2 在后台处理时没有提供用户取消打印作业的机会。
你可能知道大部分 Windows 程序允许用户取消正在进行中的打印操作。屏幕会出现一个包含一些文字和一个 Cancel 按钮的对话框。在 GDI 把打印输出保存到磁盘文件中或者打印机正在打印的整个过程中(如果后台处理程序被禁用),程序会一直显示这个对话框。这是一个非模态对话框,必须实现一个对话框过程。
这个对话框经常被称为“终止对话框”。该对话框过程经常被称为“终止对话框过程”。为了更好地与“异常终止过程”区分,我把这个对话框过程称为“打印对话框过程”。异常终止过程(以 AbortProc 命名)和打印对话框过程(我将它命名为 PrintDlgProc)是两个不同的导出函数。如果想用专业的 Windows 方式打印,必须同时使用这两个函数。
这两个函数通过以下方式互动。必须修改 AbortProc 中的 PeekMessage 循环把非模态对话框的消息发送给对话框窗口过程。PrintDlgProc 必须处理 WM_COMMAND 消息来确定 Cancel 按钮的状态。如果用户按了 Cancel 按钮,全局变量 bUserAbort 被设置为 TRUE。AbortProc 返回值和 bUserAbort 的值相反。当 AbortProc 返回 TRUE 时则继续打印。当 AbortProc 返回 FALSE 时则终止打印。PRINT2 程序总是返回 TRUE 的,而现在,如果用户单击打印对话框上的 Cancel 按钮,我们将返回 FALSE。PRINT3 程序实现了这个逻辑。
- /*----------------------------------------------
- PRINT3.C -- Printing with Dialog Box
- (c) Charles Petzold, 1998
- -----------------------------------------------*/
- #include <windows.h>
-
- HDC GetPrinterDC(void); // in GETPRNDC.C
- void PageGDICalls(HDC, int, int); // in PRINT.C
-
- HINSTANCE hInst;
- TCHAR szAppName[] = TEXT("Print3");
- TCHAR szCaption[] = TEXT("Print Program 3 (Dialog Box)");
-
- BOOL bUserAbort;
- HWND hDlgPrint;
-
- BOOL CALLBACK PrintDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM 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 = NULL;
- return TRUE;
- }
- return FALSE;
- }
-
- BOOL CALLBACK AbortProc(HDC hdcPrn, int iCode)
- {
- 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 DOCINFO di = { sizeof(DOCINFO), TEXT("Print1: Printing") };
- BOOL bSuccess = TRUE;
- HDC hdcPrn;
- int xPage, yPage;
-
- if (NULL == (hdcPrn = GetPrinterDC()))
- return FALSE;
-
- xPage = GetDeviceCaps(hdcPrn, HORZRES);
- yPage = GetDeviceCaps(hdcPrn, VERTRES);
-
- EnableWindow(hwnd, FALSE);
-
- bUserAbort = FALSE;
- hDlgPrint = CreateDialog(hInst, TEXT("PrintDlgBox"), hwnd, PrintDlgProc);
-
- SetAbortProc(hdcPrn, AbortProc);
-
- if (StartDoc(hdcPrn, &di) > 0)
- {
- if (StartPage(hdcPrn) > 0)
- {
- PageGDICalls(hdcPrn, xPage, yPage);
-
- if (EndPage(hdcPrn) > 0)
- EndDoc(hdcPrn);
- else
- bSuccess = FALSE;
- }
- }
- else
- bSuccess = FALSE;
-
- if (!bUserAbort)
- {
- EnableWindow(hwnd, TRUE);
- DestroyWindow(hDlgPrint);
- }
-
- DeleteDC(hdcPrn);
- return bSuccess && !bUserAbort;
- }
- PRINT.RC (excerpts)
-
- // Microsoft Visual C++ generated resource script.
- //
- #include "resource.h"
-
- /
- //
- // Dialog
- //
-
- PRINTDLGBOX DIALOGEX 20, 20, 186, 63
- STYLE DS_SETFONT | DS_MODALFRAME | WS_VISIBLE | WS_POPUP | WS_CAPTION | WS_SYSMENU
- FONT 8, "MS Sans Serif", 400, 0, 0x1
- BEGIN
- DEFPUSHBUTTON "Cancel",IDCANCEL, 67, 42, 50, 14
- CTEXT "Cancel Printing", IDC_STATIC, 7, 21, 172, 8
- END
在使用程序 PRINT3 时,你可能要暂时禁用后台打印处理。否则 Cancel 按钮(只有在后台处理程序从 PRINT3 中手机数据时才可见)在你单击它之前就会快速消失。当你单击 Cancel 按钮,如果没有马上终止打印(特别是慢的打印机),你也不必惊讶。因为在停止打印前,打印机必须清空它的内部缓冲区。而单击 Cancel 按钮只是告诉 GDI 不要再把数据送到打印机的内部缓冲区去。
我们在 PRINT3 程序中增加了两个全局变量:一个 BOOL 类型的 bUserAbort 和一个叫作 hDlgPrint 的对话框窗口的句柄。PrintMyPage 函数把 bUserAbort 初始化为 FALSE,并且像在 PRINT2 中一样,程序的主窗口被禁止使用。指向 AbortProc 的指针在调用 SetAbortProc 时使用。指向 PrintDlgProc 的指针在调用 CreateDialog 时被使用。hDlgPrint 变量用来保存由 CreateDialog 函数返回的窗口句柄。
现在 AbortProc 中的消息循环如下所示:
- while (!bUserAbort && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
- {
- if (!hDlgPrint || !IsDialogMessage(hDlgPrint, &msg))
- {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- }
- return !bUserAbort;
这里只在 bUserAbort 是 FALSE 时才调用 PeekMessage,也就是说只在没有终止打印操作时。把消息发送给非模态对话框时必须调用 IsDialogMessage 函数。
和普通的非模态对话框一样,在调用之前要检查对话框窗口句柄。AbortProc 返回 bUserAbort 的反数。因为 bUserAbort 的初始值是 FALSE,所以 AbortProc 返回 TRUE,这表明打印将继续。但是 bUserAbort 在打印对话框过程中可随时被设置为 TRUE。
PrintDlgProc 函数非常简单。当它在处理 WM_INITDIALOG 时,它把窗口标题设置为程序的名称并且禁用了系统菜单上的 Close 按钮。如果用户单击 Cancel 按钮,PrintDlgProc 函数就会收到一个 WM_COMMAND 消息:
- case WM_COMMAND:
- bUserAbort = TRUE;
- EnableWindow(GetParent(hDlg), TRUE);
- DestroyWindow(hDlg);
- hDlgPrint = NULL;
- return TRUE;
当 bUserAbort 值为 TRUE,表明用户已经决定终止打印操作。主窗口被重新启用并且对话框被销毁。(这两个操作的执行顺序很重要。否则在 Windows 下运行的一些其他程序会成为活动程序,并且你的程序可能会消失在背景中。)正常情况下,hDlgPrint 被设成 NULL 以防止 IsDialogMessage 在消息循环中被调用。
只有当 AbortProc 使用 PeekMessage 接受消息并且使用 IsDialogMessage 把它们发送给对话框窗口过程时,该对话框才接受消息。只有当 GDI 模块在处理 EndPage 函数时,AbortProc 才被调用。如果 GDI 发现 AbortProc的返回值是 FALSE,它就把控制从 EndPage 调用返回给 PrintMyPage。它并不返回错误代码。此时 PrintMyPage 认为页面已完成,然后调用 EndDoc 函数。然而因为 GDI 模块没有处理完 EndPage 调用,所以它没有打印任何东西。
还需要作一些清理工作。如果用户没有从对话框中取消打印作业,那么对话框仍被显示。PrintMyPage 需要重新激活主窗口并且清除该对话框。
- if (!bUserAbort)
- {
- EnableWindow(hwnd, TRUE);
- DestroyWindow(hDlgPrint);
- }
有两个变量告诉你发生了什么事:bUserAbort 告诉你用户是否终止了打印作业,bSuccess 告诉你是否出了错。你可以用这两个变量做任何操作。这里 PrintMyPage 只是简单地对它们执行一个逻辑与操作(AND)并把返回值传给 WndProc 函数:
return bSuccess && !bUserAbort;
现在我们可以把打印设备加到 POPPAD 系列程序中来最终完成 POPPAD 程序。你将需要第 11 章的 POPPAD 文件和本章如下的 POPPRINT.C 文件。
- /*--------------------------------------------------
- POPPRNT.C -- Popup Editor Printing Functions
- --------------------------------------------------*/
-
- #include <windows.h>
- #include <commdlg.h>
- #include "resource.h"
-
- BOOL bUserAbort;
- HWND hDlgPrint;
-
- BOOL CALLBACK PrintDlgProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
- {
- switch (message)
- {
- case WM_INITDIALOG:
- EnableMenuItem(GetSystemMenu(hDlg, FALSE), SC_CLOSE, MF_GRAYED);
- return TRUE;
-
- case WM_COMMAND:
- bUserAbort = TRUE;
- EnableWindow(GetParent(hDlg), TRUE);
- DestroyWindow(hDlg);
- hDlgPrint = NULL;
- return TRUE;
- }
- return FALSE;
- }
-
- BOOL CALLBACK AbortProc(HDC hPrinterDC, int iCode)
- {
- MSG msg;
-
- while (!bUserAbort && PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
- {
- if (!hDlgPrint || !IsDialogMessage(hDlgPrint, &msg))
- {
- TranslateMessage(&msg);
- DispatchMessage(&msg);
- }
- }
- return !bUserAbort;
- }
-
- BOOL PopPrntPrintFile(HINSTANCE hInst, HWND hwnd, HWND hwndEdit, PTSTR szTitleName)
- {
- static DOCINFO di = { sizeof(DOCINFO) };
- static PRINTDLG pd;
- BOOL bSuccess;
- int yChar, iCharsPerLine, iLinesPerPage, iTotalLines,
- iTotalPages, iPage, iLine, iLineNum;
- PTSTR pstrBuffer;
- TCHAR szJobName[64 + MAX_PATH];
- TEXTMETRIC tm;
- WORD iColCopy, iNoiColCopy;
-
- // Invoke Print common dialog box
- pd.lStructSize = sizeof(PRINTDLG);
- pd.hwndOwner = hwnd;
- pd.hDevMode = NULL;
- pd.hDevNames = NULL;
- pd.hDC = NULL;
- pd.Flags = PD_ALLPAGES | PD_COLLATE |
- PD_RETURNDC | PD_NOSELECTION;
-
- pd.nFromPage = 0;
- pd.nToPage = 0;
- pd.nMinPage = 0;
- pd.nMaxPage = 0;
- pd.nCopies = 1;
- pd.hInstance = 0L;
- pd.lpfnPrintHook = NULL;
- pd.lpfnSetupHook = NULL;
- pd.lpPrintTemplateName = NULL;
- pd.lpSetupTemplateName = NULL;
- pd.hPrintTemplate = NULL;
- pd.hSetupTemplate = NULL;
-
- if (!PrintDlg(&pd))
- return TRUE;
-
- if (0 == (iTotalLines = SendMessage(hwndEdit, EM_GETLINECOUNT, 0, 0)))
- return TRUE;
-
- // Calculate necessary metrics for file
-
- GetTextMetrics(pd.hDC, &tm);
- yChar = tm.tmHeight + tm.tmExternalLeading;
-
- iCharsPerLine = GetDeviceCaps(pd.hDC, HORZRES) / tm.tmAveCharWidth;
- iLinesPerPage = GetDeviceCaps(pd.hDC, VERTRES) / yChar;
- iTotalPages = (iTotalLines + iLinesPerPage - 1) / iLinesPerPage;
-
- // Allocate a buffer for each line of text
-
- pstrBuffer = (PSTR)malloc(sizeof(TCHAR) * (iCharsPerLine + 1));
-
- // Display the printing dialog box
-
- EnableWindow(hwnd, FALSE);
-
- bSuccess = TRUE;
- bUserAbort = FALSE;
-
- hDlgPrint = CreateDialog(hInst, TEXT("PrintDlgBox"), hwnd, PrintDlgProc);
-
- SetDlgItemText(hDlgPrint, IDC_FILENAME, szTitleName);
- SetAbortProc(pd.hDC, AbortProc);
-
- // Start the document
-
- GetWindowText(hwnd, szJobName, sizeof(szJobName));
- di.lpszDocName = szJobName;
-
- if (StartDoc(pd.hDC, &di) > 0)
- {
- // Collation requires this loop and iNoiColCopy
-
- for (iColCopy = 0;
- iColCopy < ((WORD)pd.Flags & PD_COLLATE ? pd.nCopies : 1);
- iColCopy++)
- {
- for (iPage = 0; iPage < iTotalPages; iPage++)
- {
- for (iNoiColCopy = 0;
- iNoiColCopy < (pd.Flags & PD_COLLATE ? 1 : pd.nCopies);
- iNoiColCopy++)
- {
- // Start the page
-
- if (StartPage(pd.hDC) < 0)
- {
- bSuccess = FALSE;
- break;
- }
-
- // For each page, print the lines
-
- for (iLine = 0; iLine < iLinesPerPage; iLine++)
- {
- iLineNum = iLinesPerPage * iPage + iLine;
-
- if (iLineNum > iTotalLines)
- break;
-
- *(int *)pstrBuffer = iCharsPerLine;
-
- TextOut(pd.hDC, 0, yChar * iLine, pstrBuffer,
- (int)SendMessage(hwndEdit, EM_GETLINE,
- (WPARAM)iLineNum, (LPARAM)pstrBuffer));
- }
-
- if (EndPage(pd.hDC) < 0)
- {
- bSuccess = FALSE;
- break;
- }
-
- if (bUserAbort)
- break;
- }
-
- if (!bSuccess || bUserAbort)
- break;
- }
-
- if (!bSuccess || bUserAbort)
- break;
- }
- }
- else
- bSuccess = FALSE;
-
- if (bSuccess)
- EndDoc(pd.hDC);
-
- if (!bUserAbort)
- {
- EnableWindow(hwnd, TRUE);
- DestroyWindow(hDlgPrint);
- }
-
- free(pstrBuffer);
- DeleteDC(pd.hDC);
-
- return bSuccess && !bUserAbort;
- }
为了使 POPPAD 尽可能简单地利用 Windows 的高层功能,POPPRNT.C 文件演示了如何使用 PrintDlg 函数。这个函数被包含在通用对话框库中并且使用一个 PRINTDLG 类型的结构。
通常 Print 选项被包含在程序的 File 菜单上。当用户选择 Print 菜单项时,程序把 PRINTDLG 的字段初始化并且调用 PrintDlg。
PrintDlg 会显示一个允许用户选择打印页面范围的对话框。因此,该对话框特别适用于像 POPPAD 一样可打印多页文档的程序。该对话框也提供一个可以指定打印份数的编辑字段和一个名为 Collate(逐份打印)的复选框。逐份打印影响多份副本的页面顺序。例如,如果文档有三页长并且用户要求打印三份,程序可以用两种顺序中的任何一种来打印。逐份是以页面顺序 1, 2, 3, 1, 2, 3, 1, 2, 3 打印。非逐份打印则是以页面顺序 1, 1, 1, 2, 2, 2, 3, 3, 3 打印。应用程序应负责以正确的顺序打印副本。
对话框还允许用户选择一个非默认打印机,并且包含一个标为 Properties 的按钮。该按钮可以激活设备模式对话框。最低限度上,它允许用户选择使用纵向或横向模式来打印。
从 PrintDlg 函数返回时,PRINTDLG 结构的字段指明了要打印的页面范围以及多份拷贝页的排序顺序。该结构也提供了可供使用的打印机设备环境的句柄。
在 POPPRNT.C 中,PopPrntPrintFile 函数(当用户从 File 菜单中选择 Print 选项时被 POPPAD 调用)调用 PrintDlg 然后着手打印文件。PopPrntPrintFile 会进行一些计算来确定每行可打印的字符数和每个页面可打印的行数。该处理涉及调用 GetDeviceCaps 来确定页面的分辨率和调用 GetTextMetrics 来确定字符的尺寸。
通过发送 EM_GETLINECOUNT 消息给编辑控件来获得文档的总行数(变量 iTotalLines)。在本地内存中分配了一个缓冲区来保存每一行的内容。该缓冲区内的每一行的第一个字被设置为该行包含的字符总数。通过给编辑控件发送一个 EM_GETLINE 消息可以把一行复制到缓冲区,然后调用 TextOut 函数把该行发送给打印机设备环境。(POPPRNT.C 还没有聪明到可以把超出打印页面宽度的行进行换行。我们将在第 17 章中研究换行技术。)
注意,打印文档逻辑对分本数量包含两个 for 循环。第一个循环使用了一个叫 iColCopy 的变量并且在用户指定逐份打印副本时生效。第二个循环使用了一个叫 iNonColCopy 的变量并且在用户指定非逐份打印副本时生效。
如果 StartPage 或 EndPage 函数返回一个错误或者 bUserAbort 是 TRUE,那么程序从递增页面数的 for 循环中退出。如果异常终止过程的返回值是 FALSE,EndPage 不返回错误。由于这个原因,所以在下一页开始前,我们会明确地检测 bUserAbort。如果没有发现错误,就调用 EndDoc:
- if (bSuccess)
- EndDoc(pd.hDC);
你或许想通过打印一个多页文件来试用 POPPAD。你可以从打印作业窗口中监控打印进度。当 GDI 处理完第一个 EndPage 调用之后,正在被打印的文件首先出现在这个窗口中。那时后台处理程序开始把文件送给打印机。如果你从 POPPAD 中取消打印作业,后台处理程序也同时终止打印——那是从异常终止过程中返回 FALSE 的结果。一旦文件出现在打印作业窗口中,你也可以从【文档】菜单中选择【取消打印】来取消打印作业。在这种情况下,在 POPPAD 中正在进行的 EndPage 调用会返回一个错误值。
Windows 编程新手经常过度沉迷于 AbortDoc 函数。这个函数很少在打印时被使用。从 POPPAD 中可发现,用户几乎可在任何时候通过 POPPAD 的打印对话框或者打印作业窗口来取消打印作业。这两种方式都不需要使用 AbortDoc 函数。AbortDoc 函数被允许在 POPPAD 中使用的时间仅仅是在调用 StartDoc 和第一次调用 EndPage 之间,但是那段代码运行得太快以至于 AbortDoc 函数变得不再必要。
图 13-11 显示了打印一个多页文档时打印函数的正确调用顺序。检查 bUserAbort 值为 TRUE 的最佳位置是在每个 EndPage 调用之后。EndDoc 函数只在上一个打印函数没有出错时才被使用。实际上一旦你从任何一个打印函数调用中得到一个错误,打印就已经结束了。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。