深入Windows窗體原理及控件重繪技巧


之前有學MFC的同學告訴我覺得Windows的控件重繪難以理解,就算重繪成功了還是有些地方不明白,我覺得可能很多人都有這樣的問題,在這里我從Windows窗體的最基本原理來講解,如果你有類似的疑惑希望這篇文章可以幫你解惑。

1.Windows窗體原理

 

首先,如果看過Win32 SDK編程的都知道Windows的三大核心系統:負責窗口對象產生和消息分發的USER模塊,負責圖像顯示繪制的GDI模塊,負責內存、進程、IO管理的KERNEL模塊。試想象一下如何在一個像素陣列上產生窗口對象,其實就是使用GDI繪制窗口,不停的以一定的頻率刷新顯示在屏幕上,這就是圖形界面,如果由在DOS或Windows DOS模擬器下編寫圖形界面的經驗這個比較好理解。所以說其實USER模塊中的窗口產生是依靠GDI模塊的(包括菜單、滾動條等都是使用GDI來繪制的)。

那么,下面我們就從USER模塊和GDI模塊來說說Windows 的窗體原理。

如果接觸過Win32 SDK編程的知道一個標准Windows窗體的產生過程:設計窗口類、注冊窗口類、創建窗口、顯示窗口、啟動消息循環泵循環獲取消息分發到窗體過程函數處理。為了保證博客的連貫性,在這里我貼上一個標准Windows窗體的產生代碼。

 

[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. #include <windows.h>  
  2.   
  3. LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);  
  4.   
  5. int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)  
  6. {  
  7.     static TCHAR szAppName[] = TEXT ("窗口類名稱");  
  8.     HWND         hwnd;  
  9.     MSG          msg;  
  10.     WNDCLASSEX   wndclassex = {0};  
  11.   
  12.     //設計窗口類  
  13.     wndclassex.cbSize        = sizeof(WNDCLASSEX);  
  14.     wndclassex.style         = CS_HREDRAW | CS_VREDRAW;  
  15.     wndclassex.lpfnWndProc   = WndProc;  
  16.     wndclassex.cbClsExtra    = 0;  
  17.     wndclassex.cbWndExtra    = 0;  
  18.     wndclassex.hInstance     = hInstance;  
  19.     wndclassex.hIcon         = LoadIcon (NULL, IDI_APPLICATION);  
  20.     wndclassex.hCursor       = LoadCursor (NULL, IDC_ARROW);  
  21.     wndclassex.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH);  
  22.     wndclassex.lpszMenuName  = NULL;  
  23.     wndclassex.lpszClassName = szAppName;  
  24.     wndclassex.hIconSm       = wndclassex.hIcon;  
  25.       
  26.     //注冊窗口類  
  27.     if (!RegisterClassEx (&wndclassex))  
  28.     {  
  29.         MessageBox (NULL, TEXT ("RegisterClassEx failed!"), szAppName, MB_ICONERROR);  
  30.         return 0;  
  31.     }  
  32.   
  33.     //產生窗口  
  34.     hwnd = CreateWindowEx (WS_EX_OVERLAPPEDWINDOW,   
  35.                           szAppName,   
  36.                           TEXT ("窗口名稱"),  
  37.                           WS_OVERLAPPEDWINDOW,  
  38.                           CW_USEDEFAULT,   
  39.                           CW_USEDEFAULT,   
  40.                           CW_USEDEFAULT,   
  41.                           CW_USEDEFAULT,   
  42.                           NULL,   
  43.                           NULL,   
  44.                           hInstance,  
  45.                           NULL);   
  46.               
  47.     //顯示窗口  
  48.     ShowWindow (hwnd, iCmdShow);  
  49.     UpdateWindow (hwnd);  
  50.       
  51.     //啟動消息循環泵循環獲取消息分配到窗體過程函數處理  
  52.     while (GetMessage (&msg, NULL, 0, 0))  
  53.     {  
  54.         TranslateMessage (&msg);  
  55.         DispatchMessage (&msg);  
  56.     }  
  57.   
  58.     return msg.wParam;  
  59. }  
  60.   
  61. //窗體過程函數  
  62. LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)  
  63. {  
  64.     HDC hdc;  
  65.     PAINTSTRUCT ps;  
  66.   
  67.     switch (message)  
  68.     {  
  69.     case WM_CREATE:  
  70.         return (0);  
  71.           
  72.     case WM_PAINT:  
  73.         hdc = BeginPaint (hwnd, &ps);  
  74.         EndPaint (hwnd, &ps);  
  75.         return (0);  
  76.           
  77.     case WM_DESTROY:  
  78.         PostQuitMessage (0);  
  79.         return (0);  
  80.     }  
  81.   
  82.     return DefWindowProc (hwnd, message, wParam, lParam);  
  83. }  

 

