windows消息循環原理實例總結

 
Code
#include <windows.h>
/* 導入包含文件WINDOWS.H,此文件包含了其它的Windows頭文件                */ 
/* WINDEF.H   基本類型定義                                              */ 
/* WINNT.H    支持Unicode的類型定義                                     */ 
/* WINBASE.H  內核函數                                                  */ 
/* WINUSER.H  用戶接口函數                                              */ 
/* WINGDI.H   圖形設備接口函數 */

/************************************************************************/ 
/* 窗口對象的過程處理函數                                               */ 
/* LRESULT: 簡單定義爲LONG(long)                                       */ 
/* CALLBACK:__stdcall,指在Windows本身和用戶的應用程序之間發生的函數調 */ 
/*           用的特殊調用序列。                                         */ 
/* HWND:    窗口句柄,32位數字,該參數爲接受消息的窗口的句柄,          */ 
/*           CreateWindow函數的返回值。                                 */ 
/* UINT:    unsigned int 無符號整型32位,                              */ 
/*           該參數爲MSG結構中的message域相同,表示該消息的數字         */ 
/* WPARAM:  UINT,32位消息參數                                         */ 
/* LPARAM:  LONG,32位消息參數                                         */ 
/************************************************************************/ 
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); 

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, PSTR lpszCmdLine, int iCmdShow) 
{
 /* 定義窗口類                                                           */ 
 /* TCHAR:char                                                          */ 
 /* _T和TEXT宏,功能一致,通常沒用,在Unicode系統中,                    */ 
 /* 自動把後面的字符串轉換爲寬字符串                                     */ 
 /************************************************************************/ 
 TCHAR tcClassName[] = TEXT("My Window");  //窗口類名字符串 

 /************************************************************************/ 
 /* WNDCLASS:窗口類結構,定義了窗口的一般特性,可以創建不同的窗口       */ 
 /* typedef struct                                                       */ 
 /* {                                                                    */ 
 /*      UINT        style ;                                             */ 
 /*      WNDPROC     lpfnWndProc ;                                       */ 
 /*      int         cbClsExtra ;                                        */ 
 /*      int         cbWndExtra ;                                        */ 
 /*      HINSTANCE   hInstance ;                                         */ 
 /*      HICON       hIcon ;                                             */ 
 /*      HCURSOR     hCursor ;                                           */ 
 /*      HBRUSH      hbrBackground ;                                     */ 
 /*      LPCTSTR     lpszMenuName ;                                      */ 
 /*      LPCTSTR     lpszClassName ;                                     */ 
 /* }                                                                    */ 
 /* WNDCLASS, * PWNDCLASS ;                                              */ 
 /************************************************************************/ 

    HWND hWnd;
    MSG msg;

    WNDCLASS wc;                      //窗口類屬性描述結構 
    wc.lpszClassName = tcClassName;   //窗口類名 
    wc.lpszMenuName  = NULL;          //窗口類菜單資源名 
    wc.lpfnWndProc   = WndProc;       //窗口對象的過程處理函數,指向函數的指針 
    wc.hInstance     = hInstance;     //當前進程對象句柄,接收於WinMain參數 
    wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); //窗口背景刷子對象 
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);     //圖標對象 
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);         //光標對象 
    wc.cbClsExtra    = 0;             //同類窗口對象公共數據區大小 
    wc.cbWndExtra    = 0;             //當前窗口對象私有數據區大小 
    wc.style         = CS_HREDRAW | CS_VREDRAW; //窗口類風格,水平或垂直尺寸改變後刷新 

    if (!RegisterClass(&wc))          //註冊窗口類,參數爲指向WNDCLASS結構的指針 
    {
        /************************************************************************/ 
        /* 窗口類註冊不成功的錯誤信息                                           */ 
        /* MessageBox 消息框                                                    */ 
        /* 參數一:窗口句柄,如果沒有則爲NULL                                   */ 
        /* 參數二:消息框主體顯示的字符串                                       */ 
        /* 參數三:消息框標題欄上的字符串                                       */ 
        /* 參數四:winuser.h中定義的MB_開始的常數組合,消息框風格:按鈕,圖標    */ 
        /*      爲0,則只有ok按鈕                                            */ 
        /* 返回值:返回IDOK(1)。                                                */ 
        /* 還可以返回IDYES、IDNO、IDCANCEL、IDABORT、IDRETRY、IDIGNORE等        */ 
        /************************************************************************/ 
        MessageBox(NULL, TEXT("RegisterClassError!"), TEXT("Error"), MB_ICONERROR); 

        return 0;                        //如果註冊失敗,返回並終止程序 
    }

    /************************************************************************/ 
 /* 定義窗口對象屬性,指定有關窗口的更詳細信息                           */ 
 /************************************************************************/ 
 TCHAR tcWindowCaptionName[] = TEXT("Win32 API"); //窗口對象標題名稱 
 CREATESTRUCT cs;                  //窗口對象屬性描述結構,定義在WINUSER.H 

 cs.lpszClass      = tcClassName;  //窗口類名 
 cs.lpszName       = tcWindowCaptionName; //窗口對象標題名稱,顯示在標題欄 
 cs.style          = WS_OVERLAPPEDWINDOW; //窗口對象風格 
 cs.x              = 100;          //窗口對象在屏幕上的x座標 
 cs.y              = 100;          //窗口對象在屏幕上的y座標 
 cs.cx             = 400;          //窗口對象的寬度 
 cs.cy             = 300;          //窗口對象的高度 
 cs.hwndParent     = NULL;         //窗口對象的父窗口句柄 
 cs.hMenu          = NULL;         //窗口對象的菜單句柄或子窗口編號 
 cs.hInstance      = hInstance;    //當前進程的實例句柄,WinMain參數 
 cs.lpCreateParams = NULL;         //創建參數指針,可以訪問以後想要引用的程序中的數據 

 /************************************************************************/ 
 /* 創建窗口對象                                                         */ 
 /* 定義窗口句柄hWnd,值爲CreateWindows函數的返回值。                    */ 
 /* 即創建成功返回窗口的句柄,否則返回NULL                               */ 
 /************************************************************************/ 
 hWnd = CreateWindow(cs.lpszClass, 
      cs.lpszName, 
      cs.style, 
      cs.x, 
      cs.y, 
      cs.cx,
      cs.cy, 
      cs.hwndParent, 
      cs.hMenu, 
      cs.hInstance, 
      cs.lpCreateParams); 

 if (hWnd == NULL)    //判斷創建是否成功 
 { 

  /************************************************************************/ 
  /* 窗口對象創建不成功的錯誤提示                                         */ 
  /************************************************************************/ 
  MessageBox(NULL, TEXT("CreateWindowError!"), TEXT("Error!"),MB_ICONERROR); 

  return 0; 
 } 

 /************************************************************************/ 
 /* 顯示窗口對象                                                         */ 
 /* 此時Windows內部已經創建了這個窗口。已經分配內存。                    */ 
 /* 但是要顯示在顯示器上還需要調用兩個函數。                             */ 
 /* ShowWindows(hwnd,iCmdShow)                                           */ 
 /* 第一個參數是剛剛用CreateWindow創建的窗口的句柄。                     */ 
 /* 第二個參數是傳給WinMain的iCmdShow。用來確定最初如何在屏幕上顯示窗口。*/ 
 /* 也可以自定義選擇以選項:                                             */ 
 /* SW_SHOWNORMAL      //常規                                            */ 
 /* SW_SHOWMAXIMIZED   //最大化                                          */ 
 /* SW_SHOWMINNOACTIVE //只顯示在任務欄                                  */ 
 /* UpdateWindow(hWnd)                                                   */ 
 /* 導致客戶區域被繪製。通過給窗口過程(Wndproc)發送一個WM_PAINT消息實現  */ 
 /************************************************************************/ 
 ShowWindow(hWnd, iCmdShow); 
 UpdateWindow(hWnd);//立即刷新窗口對象 
