本人大三學生,自學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 ; }