本人大三学生,自学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函数表示打印做业完成。windows
咱们知道,要使用打印机,必须首先获取打印机设备环境句柄,通常地,经过调用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绘制函数调用),还有在系统菜单中添加打印功能的菜单项,当用户点击打印菜单项,就会执行打印功能。咱们先放上应用程序窗口的代码例子:ide
#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函数。
先放代码上来吧,再进行分析:函数
#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按钮时,表明用户想要取消打印,因此程序就终止了打印操做。这个对话框常常被称为“终止对话框”,该对话框过程常常被称为“终止对话框过程”。如今,放上改进代码:ui
#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 ; }