/************************************************************************/ 
 /* 消息檢索,消息循環                                                   */ 
 /* MSG:消息結構,被定義在WINUSER.H                                     */ 
 /* 消息循環以GetMessage調用開始,它從消息隊列中取出一個消息             */ 
 /* 這一調用傳遞給Windows一個指向msg的MSG結構指針。                      */ 
 /* 第二、三、四個參數爲NULL或者0表示程序接收自己創建的所有窗口的所有消息*/ 
 /* Windows用從消息隊列中取出的下一個消息填充msg結構的各個域             */ 
 /* MSG:消息結構:                                                      */ 
 /* typedef struct tagMSG                                                */ 
 /* {                                                                    */ 
 /*   HWND   hwnd ;    //消息發向的窗口的句柄。                          */ 
 /*   UINT   message ; //消息標識符,一個數值,定義在Window頭文件中      */ 
 /*   WPARAM wParam ;  //一個32位的消息參數,含義根據消息不同而不同      */ 
 /*   LPARAM lParam ;  //同上                                            */ 
/*   DWORD  time ;    //消息放入消息隊列時的時間                        */ 
 /*   POINT  pt ;      //消息放入消息隊列時的鼠標座標                    */ 
 /* }                                                                    */ 
 /* MSG, * PMSG ;      //結構名                                          */ 
 /************************************************************************/ 
 while (GetMessage(&msg, NULL, 0, 0)) 
 { 

  /************************************************************************/ 
  /* 檢索消息;當檢索到WM_QUIT(其值爲0x0012)消息時,從消息循環中退出      */ 
  /************************************************************************/ 
  TranslateMessage(&msg);//將msg結構傳遞給Windows,進行虛擬鍵盤消息的轉換 
  DispatchMessage(&msg); //發送消息,由此操作系統調用相應的窗口過程處理消息 
 } 

 /************************************************************************/ 
 /* 主窗口返回                                                           */ 
 /************************************************************************/ 
 return msg.wParam;
 }