需要明白的是,所有Windows的窗體及控件歸根結底都是使用CreateWindow或CreateWindowEx來創建的,他們都需要標准Windows窗體的產生過程。

普通的窗體好理解,主要需要弄清楚是對話框及控件的產生和消息分派處理流程。

對話框及其子控件的管理依靠Windows內建的對話框管理器,對話框管理器的工作包括:

1.根據我們在資源設計器中設計的對話框及子控件產生的.rc文件來自動生成對話框和子控件(如果有手動編寫.rc文件的經歷的話,知道編寫RC文件其實就是指定窗口和子控件大小、類型、樣式等參數,對話框管理器將這些參數傳入CreateWindow函數產生窗體)

2.模態對話框直接顯示窗體,非模態對話框消息指明WS_VISIBLE屬性的話,需要調用ShowWindow來顯示窗體。

4.維護一個消息循環泵,對於模態對話框來說這個消息泵的消息不經過父窗口,所以表現為模態;對於非模態對話框這個消息泵消息經過主窗口,必須由主窗口傳給非模態對話框,表現為非模態。

3.維護一個內建的窗體過程函數,對於對話框來說會處理對話框的關閉打開及子窗口的焦點、tab等,對於子控件也是一樣,每個子控件會有自己類型的窗體過程函數,窗體過程函數處理子控件的獲得或失去焦點、按下或彈起、創建等表現樣式和行為。對於對話框來說,他會開放一個對話框過程函數,讓部分消息先通過對話框管理函數處理,如果對話框過程函數不處理才交給默認的內建過程函數處理,對於子控件來說,他們並沒有開放過程函數,而是由內建窗體函數將要處理的消息發給父窗口處理。

那么對話框管理器完成了標准Windows窗體的產生中后半部分工作,至於設計窗口類和注冊窗口類這是由Windows自己預先做好了的,如常見的“button”、“listbox”、“edit”類等等。

一個簡要的示意圖如下

那么既然所有的窗體(包括對話框和控件)產生過程一樣,那么我們就可以將對話框管理器的部分工作替換掉:

1.不使用對話框讀取.rc模板的方式,直接將參數傳遞給CreateWindow函數來創建對話框和控件,這就是常見的動態創建控件原理。

2.設置控件自繪制如BS_OWNDRAW屬性,開放控件的WM_DRAWITEM消息給父窗口,由父窗口來繪制按鈕樣式,這就是常見的控件重繪原理。

3.替換掉內建的窗體函數,將消息傳到自定義的窗體過程函數處理,這就是常見的控件子類化原理。

 

下面,為了做演示,先用通用模板創建的方式創建一個模態對話框和其子控件,然后模板創建一個非模態對話框,在非模態對話框中使用動態創建的方式創建和模態對話框中模板創建一樣的按鈕(當然位置和大小等可能不一樣,這里只是為了說明原理故筆者並沒有去管這些細節,如果你願意完全可以把它們做的一模一樣)。

