標籤:Windows程式設計核心總結
本人大三學生,自學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.Cvoid PageGDICalls (HDC, int, int) ; // in PRINT.CHINSTANCE 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.Cvoid 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: // 按下取消按鈕之後 // 全域變數,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)