本人大三学生, 自学 Windows 程序设计有两三个月了, 我是看鱼 C 工作室发布的 Windows 程序设计视频入门的, 这视频集数虽然不是特别多, 目前只有前面九章的视频内容, 但小甲鱼老师讲解书本内容十分详细, 入微, 能让我们学习到不少知识. 我开发 Win32 的环境是 VS2013.
一, 打印机工作机理
在 Windows 中使用打印机时, 实际上启动了一系列模块之间复杂的交互过程, 包括 GDI32 模块, 打印机设备驱动程序模块, Windows 后台打印处理程序和其他模块.
应用程序想要开始使用打印机, 首先调用 CreateDC 函数获取打印机设备环境句柄, 而该参数必须需要知道打印机设备名, 所以还需要先待用 EnumPrinters 函数获取打印机设备名. 注意, 当调用了 CreateDC 函数后, 参数相应的打印机设备驱动程序被载入内存. 应用程序再调用 StartDoc 函数开始新文档, 该函数由 GDI 模块处理, GDI 模块调用刚刚被调入内存中的打印机设备驱动程序中的 Control 函数, 告诉设备驱动程序做好打印准备. 然后调用 StartPage 函数开始新的一页, 以 EndPage 函数结束这一页, 注意, 在 StartPage 和 EndPage 函数之间调用 GDI 函数开始在页面绘制, 这时 GDI 模块就会先将这些 GDI 绘制函数存储到硬盘上的图元文件上. 好了, 调用完 EndPage 函数结束了这一页, 那么真正的打印工作开始了, 打印机设备驱动程序就会将硬盘上的图元文件转化成适用于打印机的输出, 具体怎么转换我们不关心. 接着, 这些转化后的打印机输出会被 GDI 模块存储到另一个临时文件中, 到这为止, 这一页的所有工作都完成了, 那就要进行下一页的打印了, 怎么告诉后台打印处理程序需要打印新的一页? GDI 模块会采用进程间调用告诉后台打印处理程序新的作业已就绪, 应用程序应该处理下一个页面了, 循环反复... 将所有页面都打印完后, 就可以调用 EndDoc 函数表示打印作业完成.
二, 获取打印机设备环境句柄
我们知道, 要使用打印机, 必须首先获取打印机设备环境句柄, 一般地, 通过调用 CreateDC 函数获取打印机设备环境句柄, 但是要注意的问题是, 该函数的参数 2 需要指定打印机设备的名称. 打印机设备的名称怎么来? 我们都知道, 一台计算机可以同时连接多台打印机, 而不管连接多少台打印机, 默认打印机只有一台, 默认打印机就是用户最近一次选用的打印机. 所以, 我们可以获取默认打印机设备的名称, 通过调用 EnumPrinters 函数获取默认打印机的名称, 再将该名称作为 CreateDC 函数的参数. 下面是完整的获取打印机设备环境句柄的代码例子:
- HDC GetPrinterDC(void)
- {
- DWORD dwNeeded, dwReturned;
- HDC hdc;
- PRINTER_INFO_4 * pinfo4;
- PRINTER_INFO_5 * pinfo5;
- if (GetVersion() & 0x80000000) // Windows 98
- {
- // 第一次调用该函数是为了得到所需的结构大小
EnumPrinters(PRINTER_ENUM_DEFAULT, NULL, 5, NULL,
- 0, &dwNeeded, &dwReturned);
- pinfo5 = (PRINTER_INFO_5 *)malloc(dwNeeded);
- // 第二次调用该函数才是真正填充该结构
EnumPrinters(PRINTER_ENUM_DEFAULT, NULL, 5, (PBYTE)pinfo5,
- dwNeeded, &dwNeeded, &dwReturned);
- // 将获取的结构里的 pPrinterName 成员作为 CreateDC 函数的参数
- hdc = CreateDC(NULL, pinfo5->pPrinterName, NULL, NULL);
- free(pinfo5);
- }
- else // Windows NT
- {
- // 下面同理
EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 4, NULL,
- 0, &dwNeeded, &dwReturned);
- pinfo4 = (PRINTER_INFO_4 *)malloc(dwNeeded);
EnumPrinters(PRINTER_ENUM_LOCAL, NULL, 4, (PBYTE)pinfo4,
- dwNeeded, &dwNeeded, &dwReturned);
- hdc = CreateDC(NULL, pinfo4->pPrinterName, NULL, NULL);
- free(pinfo4);
- }
- // 返回打印机设备环境句柄
- return hdc;
- }
三, 打印图形和文字
我们在上面的代码中辣么辛苦获取了打印机设备环境句柄, 那么我们该怎么使用它呢? 不急, 我们先放放. 我们先创建一个应用程序窗口, 在窗口的客户区显示我们将要打印的内容(GDI 绘制函数调用), 还有在系统菜单中添加打印功能的菜单项, 当用户点击打印菜单项, 就会执行打印功能. 我们先放上应用程序窗口的代码例子:
#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) ;// 沿 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:
- // 当用户点击打印菜单项, 那么就会执行 PrintMyPage 函数来进行打印, PrintMyPage 函数返回值是判断打印是否成功, 若失败则弹出一个错误对话框
- 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) ;
- //Look, 我们都知道当生成窗口时, 整个客户区都是无效的, 那么就会发射一条 WM_PAINT 消息, 接着就调用 PageGDICalls 函数, 在客户区绘制了需要打印的内容
- PageGDICalls (hdc, cxClient, cyClient) ;
- EndPaint (hwnd, &ps) ;
- return 0 ;
- case WM_CLOSE:
- if (IDOK == MessageBox(hwnd, TEXT("是否退出?"), TEXT("对话框"), MB_OKCANCEL | MB_DEFBUTTON1 | MB_ICONQUESTION))
- {
- DestroyWindow(hwnd);
- }
- else
- {
- return 0;
- }
- case WM_DESTROY:
- PostQuitMessage(0);
- return 0;
- }
- return DefWindowProc (hwnd, message, wParam, lParam) ;
- }
四, 打印功能的实现(即 PrintMyPage 函数的实现)
到了这里, 我们已经完成了大部分功能, 就差最后一个打印功能的函数了, 即 PrintMyPage 函数.
先放代码上来吧, 再进行分析:
- #include <windows.h>
- HDC GetPrinterDC (void) ;
- void PageGDICalls (HDC, int, int) ;
- HINSTANCE hInst ;
- TCHAR szAppName[] = TEXT ("Print1") ;// 定义全局变量, 在上一个文件中有引用
- TCHAR szCaption[] = TEXT ("Print Program 1") ;// 定义全局变量, 在上一个文件中有引用
- BOOL PrintMyPage (HWND hwnd)
- {
- //DOCINFO 结构, 第一个字段表明了该结构的大小, 第二个字段则是一个值为 TEXT ("Print1: Printing")的字符串
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) ;
- /*
- 只有 StartDoc,StartPage,EndPage 函数都成功时, 即返回值都大于 0 时, 才能够调用 EndDoc 结束文档
- */
- if (StartDoc (hdcPrn, &di)> 0)// 开始新文档
- {
- if (StartPage (hdcPrn)> 0)// 开始新的一页
- {
- //GDI 绘制命令, GDI 模块将 GDI 绘制命令存储在硬盘上的图元文件
- PageGDICalls (hdcPrn, xPage, yPage) ;
- if (EndPage (hdcPrn)> 0)// 在调用 EndPage 函数后, 打印机设备程序将图元文件转化为打印输出, 最后将打印输出存储为另一个临时文件
- EndDoc (hdcPrn) ;// 打印结束
- else
- bSuccess = FALSE ;
- }
- }
- else
- bSuccess = FALSE ;
- DeleteDC (hdcPrn) ;
- return bSuccess ;
- }
五, 用异常终止过程取消打印
好啦, 到目前为止, 全部功能基本实现了. 可出现了一个问题, 如果一个文档非常大, 用户想打印一页, 但不小心按错了, 变成打印几百页了, 那怎么终止打印呢? 所以, 当应用程序仍在打印时, 程序应为用户提供一个可取消打印作业的便利方法. 所以, 我们需要修改一下打印功能文件的代码. 如果需要取消一个打印作业, 那么就要调用一个 "异常终止过程", 它是一个函数哦. 程序员可以把这个函数的地址作为参数传给 SetAbortProc 函数(其实这个流程就是注册一个 "异常终止过程"), 每当打印时, 调用 EndPage 函数时, 就会调用 "异常终止过程" 来提前判断是否继续打印. 好的, 这里先上代码吧.
- #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)") ;
- // 添加的内容, 异常终止过程函数的定义, 即在调用 EndPage 函数时执行的函数
- BOOL CALLBACK AbortProc (HDC hdcPrn, int iCode)// 参数 1 是打印机设备环境句柄, 如果一切正常, 参数 2 为 0, 如果由于 GDI 模块生成临时打印输出文件导致磁盘空间不足, 参数 2 为 SP_OUTOFDISK
- {
- MSG msg ;
- // 看, 好像消息循环. 没错, 这里就是消息循环, 不过获取消息的函数是 PeedMessage 函数, 我们都知道若消息队列有等待处理的消息, 那么就返回 TRUE, 若没有消息, 则返回 FALSE. 我们能注意到, 无论该函数怎么处理, 最后始终是返回 TRUE, 说明打印作业可以继续, 那么貌似不能达到我们预期的效果(根据用户的操作, 手动取消打印), 后面我们会继续完善, 添加打印对话框实现用户与程序交互.
- while (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
- {
- TranslateMessage (&msg) ;
- DispatchMessage (&msg) ;
- }
- return TRUE ;
- }
- BOOL PrintMyPage (HWND hwnd)
- {
- static DOCINFO di = { sizeof (DOCINFO), TEXT ("Print2: Printing") } ;
- BOOL bSuccess = TRUE ;
- HDC hdcPrn ;
- short 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 ;
- }
六, 增加一个打印对话框(实现用户与程序交互)
我们知道上一个代码的改进存在问题, 首先它不直接显示它是否在打印以及打印何时结束, 只有当你用鼠标在程序上移动并发现程序没有反应时, 你才确定它还在处理 PrintMyPage 例程, 即还在打印过程中. 我们可以提供一个非模态对话框, 还有维护对话框过程. 当用户点击对话框的 Cancel 按钮时, 代表用户想要取消打印, 所以程序就终止了打印操作. 这个对话框经常被称为 "终止对话框", 该对话框过程经常被称为 "终止对话框过程". 现在, 放上改进代码:
- #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: // 按下取消按钮之后
- // 全局变量, TRUE 标识取消按钮按下
- bUserAbort = TRUE ;
- EnableWindow (GetParent (hDlg), TRUE) ; // 启动主窗口
- DestroyWindow (hDlg) ; // 关闭对话框
- hDlgPrint = NULL ; // 设定为 NULL, 防止在消息循环中呼叫 IsDialogMessage
- return TRUE ;
- }
- return FALSE ;
- }
- // 放弃处理程序, 用来停止打印
- BOOL CALLBACK AbortProc (HDC hdcPrn, int iCode)
- {
- MSG msg ;
- while (!bUserAbort && PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
- {
- // IsDialogMessage 函数用来将消息发送给非系统模态对话框
- if (!hDlgPrint || !IsDialogMessage (hDlgPrint, &msg))
- {
- TranslateMessage (&msg) ;
- DispatchMessage (&msg) ;
- }
- }
- // 返回 TRUE 标识继续打印
- return !bUserAbort ;
- }
- BOOL PrintMyPage (HWND hwnd)
- {
- static DOCINFO di = { sizeof (DOCINFO), TEXT ("Print3: 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) ;
- // 先设置用户取消状态为 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) ;
- // bUserAbort 可以告诉您使用者是否终止了打印作业
- // bSuccess 会告诉您是否出了故障
- return bSuccess && !bUserAbort ;
- }
Windows 程序设计核心总结(打印机 - 2018.5.5)
来源: http://www.bubuko.com/infodetail-2589187.html