代碼太長,這里只貼出部分代碼,詳細代碼請下載演示文件

主窗口消息泵

 

[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1.   while (GetMessage (&msg, NULL, 0, 0))  
  2.   {  
  3. //注意非模態對話框消息由主窗口分發  
  4. if (hDlgModeless == NULL || !IsDialogMessage(hDlgModeless, &msg))  
  5. {  
  6.     TranslateMessage (&msg);  
  7.     DispatchMessage (&msg);  
  8. }  
  9.   }  

主窗口菜單響應

 

 

[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. case IDM_TEMPLATE:  
  2.     DialogBox(GetWindowLong(hwnd, GWL_HINSTANCE),  
  3.               IDD_TEMPLATE,  
  4.               hwnd,  
  5.               TemplateProc);  
  6.     break;  
  7. case IDM_CREATE:  
  8.     hDlgModeless = CreateDialog(GetWindowLong(hwnd, GWL_HINSTANCE),  
  9.                                  MAKEINTRESOURCE(IDD_CREATE),  
  10.                                  hwnd,  
  11.                                  CreateProc);  
  12.     ShowWindow(hDlgModeless, SW_NORMAL);//注意非模態對話框不指明WS_VISIBLE屬性必須顯示調用ShowWindow來顯示  
  13.     break;  

模板創建的模態對話框對話框過程函數

 

 

[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. BOOL CALLBACK  TemplateProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)  
  2. {  
  3.     switch(message)  
  4.     {  
  5.     case WM_CLOSE:  
  6.         {  
  7.             EndDialog(hDlg,0);  
  8.         }  
  9.         return (TRUE);  
  10.   
  11.     case WM_COMMAND:  
  12.         switch (LOWORD(wParam))  
  13.         {  
  14.         case IDCANCEL:  
  15.             {  
  16.                 SendMessage(hDlg, WM_CLOSE, 0, 0);  
  17.             }  
  18.             return (TRUE);  
  19.         case IDOK:  
  20.             {  
  21.                   
  22.             }  
  23.             return (TRUE);  
  24.         }  
  25.         return (FALSE);  
  26.     }  
  27.   
  28.     return (FALSE);  
  29. }  

 

模板創建的非模態對話框的對話框過程函數

[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. BOOL CALLBACK  CreateProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)  
  2. {  
  3.     switch(message)  
  4.     {  
  5.     case WM_INITDIALOG:  
  6.         {  
  7.             //動態創建控件子窗口  
  8.             CreateWindow(TEXT("button"),   
  9.                          TEXT("確定"),   
  10.                          WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,  
  11.                          10, 10,  
  12.                          100, 50,  
  13.                          hDlg,  
  14.                          (HMENU)IDOK,  
  15.                          GetWindowLong(hDlg, GWL_HINSTANCE),  
  16.                          NULL);  
  17.             CreateWindow(TEXT("button"),   
  18.                         TEXT("取消"),   
  19.                         WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,  
  20.                         10, 100,  
  21.                         100, 50,  
  22.                         hDlg,  
  23.                         (HMENU)IDCANCEL,  
  24.                         GetWindowLong(hDlg, GWL_HINSTANCE),  
  25.                         NULL);  
  26.         }  
  27.         return (TRUE);  
  28.   
  29.     case WM_CLOSE:  
  30.         DestroyWindow(hDlg);  
  31.         hDlgModeless = NULL;//注意及時將指針置0防止窗口銷毀后消窗口分發消息  
  32.         return (TRUE);  
  33.   
  34.     case WM_COMMAND:  
  35.         switch (LOWORD(wParam))  
  36.         {  
  37.         case IDCANCEL:  
  38.             {  
  39.                 SendMessage(hDlg, WM_CLOSE, 0, 0);  
  40.             }  
  41.             return (TRUE);  
  42.         case IDOK:  
  43.             {  
  44.   
  45.             }  
  46.             return (TRUE);  
  47.         }  
  48.         return (FALSE);  
  49.     }  

創建效果

 

模態對話框

非模態對話框

二者起到了相同的作用,動態創建比模板創建要靈活的多,這個深入學習請自行查找相關資料。上例中需要注意的模態對話框和非模態對話框,前者的消息不流經主窗口消息泵,后者的消息要先流經主窗口消息泵。

2.控件重繪(WM_DRAWITEM)

寫這篇博文的初衷就是講解控件重繪原理,自然不能少了這一內容,在剛剛提到了修改對話框管理器的行為的幾種方式,后兩種(開放WM_DRAWITEM消息和控件子類化)都是常用的控件重繪技巧,在這一節先講WM_DRAWITEM消息重繪,下一節講控件子類化重繪,都是以按鈕的重繪為例來講解。
WM_DRAWITEM顧名思義當控件需要重繪的時候發給主窗口的消息,一般在按鈕按下或彈起、獲得焦點或失去焦點、創建等時候會產生這一消息,默認是不開啟重繪消息的,如果使用模板創建按鈕必須在按鈕屬性中設置OwnDraw屬性為True,如果動態創建按鈕必須加上BS_OWNDRAW這一屬性。
下面我要重繪兩個個按鈕,按鈕是模板創建的,是默認的IDOK和IDCANCEL按鈕,希望達到的效果是
按鈕普通狀態分別為
按鈕獲得焦點分別為
按鈕按下狀態分別為
 
下面先貼出繪制部分代碼,再講解
[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. BOOL CALLBACK SelfDrawProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)  
  2. {  
  3.     LPDRAWITEMSTRUCT pdis;  
  4.     HDC hdc;  
  5.     HDC hMemDc;  
  6.   
  7.     static HINSTANCE hInstance;  
  8.     static HBITMAP hBitmapOK_D;  
  9.     static HBITMAP hBitmapOK_U;  
  10.     static HBITMAP hBitmapOK_F;  
  11.     static HBITMAP hBitmapCANCEL_D;  
  12.     static HBITMAP hBitmapCANCEL_U;  
  13.     static HBITMAP hBitmapCANCEL_F;  
  14.     static HWND    hwndOk;  
  15.     static HWND    hwndCanel;  
  16.     static BITMAP  bm;  
  17.   
  18.   
  19.     switch(message)  
  20.     {  
  21.     case WM_INITDIALOG:  
  22.         {  
  23.             hInstance = GetWindowLong(hDlg, GWL_HINSTANCE);  
  24.             hwndOk = GetDlgItem(hDlg, IDOK);  
  25.             hwndCanel = GetDlgItem(hDlg, IDCANCEL);  
  26.   
  27.             hBitmapOK_D = LoadBitmap(hInstance, TEXT("image1d"));  
  28.             hBitmapOK_U = LoadBitmap(hInstance, TEXT("image1u"));  
  29.             hBitmapOK_F = LoadBitmap(hInstance, TEXT("image1f"));  
  30.             hBitmapCANCEL_D = LoadBitmap(hInstance, TEXT("image2d"));  
  31.             hBitmapCANCEL_U = LoadBitmap(hInstance, TEXT("image2u"));  
  32.             hBitmapCANCEL_F = LoadBitmap(hInstance, TEXT("image2f"));  
  33.   
  34.             GetObject(hBitmapCANCEL_D, sizeof(BITMAP), (PTSTR)&bm);  
  35.   
  36.             //調整按鈕大小和最大圖片一樣大  
  37.             SetWindowPos(hwndOk, HWND_TOPMOST, 0, 0, bm.bmWidth, bm.bmHeight, SWP_NOZORDER | SWP_NOMOVE);  
  38.             SetWindowPos(hwndCanel, HWND_TOPMOST, 0, 0,  bm.bmWidth, bm.bmHeight, SWP_NOZORDER | SWP_NOMOVE);  
  39.         }  
  40.         return (TRUE);  
  41.   
  42.     case WM_CLOSE:  
  43.         {  
  44.             EndDialog(hDlg,0);  
  45.         }  
  46.         return (TRUE);  
  47.   
  48.     case WM_COMMAND:  
  49.         switch (LOWORD(wParam))  
  50.         {  
  51.         case IDCANCEL:  
  52.             {  
  53.                 SendMessage(hDlg, WM_CLOSE, 0, 0);  
  54.             }  
  55.             return (TRUE);  
  56.         case IDOK:  
  57.             {  
  58.                   
  59.             }  
  60.             return (TRUE);  
  61.         }  
  62.         return (FALSE);  
  63.       
  64.     //自繪制按鈕  
  65.     case WM_DRAWITEM:  
  66.         //獲得繪制結構體,包含繪制的按鈕DC和當前按鈕狀態等  
  67.         pdis = (LPDRAWITEMSTRUCT)lParam;  
  68.               
  69.         if (pdis->CtlType == ODT_BUTTON)//只繪制button類型  
  70.         {  
  71.             hdc = pdis->hDC;  
  72.             SaveDC(hdc);//保存DC,繪制完必須恢復默認  
  73.   
  74.             //繪制默認狀態  
  75.             hMemDc = CreateCompatibleDC(hdc);  
  76.             SelectObject(hMemDc, pdis->CtlID == IDOK ? hBitmapOK_U : hBitmapCANCEL_U);  
  77.             BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hMemDc, 0, 0, SRCCOPY);  
  78.             DeleteDC(hMemDc);  
  79.   
  80.             //繪制獲取焦點時狀態  
  81.             if (pdis->itemState & ODS_FOCUS)  
  82.             {  
  83.                 hMemDc = CreateCompatibleDC(hdc);  
  84.                 SelectObject(hMemDc, pdis->CtlID == IDOK ? hBitmapOK_F : hBitmapCANCEL_F);  
  85.                 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hMemDc, 0, 0, SRCCOPY);  
  86.                 DeleteDC(hMemDc);  
  87.             }  
  88.   
  89.             //繪制下壓狀態  
  90.             if (pdis->itemState & ODS_SELECTED)  
  91.             {  
  92.                 hMemDc = CreateCompatibleDC(hdc);  
  93.                 SelectObject(hMemDc, pdis->CtlID == IDOK ? hBitmapOK_D : hBitmapCANCEL_D);  
  94.                 BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hMemDc, 0, 0, SRCCOPY);  
  95.                 DeleteDC(hMemDc);  
  96.             }  
  97.               
  98.             RestoreDC(hdc, -1);  
  99.         }  
  100.   
  101.         return (TRUE);  
  102.     }  
  103.     return (FALSE);  
  104. }  
