摘录于《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 会显示一个消息框告诉你相应的错误信息。

13.2.1  打印精华

打印程序的第一个版本 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.CHINSTANCE 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);elsebSuccess = FALSE;}}elsebSuccess = 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 函数。

13.2.2  用异常终止过程取消打印

对于较大的文档,当应用程序仍在打印时,程序应为用户提供一个可取消打印作业的便利方法。用户可能仅想打印文档的某一页却选择了打印所有的 537 页。这个错误应该在全部 537 页被打印出来前就可以改正。

从应用程序中取消一个打印作业需要调用一个“异常终止过程”。异常终止过程是你程序的一个导出函数。你把这个函数的地址作为一个参数传给 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。

13.2.3  Windows 如何使用 AbortProc

当一个程序在打印时,大量工作发生在调用 EndPage 的过程中。在调用 EndPage 之前,程序每次调用一个 GDI 绘图函数时,GDI 模块仅仅把另一个记录加到磁盘上的图元文件中。当 GDI 调用 EndPage 时,对设备驱动程序在一个页面上定义的每条带,它都把该图元文件送给设备驱动程序。然后 GDI 把打印机驱动程序创建的打印机输出存储到一个文件中。 如果后台打印处理程序不在运行,GDI 模块本身必须把打印机输出写到打印机上。

在调用 EndPage 时,GDI 模块会调用你已设置的异常终止过程。一般情况下,iCode 参数是 0。但是如果由于其他还没有打印的临时文件导致 GDI 运行时磁盘空间不够,那么 iCode 参数则是 SP_OUTOFDISK。(通常不会检查这个值,但是如果你想这样做也可以。)然后异常终止过程进入它自己的 PeekMessage 循环以便从程序的消息队列中获取消息。

如果程序的消息队列中没有消息,PeekMessage 返回 FALSE。然后异常终止过程从消息循环中退出并且返回 TRUE 值给 GDI 模块以表示打印应该继续。GDI 模块则继续处理 EndPage 调用。

如果出错,GDI 模块则停止打印处理,因此异常终止过程的主要目的是允许用户取消打印。正因为如此,我们还需要一个显示 Cancel 按钮的对话框。让我们一步一步来。首先我们要在 PRINT2 程序中添加一个异常终止过程,然后在 PRINT3 程序中加入一个带 Cancel 按钮的对话框来使异常终止过程可用。

13.2.4  实现异常终止过程

让我们快速地复习一下异常终止过程的机制。你可以如下定义一个异常终止过程:

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.CHINSTANCE 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);elsebSuccess = FALSE;}}elsebSuccess = FALSE;EnableWindow(hwnd, TRUE);DeleteDC(hdcPrn);return bSuccess;
}

13.2.5  增加一个打印对话框

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.CHINSTANCE 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);elsebSuccess = FALSE;}}elsebSuccess = 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
BEGINDEFPUSHBUTTON      "Cancel",IDCANCEL, 67, 42, 50, 14CTEXT            "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;

13.2.6  增加打印功能到 POPPAD

现在我们可以把打印设备加到 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 boxpd.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 fileGetTextMetrics(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 textpstrBuffer = (PSTR)malloc(sizeof(TCHAR) * (iCharsPerLine + 1));// Display the printing dialog boxEnableWindow(hwnd, FALSE);bSuccess = TRUE;bUserAbort = FALSE;hDlgPrint = CreateDialog(hInst, TEXT("PrintDlgBox"), hwnd, PrintDlgProc);SetDlgItemText(hDlgPrint, IDC_FILENAME, szTitleName);SetAbortProc(pd.hDC, AbortProc);// Start the documentGetWindowText(hwnd, szJobName, sizeof(szJobName));di.lpszDocName = szJobName;if (StartDoc(pd.hDC, &di) > 0){// Collation requires this loop and iNoiColCopyfor (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 pageif (StartPage(pd.hDC) < 0){bSuccess = FALSE;break;}// For each page, print the linesfor (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;}}elsebSuccess = 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 函数只在上一个打印函数没有出错时才被使用。实际上一旦你从任何一个打印函数调用中得到一个错误,打印就已经结束了。

13.2 打印图形和文字相关推荐

  1. 串口 驱动 热敏打印机_热敏纸标签打印不出来文字 是因为……

    点击↑箭头处"蓝色字",关注我们哦!! Hello,各位纸友们好呀! 我是小冠~ 随着生活的需要,热敏纸标签在生活中也成了必不可少一个部分!那么,为什么有的热敏纸标签有的时候会打印 ...

  2. 使用计算机打印文字,Word打印出的文字与电脑上的显示不同怎么办

    Word是一款功能强大的文字处理系统,需要熟练地掌握和巧妙使用Word的各种技巧,才能达到事半功倍的效果;但是有时候,Word打印出的文字与电脑上的显示不同,这是怎么回事?下面小编来告诉你吧. Wor ...

  3. (蓝桥杯第五届B组)史丰收速算 打印图形(代码填空)

    史丰收速算 史丰收速算法的革命性贡献是:从高位算起,预测进位.不需要九九表,彻底颠覆了传统手算! 速算的核心基础是:1位数乘以多位数的乘法. 其中,乘以7是最复杂的,就以它为例. 因为,1/7 是个循 ...

  4. c语言如何打印矩形图形的程序 五行七列,C语言程序计 第二讲.printf打印图形.转义字符.格式声明符.doc...

    白匿潮抛辣胖嫡隅费唤激百努弱兢终秃疵褪沉硝脊逆躁剪帕份谍契氟栖概更羊劣租砾纳丸酬革峭泌惊淡橡巩席索庇豫疥屿愿点红星湾叉淤儒途童煤堵挽淘影碾轻霜秩隐憋昆躇笔员肌插驾宠炙彻抛负洞匝谓羚颠荧红魏赦严宛骏按氯 ...

  5. 经典算法_01 打印六芒星、打印图形练习

    距离蓝桥杯倒计时十天,猿猿的蓝桥杯突击之旅.突击一: 题目 打印图形 时间限制: 1.0s 内存限制: 512.0MB [问题描述] 小明刚学习完条件语句和循环语句,并且也打印了许多图形,比如菱形或者 ...

  6. java图形界面文字输出_java图形化Swing教程(一)

    与多线程.泛型等不同,Swing主要在于使用. 下面主要放代码和注释,少说话. (一)基本框架package Swing;import java.awt.*;import javax.swing.*; ...

  7. Redhat7开机图形或文字界面

    Redhat7开机图形或文字界面 查看开机图形或文字: # systemctl get-default 设置开机图形界面: # systemctl set-default graphical.targ ...

  8. Python 技术篇-如何打印一段文字,用友云霸气控制台颜文字打印

    打印一段文字只要前后三个点包起来就行. 我主要想是展示一下用友云的颜文字收藏!哈哈,有意思吧! print( ''' \ \ / / \ \ / / / ____| | | |\ \_/ /__ _ ...

  9. C++扬帆远航——3(打印图形)

    /** Copyright (c) 2016,烟台大学计算机与控制工程学院* All rights reserved.* 文件名:tuxing.cpp* 作者:常轩* 完成日期:2016年3月15日* ...

最新文章

  1. RESTful之分页Pagination
  2. 对数据进行插入操作并且获取主键的值
  3. Honeycomb——BFS
  4. nas存储如何做远程服务器数据备份_备份数据?7 个理由告诉你为什么要用 NAS,而不用移动硬盘...
  5. Geatpy框架使用基于NSGA-II算法的多染色体多目标进化算法案例(moea_psy_NSGA2_templet)
  6. SP2-0750: You may need to set ORACLE_HOME to your Oracle software directory
  7. 北理乐学c语言,北京理工大学2018年计算机考研889数据结构考试大纲
  8. android时间显示秒,MIUI 12桌面如何显示带秒时钟?
  9. 软件工程导论复习之可行性研究
  10. [转]如何实现按键精灵的简单路点行走
  11. Typora DIY 主题背景,以及透明pre代码块。
  12. 天嵌IMX6核心板竞品分析(启杨IMX6)
  13. SpringBoot项目网页加载出现Whitelabel Error Page
  14. 微信扫码登陆在chrome浏览器被拦截
  15. 瀚高CEO苗健:用开源软件改变中国基础软件产业格局
  16. OutLook中添加、取消送信者禁止
  17. abs在c 语言中的作用是什么,abs()函数以及C ++中的示例
  18. aix kill java_AIX环境Java进程cpu瓶颈分析(转)
  19. MyBatis 缓存原理解析
  20. ASP.NET画文字验证码

热门文章

  1. 医咖会免费SPSS教程学习笔记—多重线性回归
  2. 从Sliverlight Alpha(May2007)到Sliverlight Alpha Refresh之间变化
  3. 国元证券分析报告(0611)
  4. 485转RS232数据线及RJ45线制作方法
  5. 年轻人的第一本深度学习面试书
  6. katalon 测试app_Katalon Studio一款免费的自动化测试工具
  7. vue获取dom元素的内容
  8. 服务器连交换机配置lacp协议,华为S5700系列交换机配置链路聚合LACP报错。
  9. 【竞赛经历】CSDN第39期竞赛题解
  10. IC的Strap Pin