/************************************************************************/ 
/* 窗口對象的過程處理函數                                               */ 
/* 四個參數與MSG結構中的前四個參數相同。                                */ 
/* 程序通常不直接調用窗口過程,由Windows本身調用。                      */ 
/* 程序可以通過SendMessage函數調用自己的窗口過程                        */ 
/************************************************************************/ 
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMsg, WPARAM wParam, LPARAM lParam) 


 /************************************************************************/ 
 /* 對消息進行分類處理                                                   */ 
 /* WINUSER.H爲每個消息定義以WM爲前綴的標識符                            */ 
 /*一般Windows程序元用switch和case結構來處理,此時必須返回0。            */ 
 /*窗口過程不處理的其它消息必須傳遞給DefWindowProc函數,                 */ 
 /*窗口過程返回該函數返回值                                              */ 
 /************************************************************************/ 
 switch (iMsg) 
 { 

  /************************************************************************/ 
  /* 客戶區的繪製消息,窗口客戶區域無效時刷新                             */ 
  /************************************************************************/ 
  case WM_PAINT: 
  { 

   /************************************************************************/ 
   /* PAINTSTRUCT:繪圖結構,定義在WINUSER.H中                             */ 
   /* RECT:矩形結構                                                       */ 
   /* HDC:設備描述表句柄                                                  */ 
   /************************************************************************/ 
   PAINTSTRUCT ps; 
   HDC hDC; 
   RECT rect; 
   /* 對於WM_PAINT的處理幾乎總是從一個BeginPaint函數開始的:               */ 
   /* hDC = BeginPaint(hWnd, &ps)                                          */ 
   /* 而已一個EndPaint函數結束                                             */ 
   /* EndPaint(hWnd, &ps)                                                  */ 
   /* 兩個調用中第一個參數是程序的窗口句柄,                               */ 
   /* 第二個參數是指向類型爲PAINTSTRUCT的結構指針                          */ 
   /************************************************************************/ 
   hDC = BeginPaint(hWnd, &ps);       //獲取顯示設備對象及繪製描述屬性 
   GetClientRect(hWnd, &rect);        //獲取當前窗口對象客戶區矩形 
   SetBkMode(hDC,TRANSPARENT);        //設置背景方式 
   SetTextColor(hDC, RGB(255, 0, 0)); //設置文本顏色 

   /************************************************************************/ 
   /* 繪製文本                                                             */ 
   /* DrawText函數,第一個參數是從BeginPaint返回的設備描述表句柄           */ 
   /* 第二個參數是要輸出的文本                                             */ 
   /* 第三個參數是-1,表示文本串是以字節0終結的。                          */ 
   /* 第四個參數要繪製的矩形區域                                           */ 
   /* 最後一個參數是系列標誌位,定義在WINDUSER.H中,水平、垂直中央,單行   */ 
   /************************************************************************/ 
   DrawText(hDC, TEXT("Hello, Win32!"), -1, &rect, DT_CENTER | DT_VCENTER | DT_SINGLELINE); 
   EndPaint(hWnd, &ps);               //客戶區繪製結束,歸還顯示設備對象 
   return 0; 
  } 

  case WM_DESTROY:                    //接收到WM_DEFTROY消息 
  { 

   /************************************************************************/ 
   /* 發送WM_QUIT消息,通知線程消息檢索循環,清除窗口主程序可以退出        */ 
   /* PostQuitMessage(0)函數在消息隊列裏插入一個WM_QUIT消息                */ 
   /************************************************************************/ 
   PostQuitMessage(0); 

   return 0; 
  } 
 } 

 /************************************************************************/ 
 /* 程序末處理的消息交給window系統的缺省窗口對象的過程處理函數處理       */ 
 /************************************************************************/ 
 return DefWindowProc(hWnd, iMsg, wParam,lParam); 
}

 

 

