windows消息和消息隊列 二

與基於MS - DOS的應用程序不同,Windows的應用程序是事件(消息)驅動的。它們不會顯式地調用函數(如C運行時庫調用)來獲取輸入,而是等待windows向它們傳遞輸入。 windows系統把應用程序的輸入事件傳遞給各個窗口,每個窗口有一個函數,稱爲窗口消息處理函數。窗口消息處理函數處理各種用戶輸入,處理完成後再將控制權交還給系統。窗口消息處理函數一般是在註冊一個窗口的時候指定的。你可以從典型的SDK程序中窗口消息處理函數是怎麼聲明和實現的。

對於Windows XP系統:如果頂層窗口停止響應消息超過幾秒鐘,系統會認爲窗口無迴應。在這種情況下,系統將隱藏這個窗口,然後生成一個影子(ghost)窗口覆蓋在它上面。這個影子窗口具有着相同的Z軸順序,位置,大小,顯示屬性。影子窗口允許用戶將其移動,調整大小,甚至關閉(關閉的是停止響應的window)。此時只有這幾個動作是被允許的,在調試模式下,系統不會生成影子窗口。

本節討論以下主題:

Windows消息

1. 消息類型

2. 消息傳遞

3. 消息處理

4. 消息過濾

5. post message和send message

6. 消息死鎖

7. 廣播消息

8. 查詢消息

1. Windows消息

   windows通過消息的形式向窗口傳遞用戶輸入。消息可以由系統和應用程序生成。該系統會爲每個輸入事件產生相應的消息,

例如,用戶點擊鼠標,移動鼠標或滾動條,或是應用程序改變了系統的某些屬性,比如說系統更改了字體資源,改變了某個窗口的

大小。 不僅如此,應用程序可以生成消息,通告發送消息指定它的窗體去執行某些任務或者是與其他的應用程序交互。

windows系統將消息發送到一個窗口消息處理函數時傳遞四個參數:窗口句柄,消息標識符,兩個DWORD值(消息參數)。

窗口句柄標識了該消息的目的窗口。windows使用它來確定是哪個窗口的的窗口消息處理函數收到該消息。

   一個消息標識符是一個有名字的常量,用來表明消息的意義。當一個窗口處理函數收到一條消息,它根據判斷消息標識符來決定如何處理該消息,例如,消息標識符WM_PAINT消息告訴窗口程序窗口的客戶區已發生變化,必須重繪。 消息參數(DWORD值)指定傳遞的數據或是數據的地址。消息參數可以是一個整型值,一個指針值。也可以爲NULL。

一個窗口過程必須根據消息標識符來確定如何解釋消息參數。

2. windows 消息類型

   本節描述消息的兩種類型:

   (1) 系統定義的消息

   (2) 應用程序定義的消息