在WM_INITDIALOG函數中加載相關資源和設置按鈕大小
在WM_DRAWITEM完成主要繪制工作,獲得WM_DRAWITEM消息時獲得繪制的結構體,這個結構體包括當前要繪制的按鈕的ID、狀態等,我們主要的工作就是將對應狀態的按鈕貼上相應的位圖即可。
效果如下
WM_DRAWITEM消息控件重繪是最常用的重繪技巧,在網上常見的別人封裝好的自定義控件都是這樣的原理。

3.控件重繪(控件子類化)

子類化是借鑒C++的面向對象中的繼承和重載的思想,基本意思就是如果子類對消息處理了的話對應C++的重載,這時候父類就沒辦法再處理這個消息,除非人為的將消息傳遞給父類,所有的消息先流經子類再到父類,當然這一過程需要子類的配合,具體意思我們用代碼來說明。
同樣是達到上一節WM_DRAWITEM繪制的按鈕效果
我們用控件子類化完成這一效果,貼出部分代碼,完整代碼請下載演示文件
對話框過程函數
[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. WNDPROC btnOkOldProc, btnCancelOldProc;  
  2. BOOL CALLBACK SubclassProc(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)  
  3. {  
  4.     static HWND    hwndOk;  
  5.     static HWND    hwndCanel;  
  6.   
  7.     switch(message)  
  8.     {  
  9.     case WM_INITDIALOG:  
  10.         {  
  11.             hwndOk = GetDlgItem(hDlg, IDOK);  
  12.             hwndCanel = GetDlgItem(hDlg, IDCANCEL);  
  13.   
  14.             //窗口子類化  
  15.             btnOkOldProc = SetWindowLong(hwndOk, GWL_WNDPROC, (LONG)BtnProc);  
  16.             btnCancelOldProc = SetWindowLong(hwndCanel, GWL_WNDPROC, (LONG)BtnProc);  
  17.         }  
  18.         return (TRUE);  
  19.   
  20.     case WM_CLOSE:  
  21.         {  
  22.             EndDialog(hDlg,0);  
  23.         }  
  24.         return (TRUE);  
  25.   
  26.     case WM_COMMAND:  
  27.         switch (LOWORD(wParam))  
  28.         {  
  29.         case IDCANCEL:  
  30.             {  
  31.                 SendMessage(hDlg, WM_CLOSE, 0, 0);  
  32.             }  
  33.             return (TRUE);  
  34.         case IDOK:  
  35.             {  
  36.                   
  37.             }  
  38.             return (TRUE);  
  39.         }  
  40.         return (FALSE);  
  41.     }  
  42.     return (FALSE);  
  43. }  
按鈕過程函數(子類)
[cpp]  view plain  copy
 
 print?在CODE上查看代碼片派生到我的代碼片
  1. typedef enum tagBUTTONSTATE  
  2. {  
  3.     BTNSTATE_DEFAULT=0,  
  4.     BTNSTATE_FOCUS,  
  5.     BTNSTATE_SELECTED  
  6. }BUTTONSTATE;  
  7. LRESULT CALLBACK  BtnProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)  
  8. {  
  9.     HDC hdc;  
  10.     HDC hMemDc;  
  11.     PAINTSTRUCT ps;  
  12.       
  13.     static int      id;  
  14.     static HINSTANCE hInstance;  
  15.     static HBITMAP hBitmapOK_D;  
  16.     static HBITMAP hBitmapOK_U;  
  17.     static HBITMAP hBitmapOK_F;  
  18.     static HBITMAP hBitmapCANCEL_D;  
  19.     static HBITMAP hBitmapCANCEL_U;  
  20.     static HBITMAP hBitmapCANCEL_F;  
  21.     static BITMAP  bm;  
  22.     static BOOL    bOnce = TRUE;  
  23.     static BUTTONSTATE btnOkState=BTNSTATE_FOCUS;  
  24.     static BUTTONSTATE btnCancelState=BTNSTATE_DEFAULT;  
  25.   
  26.     id = GetWindowLong(hwnd, GWL_ID);  
  27.   
  28.     //初次進入函數加載資源,模擬WM_CREATE  
  29.     if (TRUE == bOnce)  
  30.     {  
  31.         hInstance = GetWindowLong(hwnd, GWL_HINSTANCE);  
  32.   
  33.         hBitmapOK_D = LoadBitmap(hInstance, TEXT("image1d"));  
  34.         hBitmapOK_U = LoadBitmap(hInstance, TEXT("image1u"));  
  35.         hBitmapOK_F = LoadBitmap(hInstance, TEXT("image1f"));  
  36.         hBitmapCANCEL_D = LoadBitmap(hInstance, TEXT("image2d"));  
  37.         hBitmapCANCEL_U = LoadBitmap(hInstance, TEXT("image2u"));  
  38.         hBitmapCANCEL_F = LoadBitmap(hInstance, TEXT("image2f"));  
  39.   
  40.         GetObject(hBitmapCANCEL_D, sizeof(BITMAP), (PTSTR)&bm);  
  41.         SetWindowPos(hwnd, HWND_TOPMOST, 0, 0, bm.bmWidth, bm.bmHeight, SWP_NOZORDER | SWP_NOMOVE);  
  42.   
  43.         bOnce = FALSE;  
  44.     }  
  45.   
  46.     switch (message)  
  47.     {  
  48.     case WM_CREATE:  
  49.         //注意這個消息不會進入  
  50.         return (0);  
  51.           
  52.     case WM_PAINT:  
  53.         hdc = BeginPaint (hwnd, &ps);  
  54.   
  55.         hMemDc = CreateCompatibleDC(hdc);  
  56.   
  57.         //繪制不同狀態下的按鈕樣式  
  58.         if (btnOkState == BTNSTATE_DEFAULT && id == IDOK)  
  59.         {  
  60.             SelectObject(hMemDc, hBitmapOK_U);  
  61.         }  
  62.         if(btnCancelState == BTNSTATE_DEFAULT && id==IDCANCEL)  
  63.         {  
  64.             SelectObject(hMemDc, hBitmapCANCEL_U);  
  65.         }  
  66.         if (btnOkState == BTNSTATE_FOCUS && id==IDOK)  
  67.         {  
  68.             SelectObject(hMemDc, hBitmapOK_F);  
  69.         }  
  70.         if(btnCancelState == BTNSTATE_FOCUS && id==IDCANCEL)  
  71.         {  
  72.             SelectObject(hMemDc, hBitmapCANCEL_F);  
  73.         }  
  74.         if (btnOkState == BTNSTATE_SELECTED && id==IDOK)  
  75.         {  
  76.             SelectObject(hMemDc, hBitmapOK_D);  
  77.         }  
  78.         if(btnCancelState == BTNSTATE_SELECTED && id==IDCANCEL)  
  79.         {  
  80.             SelectObject(hMemDc, hBitmapCANCEL_D);  
  81.         }  
  82.   
  83.         BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, hMemDc, 0, 0, SRCCOPY);  
  84.         DeleteDC(hMemDc);  
  85.   
  86.         EndPaint (hwnd, &ps);  
  87.         return (0);  
  88.   
  89.     case WM_SETFOCUS:  
  90.         if (id==IDOK)  
  91.         {  
  92.             btnOkState = BTNSTATE_FOCUS;  
  93.         }  
  94.         else  
  95.         {  
  96.             btnCancelState = BTNSTATE_FOCUS;  
  97.         }  
  98.         return (0);  
  99.   
  100.     case WM_KILLFOCUS:  
  101.         if (id==IDOK)  
  102.         {  
  103.             btnOkState = BTNSTATE_DEFAULT;  
  104.         }  
  105.         else  
  106.         {  
  107.             btnCancelState = BTNSTATE_DEFAULT;  
  108.         }  
  109.         return (0);  
  110.   
  111.     case WM_KEYDOWN:  
  112.         if (wParam == VK_SPACE)  
  113.         {  
  114.             if (id==IDOK)  
  115.             {  
  116.                 btnOkState = BTNSTATE_SELECTED;  
  117.             }  
  118.             else  
  119.             {  
  120.                 btnCancelState = BTNSTATE_SELECTED;  
  121.             }  
  122.   
  123.             InvalidateRect(hwnd, NULL, TRUE);  
  124.         }  
  125.         return (0);  
  126.   
  127.     case WM_KEYUP:  
  128.         if (wParam == VK_SPACE)  
  129.         {  
  130.             if (id==IDOK)  
  131.             {  
  132.                 btnOkState = BTNSTATE_FOCUS;  
  133.             }  
  134.             else  
  135.             {  
  136.                 btnCancelState = BTNSTATE_FOCUS;  
  137.             }  
  138.   
  139.             InvalidateRect(hwnd, NULL, TRUE);  
  140.         }  
  141.         return (0);  
  142.   
  143.     case WM_LBUTTONDOWN:  
  144.             SetCapture(hwnd);  
  145.             if (id==IDOK)  
  146.             {  
  147.                 btnOkState = BTNSTATE_SELECTED;  
  148.             }  
  149.             else  
  150.             {  
  151.                 btnCancelState = BTNSTATE_SELECTED;  
  152.             }  
  153.   
  154.             InvalidateRect(hwnd, NULL, TRUE);  
  155.         return (0);  
  156.   
  157.     case WM_LBUTTONUP:  
  158.             if (id==IDOK)  
  159.             {  
  160.                 btnOkState = BTNSTATE_FOCUS;  
  161.             }  
  162.             else  
  163.             {  
  164.                 btnCancelState = BTNSTATE_FOCUS;  
  165.             }  
  166.   
  167.             InvalidateRect(hwnd, NULL, TRUE);  
  168.             ReleaseCapture();  
  169.         return (0);  
  170.           
  171.     case WM_DESTROY:  
  172.         DestroyWindow(hwnd);  
  173.         return (0);  
  174.     }  
  175.     return CallWindowProc (id == IDOK ? btnOkOldProc : btnCancelOldProc, hwnd, message, wParam, lParam);  
  176. }  