我們看上面的流程,可以總結出創建一個窗口的流程:

(1).註冊窗口類(RegisterClass)。在註冊之前,要先填寫RegisterClass的參數WNDCLASSEX結構。

(2)建立窗口(CreateWindow)。

(3)顯示窗口(ShowWindows)。

(4)刷新窗口客戶區(UpdateWindow)。

(5)進入無限的消息獲取和處理的循環。首先獲取消息(GetMessage),如果有消息到達,則將消息分派到回調函數處理(DispatchMessage),如果消息是WM_QUIT,則退出循環。

二.那麼爲什麼要按照這樣的順序做呢?我們來用下圖闡述一下原理:

(1).當我們按了鍵盤和shu標後,此時會產生一個消息(包含消息的類型,發生的時間,位置等),並放入到系統消息隊列中。此時windows會檢查消息發生的位置,如果發現這個消息剛好位於某個應用程序的窗口內的時候,就將這個消息放於應用程序的消息隊列中。如圖c所示。

(2).當應用程序還沒有來取消息的時候,消息就暫時保留在消息隊列裏,當程序中的消息循環執行到GetMessage的時候,控制權轉移到GetMessage所在的USER32.DLL中(箭頭1),USER32.DLL從程序消息隊列中取出一條消息(箭頭2),然後把這條消息返回應用程序(箭頭3)。

(3).然後應用程序將處理這條消息,但方法不是自己直接調用窗口過程來完成,而是通過DispatchMessage間接調用窗口過程,Dispatch的英文含義是“分派”,之所以是“分派”,是因爲一個程序可能建有不止一個窗口,不同的窗口消息必須分派給相應的窗口過程。當控制權轉移到USER32.DLL中的DispatchMessage時,DispatchMessage找出消息對應窗口的窗口過程,然後把消息的具體信息當做參數來調用它(箭頭5),窗口過程根據消息找到對應的分支去處理,然後返回(箭頭6),這時控制權回到DispatchMessage,最後DispatchMessage函數返回應用程序(箭頭7)。這樣,一個循環就結束了,程序又開始新一輪的GetMessage。

(4). 應用程序之間也可以互發消息,PostMessage是把一個消息放到其他程序的消息隊列中,如圖4.4中箭頭d所示,目標程序收到了這條消息就把它放入該程序的消息隊列去處理;而SendMessage則越過消息隊列直接調用目標程序的窗口過程(如圖4.4中箭頭I所示),窗口過程返回以後才從SendMessage返回(如圖4.4中箭頭II所示)。

窗口過程是由Windows回調的,Windows又是怎麼知道往哪裏回調呢?答案是我們在調用RegisterClassEx函數的時候告訴了Windows。

三.理解了原理之後,我們來仔細分析上面創建一個窗口的程序。

(1).爲什麼要使用註冊窗口類?

例如:在一個窗口中,可能有不同的按鈕,它們的工作原理都是一樣的。但是各個按鈕可能都有不同的表現形式,比如大小,顏色等。所以在這種情況下,我們需要將創建窗口的共性提取出來,然後再設置每個具體的窗口。這樣就更加符合面向對象的原理。

(2).當應用程序取得消息後,DispatchMessage是如何知道要發給誰去處理呢?

這是因爲在註冊窗口類時,已經指定了。否則windows不可能知道。

(3).註冊窗口類後,就要創建窗口了,建立窗口以後,傳回來的是窗口句柄,要把它先保存起來,這時候,窗口雖已建立,但還沒有在屏幕上顯示出來,要用ShowWindow把它顯示出來,ShowWindow也可以用在別的地方,主要用來控制窗口的顯示狀態(顯示或隱藏),大小控制(最大化、最小化或原始大小)和是否激活(當前窗口還是背後的窗口),它用窗口句柄做第一個參數,第二個參數則是顯示的方式

 

(4).窗口建立後,我們需要在窗口的客戶區顯示內容,這就要用到UpdateWindow, 它實際上就是向窗口發送了一條WM_PAINT消息

 

三.消息循環

函數會在這裏返回取到的消息,hWnd參數指定要獲取哪個窗口的消息,例子中指定爲NULL,表示獲取的是所有本程序所屬窗口的消息,wMsgFilterMin和wMsgFilterMax爲0表示獲取所有編號的消息。

GetMessage函數從消息隊列裏取得消息,填寫好MSG結構並返回

TranslateMessage將MSG結構傳給Windows進行一些鍵盤消息的轉換,當有鍵盤按下和放開時,Windows產生WM_KEYDOWN和WM_KEYUP或WM_SYSKEYDOWN和WM_SYSKEYUP消息,但這些消息的參數中包含的是按鍵的掃描碼,轉換成常用的ASCII碼要經過查表,很不方便,TranslateMessage遇到鍵盤消息則將掃描碼轉換成ASCII碼並在消息隊列中插入WM_CHAR或WM_SYSCHAR消息,參數就是轉換好的ASCII碼,如此一來,要處理鍵盤消息的話只要處理WM_CHAR消息就好了。遇到別的消息則TranslateMessage不做處理。

例如:如果我們按一個健盤上的健,如果不用TranslateMessage,則會處理WM_KEYDOWN和WM_KEYUP消息,否則,就只會產生WM_CHAR消息。

最後,由DispatchMessage將消息發送到窗口對應的窗口過程去處理。窗口過程返回後DispatchMessage函數才返回,然後開始新一輪消息循環。

 

四.       WM_PAINT消息

在最初創建窗口時,整個客戶區域是無效的。因爲程序還沒有在窗口上畫什麼東西。當調 用updatawindow後,會發送一個WM_PAINT消息(第一個WM_PAINT消息),它會指示在窗口的客戶區內畫一些東西。

那麼當我們改變窗口大小時,使整個窗口無效,迫使windows刷新。

當窗口與另一個窗口重疊時,當移開後,使被重疊的那部分無效。

當最小化窗口後,當窗口重新恢復到原始狀態時,windows並不保存客戶區的內容(因爲那樣工作量太大),只是使之無效而已。

 一旦客戶區域失效,窗口過程就會接收一個新的WM_PAINT消息,此時使用GetClientRect獲得變化後的客戶區哉。並在新窗口的中央顯示文本。

因爲按鈕是子窗口,是系統已定義好的,所以不需要使用註冊窗口類註冊,也不需要寫消息處理,直接在窗口的WM_CREATE中創建就可以了,在上一講的窗口過程中加入如下代碼:

case WM_CREATE:

{

         CreateWindow(TEXT("BUTTON"),TEXT("按鈕(&A)"),WS_CHILD | WS_VISIBLE |BS_PUSHBUTTON,10,10,65,22,hWnd,(HMENU)1000,hInst,NULL);

                    return 0;

}

這樣就實現了在sdk中加入控件的功能,非常簡單吧!!

 

同樣的道理,如果我們想增加一個文本框控件,僅需要加入:

///下面一個是編輯框按鈕,你可以在其中輸入文字,具有一個簡單的寫字板的

       ///功能,可以複製、粘貼、剪切等操作

       ///////////////////////////////////////////////////////////////////

       CreateWindow(

       "edit",    //在這裏設置此按鈕爲edit類型,表示將控件設定爲編輯框控件

       "試試",      //字符型數組在前面已經定義,在這裏顯示它的內容

      WS_CHILD | WS_VISIBLE,

       10,

       160,

       568,

       130,

       hWnd,

       (HMENU)1001,

       hInst,

       NULL);

備註:設定按鈕的類型爲button,其它類型有靜態控件static,
            滾動條控件scrollbar,編輯框控件edit,列表框控件listbox

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章