系統定義的消息

   操作系統嚮應用程序發送消息來和應用程序通訊。操作系統通過消息控制應用程序的運行,嚮應用程序傳遞用戶輸入以及一些其他有用的信息。

   應用程序也可以發送系統定義的消息,應用程序通過這些消息去控制使用註冊窗口類創建的控件的窗口的運行。

   每個系統定義的消息都有一個唯一的消息標識符和相應的符號常量(在windows SDK的頭文件裏定義)。符號常量通常會表明系統定義的消息所屬的類別。不同的前綴表明不同的類別。一下是常見的分類:

   PrefixMessage category

   WM General window(一般的窗口)

   ABM Application desktop toolbar (應用程序桌面工具條)

   BM Button control (按鈕控件)

   CB Combo box control (組合框控件)

   CBEMExtended combo box control(擴展的組合框控件)

   CDM Common dialog box (普通的對話框)

   DBT Device (設備)

   DL Drag list box (下拉列表)

   DM Default push button control (默認按鈕控件)

   DTM Date and time picker control(日期和時間選擇控件)

   EM Edit control (編輯控件)

   HDM Header control (表頭控件)

   HKM Hot key control (熱鍵控件)

   IPM IP address control (IP地址控件)

   LB List box control (列表框控件)

   LVM List view control (列表視圖控件)

   MCM Month calendar control (數學日曆控件)

   PBM Progress bar (進度條控件)

   PGM Pager control ()

   PSM Property sheet (屬性頁)

   RB Rebar control (分隔條控件)

   SB Status bar window (狀態條控件)

   SBM Scroll bar control (滾動條控件)

   STM Static control (靜態控件)

   TB Toolbar (工具條)

   TBM Trackbar (跟蹤欄)

   TCM Tab control (選項卡控件)

   TTM Tooltip control ()

   TVM Tree-view control ()

   UDM Up-down control ()

   (2)應用程序定義的消息

   應用程序可以通過創建自定義的消息,用來和自己的窗口和其他進程通訊。如果應用程序創建了自己的消息,窗口處理函數可以解析這些信息,並作出相應的處理。

   消息標識符值的取值範圍:

   該系統保留了一個消息範圍,從0x0000到0x03FF(0x03FF等於WM_USER -1)範圍. 這個範圍內的值爲系統定義的消息。應用程序不能使用這些值作爲自己的自定義消息。

   從0x0400(數值WM_USER)到0x7FFF的值是爲應用程序保留的。應用程序可以使用這個範圍內的值來定義自己的消息。

   如果你的操作系用的版本(windows version)主版本爲4.0版,你還可以使用0x8000(WM_APP)到0xBFF之間的值來定義自己的消息。

   除此之外,應用程序還可以調用RegisterWindowMessage函數註冊一個消息時,操作系統會返回一個介於0xC000和0xFFFF之間的一個消息標識符。並且保證這個返回值是系統唯一的。因此,可以避免和其他應用程序使用的消息相沖突。

3. 消息派發

   windows使用兩種方法將消派發到一個窗口消息處理函數:一是將消息放到消息隊列(先進先出隊列),二是不放到消息隊列,直接發送到窗口消息處理函數,讓窗口處理函數來處理消息。

   派發到消息隊列的消息被稱爲排隊消息(Queued messages)。它們主要是用戶輸入事件,比如說鼠標或鍵盤消息盤,有WM_MOUSEMOVE消息,WM_LBUTTONDOWN,WM_KEYDOWN,和WM_CHAR消息。還有一些其他的,包括WM_TIMER,WM_PAINT,以及WM_QUIT。大多數其他的消息息,這是直接發送到窗口過程,被稱爲非隊列消息(non queued messages)。

(1) 隊列(Queued)消息

   windows可同時顯示任意數量的窗口。此時,系統使用消息隊列來將鍵盤和鼠標事件正確的派發到正確的窗口。

windows維護着一個系統消息隊列,以及分別爲每個GUI線程維護一個各自的線程消息隊列。爲了避免非GUI線程的創建線程消息隊列的開銷,所有線程創建初始化時,均不創建消息隊列。只有當線程第一次調用GDI函數時,系統纔會爲線程創建消息隊列。所以那些非GUI線程是沒有消息隊列的。

   每當用戶移動鼠標,點擊按鈕或鍵盤時,鼠標或鍵盤的設備驅動程序會將輸入轉換成消息,並將消息放在系統消息隊列裏。windows會檢查自己的系統消息隊列,如果消息隊列不爲空,則每次取出並刪除一個消息,然後確定消息的目標窗口,然後把消息放到創建這個窗口的線程的線程消息隊列裏。線程的消息隊列接收由線程創建的窗口的所有的鼠標和鍵盤消息。然後線程會從隊列中刪除信息,並告訴系統把它們派發到對應的窗口消息處理函數。

   除了WM_PAINT, WM_TIMER和WM_QUIT消息以外,系統總是派發消息放到消息隊列的末尾。這將保證讓一個窗口以first-in, first-out的順序接收消息。WM_PAINT,WM_TIMER,和WM_QUIT消息,會一直被保存在隊列(系統隊列嗎??)中,只有在應用程序隊列中沒有其他消息時纔會被派發到該隊列中,並交給窗口消息處理函數處理。此外,同一個窗口的多個WM_PAINT消息被合併成一個WM_PAINT消息,客戶區的所有無效部分也會被合併。這樣是爲了減少窗口重繪客戶區的次數。

   windows向線程消息隊列傳遞消息時,首先會填充一個MSG結構,然後將這個MSG結構複製到消息隊列。MSG中的信息包括:目標窗口,消息標識符,兩個消息參數,消息派發時的時間,鼠標光標位置。一個線程可以使用PostMessage或PostThreadMessage功能向自己的消息隊列或者是其他線程的消息隊列發送消息。

   應用程序可以使用GetMessage函數從自己的消息隊列中刪除消息。查看而不刪除消息,用的是PeekMessage函數。

PeekMessage函數會返回一個帶有消息信息的MSG結構。

   從消息隊列中刪除消息後,應用程序可以使用DispatchMessage函數指示系統將消息發送到一個窗口消息處理函數。 DispatchMessage的參數是是前一次調用GetMessage或PeekMessage獲得的MSG結構的指針。 DispatchMessage會傳遞窗口句柄,消息標識符,這兩個消息參數這些信息給窗口消息處理函數,它不會傳遞消息派發時間以及鼠標光標位置。應用程序可以在處理消息時調用的GetMessageTime和GetMessagePos來獲得這些信息。

   線程可以使用WaitMessage函數,交出自己的控制權,當它的消息隊列中沒有消息時,調用WaitMessage函數會掛起線程,直到自己的消息隊列裏有消息時才返回。

您可以調用SetMessageExtraInfo函數來關聯一個值給當前線程的消息隊列。然後調用GetMessageExtraInfo函數來獲取由GetMessage或PeekMessage函數得到的最後一條消息相關聯的值。你可以去msdn上看更多的關於這幾個函數的信息。

   (2) 非隊列(Nonqueued)消息

   Nonqueued消息被立即送往目的地的窗口消息處理函數,繞過了系統的消息隊列和線程消息隊列。系統通常會發送nonqueued消息,來通知那些會影響窗口的事件。例如,當用戶激活一個新的應用程序窗口時,系統會發送一些列消息到窗口,包括WM_ACTIVATE,WM_SETFOCUS,WM_SETCURSOR。這些消息通知窗口被激活,鍵盤輸入被定向到窗口,並且鼠標光標也移到窗口的邊界內。

   Nonqueued消息也有可能來源於應用程序調用系統函數。例如,系統調用SetWindowPos函數移動一個窗口後會發送WM_WINDOWPOSCHANGED消息。 一些函數也發送nonqueued消息, 有BroadcastSystemMessage,BroadcastSystemMessageEx,SendMessage,SendMessageTimeout,和Sen

dNotifyMessage。 關於這些函數的詳細信息,你可以查閱MSDN。

消息處理

應用程序必須刪除並處理髮送到它的線程消息隊列的消息。單線程應用程序通常在它的WinMain函數的消息循環,刪除和分發消息到適當的窗口進行處理。多線程應用程序可以在每一個線程創建一個窗口的消息循環。以下章節描述了一個消息

循環如何工作,並講述窗口消息處理函數的作用:

(1)消息循環

(2)窗口處理函數

消息循環

一個簡單的消息循環包含調用以下三個函數:GetMessage,TranslateMessage,和DispatchMessage。請注意,如果有一個錯誤,GetMessage返回-1 -因此,需要測試它的返回值,來判斷爲-1的情況

代碼片段:

...

MSG msg;

BOOL bRet;

while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)

{

   if (bRet == -1)

   {

   // handle the error and possibly exit

   }

   else

   {

   TranslateMessage(&msg);

   DispatchMessage(&msg);

   }

}

GetMessage函數從隊列中獲取消息,並將消息內容複製到一個MSG結構。它返回一個非零值,除非遇到WM_QUIT消息,此種返回FALSE並結束消息循環。在一個單線程應用程序,結束消息循環往往是在關閉應用程序的第一步。應用程序可以調用PostQuitMessage函數來響應WM_DESTROY,結束消息循環。

如果您指定一個窗口句柄作爲GetMessage的第二個參數,那麼GetMessage只獲取在消息隊列裏和這個窗口有關的消息。 GetMessage也可以在隊列中篩選消息,只獲取指定範圍內的消息。如需有關消息過濾的詳細信息,請參考消息過濾。

線程的消息循環必須包括TranslateMessage,如果線程需要接受鍵盤字符的輸入。每次用戶按下一個鍵,該系統產生相應的虛擬鍵消息(WM_KEYDOWN和WM_KEYUP)。虛擬鍵消息包含一個虛擬鍵碼,標識的是被按下的鍵,而不是它相關的字符值。要獲得此值,消息循環必須包含TranslateMessage,用來將虛擬鍵消息翻譯成字符消息(WM_CHAR)的並放到應用程序的消息隊列裏。經過若干次循環後,WM_CHAR消息會被並派發到一個窗口。

DispatchMessage函數將消息發送到到與MSG結構中的窗口句柄關聯的窗口。如果窗口句柄是HWND_TOPMOST,DispatchMessage則將消息發送到操作系統所有的頂層窗口。如果窗口句柄是NULL,DispatchMessage不做任何事。

一個應用程序的主線程初始化後,系統就啓動應用程序的消息循環,並創造至少一個窗口。一旦啓動,消息循環持續從該線程的消息隊列中刪除消息,並派發他們到相應的窗口。GetMessage函數從消息列表中獲取到WM_QUIT消息時,消息循環結束。

一個消息隊列只需要一個消息循環,即使一個應用程序包含有多個窗口。 DispatchMessage總是調度消息到正確的窗口,這是因爲每個隊列中的消息是MSG結構,它包含着消息所屬的窗口的句柄。

您可以以多種方式來修改消息循環。例如,您可以從隊列中刪除消息,但是不派發他們。當發送有些不帶有目的地窗口的消息時這非常有用。您也可以使用GetMessage只獲取指定的消息,這是有用的,如果你必須你暫時繞過正常的消息隊列FIFO順序。

應用程序使用快捷鍵時,必須能夠將鍵盤消息轉換爲命令消息。因此,應用程序的消息循環必須包括TranslateAccelerator函數調用。關於快捷鍵的更多信息,請參見鍵盤加速器。

如果一個線程使用一個無模式對話框,那麼消息循環必須包括IsDialogMessage函數,以便該對話框可以接收鍵盤輸入。

   (2)窗口消息處理函數

   窗口消息函數接收和處理的所有發送到窗口的消息。每個窗口類有一個窗口消息處理函數,用該類創建的每個窗口使用同一窗口消息處理函數。

   該系統將消息發送到一個窗口的程序,並傳遞消息的相關信息到窗口消息處理函數,窗口消息處理函數檢查消息標識符,根據傳過來的參數識別並處理不同的消息,

   一個窗口過程通常不會忽略一個消息。如果消息沒有被處理,必須被髮送給系統默認的窗口消息處理函數,這是否通過調用DefWindowProc函數,來執行一個默認的處理,並返回一個處理的結果。窗口程序必須然後返回該值作爲自己的消息處理的結果。大多數窗口消息處理函數只處理一少部分消息,並將其他的返回給系統默認的窗口消息處理函數。

因爲窗口消息處理函數被所有屬於同一個窗口類的窗口共享,它可以處理幾個不同的窗口的消息。要確定具體的窗口消息,窗口消息處理函數可以檢查消息結構裏的窗口句柄。

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