在以上代碼,我們在對話框的WM_INITDIALOG消息中強制換掉按鈕原有的內建窗體過程函數,使用我們自己的BtnProc過程函數。需要注意的是在我們換掉
按鈕原有的內建窗體過程函數的時候按鈕已經創建完成,所以如果我們在BtnProc的WM_CREATE設置斷點,程序是不會進入的。和WM_DRAWITEM一樣,我們需要按鈕的不同狀態時繪制,因為我們采用自己的BtnProc過程函數,所以我們只能自己來維護按鈕的狀態,在WM_PAINT函數中根據不同狀態繪制不同樣式的按鈕,在其他消息中處理按鈕的按下或彈起、獲得焦點、或失去焦點等狀態轉變。
創建效果如下
我們基本上模擬了WM_DRAWITEM消息重繪效果:按Tab鍵切換焦點,按Space鍵按鈕按下彈起(當然只是為了演示原理,會有一些Bug,你可以想辦法完善他們)。在上訴代碼中,我們在最后調用了原來的內建的窗體過程函數,我們處理了WM_PAINT、WM_KEYUP、WM_KEYDOWN等消息,這些消息都return (0)直接返回了,即內建的窗體過程函數沒有機會處理這些消息,其他的子類沒有處理的消息都傳給原來內建的窗體過程函數處理了,如果我們想原來的內建窗體過程函數也處理WM_PAINT,那么將return (0)改成break即可。這就是我上面提到的子類化的實現必須依靠子類化窗體函數的配合,我們也可以將所有的消息都在子類中處理不回傳給原來的內建窗口,但是這樣的工作量太大,一般是不會這樣做的。
另外,可以看到相比於WM_DRAWITEM消息重繪,子類化實現控件重繪工作量要大得多,當然這樣的靈活性要更大。實際上,微軟提供子類化的作用更多是為了重新定制子控件的行為,比如說要將一組相同按鈕按下時發送一個自定義消息,這時候就可以將這些按鈕的消息子類化都先流經一個子類化窗體過程函數,然后再調用內建的窗體過程函數。
 
總結來說,一般重繪控件樣式使用WM_DRAWITEM消息,重新定制控件行為使用窗體子類化。
 
博客完整演示代碼 下載鏈接
畢竟現在來說如果不是為了實現一個自繪控件庫的話,不會使用SDK自繪的方式,下一次我會講一下MFC中自繪方式的具體流程和實例。
 
原創,轉載請注明來自 http://blog.csdn.net/wenzhou1219

http://blog.csdn.net/wenzhou1219/article/details/17652181


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM