你的第一個Windows程序——窗口消息

MSDN在線原文(英文)

窗口消息

一個GUI應用程序必須響應用戶和操作系統的事件。

來自用戶的事件 包括所有的使他人能夠與你的程序互動的方法:鼠標點擊,鍵盤輸入,觸屏手勢等。
來自操作系統的事件 包括任何可以影響程序行爲的“外部”程序,比如用戶可以插入一個新的硬件,或者Windows可能會進入低功耗狀態(睡眠或休眠)。

在程序運行時,這些事件可以在任意順序任何時間發生,你如何構建一個執行流程不能提前預測的程序?
爲了解決這個問題,Windows使用一個“消息傳遞”模塊,操作系統和你的窗口應用程序傳遞消息給這個模塊。消息是一個簡單的數字代碼,它指定一個特定的事件。例如,用戶按壓了鼠標左鍵,窗口收到以下消息代碼的消息:

#define WM_LBUTTONDOWN 0x0201

有一些消息具有與它們相關的數據,比如 WM_LBUTTONDOWWN 消息包含了鼠標當前位置的X座標與Y座標。
爲了傳遞一個消息給窗口,操作系統將調用註冊該窗口的窗口過程(現在你知道什麼是窗口過程).

消息循環

一個應用程序在它運行時,會收到數以千記的消息(想想看,每一次的擊鍵和鼠標點擊生成一條消息),而且一個應用程序有多個窗口,每個都有自己的窗口過程。程序如何獲得所有這些消息並提供個正確的窗口過程?應用程序需要一個循環來獲取消息並把它們發送到正確的窗口。對於創建窗口的每個線程,操作系統創建一個窗口消息隊列,這個隊列保存所有在這個線程創建的窗口消息。在你的程序裏,隊列本身是隱藏的,你不能直接操縱隊列,但你可以調用GetMessage函數把消息從隊列拉出來。

MSG msg;
GetMessage(&msg, NULL, 0, 0);

這個函數從隊列頭部刪除第一條消息,如果隊列是空的,直到另一個功能塊的消息進行排隊。事實上,GetMessage阻塞並不會使你的程序沒有反應。如果沒有消息,程序不做任何事情。如果你需要執行後臺處理,你可以創建額外的線程繼續運行,而GetMessage函數等待另一條消息。(查看寫窗口過程)

GetMessage函數的第一個參數是一個MSG結構的地址,如果函數調用成功,它填充MSG結構有關的消息中的信息,包括目標窗口和消息代碼。其它三個參數讓你能夠從消息隊列中進行過濾。在幾乎所有的情況下,將參數設置爲0(零)。

雖然MSG結構包含消息有關的信息,你幾乎不用直接檢查這種結構,相反,你要把它直接傳遞給兩個函數
TranslateMessage(&msg); 
DispatchMessage(&msg);

TranslateMessage函數與鍵盤輸入關聯,它把鍵盤的鍵擊(鍵按下,按鍵放開)轉換成字符,你不需要真正的知道這個函數是如何工作的,只需要知道在調用DispatchMessage函數之前調用它。如果你需要更深入的瞭解,MSDN文檔的鏈接會給你更多的信息。
 
DispatchMessage函數告訴操作系統調用消息的目標窗口的窗口過程,換句話說,操作系統查找窗口表的窗口句柄,發現與窗口關聯的函數指針,並調用函數。
例如,假設用戶按下鼠標左鍵,這引起一連串的事件:
  1. 操作系統在消息隊列放置一個WM_LBUTTONDOWN消息。
  2. 你的程序調用GetMessage函數。
  3. GetMessage中隊列中提取WM_LBUTTONDOWN並填充MSG結構。
  4. 你的程序調用TranslateMessage和DispatchMessage函數。
  5. 在DispatchMessage,操作系統調用你的窗口過程。
  6. 你的窗口函數可以響應消息或忽略它。

當窗口函數返回,它將返回到DispatchMessage,下一條消息再回到消息循環。只要你的程序在運行,消息將繼續到達隊列。因此,你需要一個循環,不斷的從隊列中提取消息並調度它們。你可能想到的循環執行以下操作:

//警告:實際上不要這樣寫循環
while (1)      
{
    GetMessage(&msg, NULL, 0,  0);
    TranslateMessage(&msg); 
    DispatchMessage(&msg);
}


正如以上所寫,這個循環永遠不會結束。這是GetMessage函數放在循環內的返回值。正常情況下,GetMessage函數返回一個非零值。當你想要退出程序和中斷消息循環,簡單的調用PostQuitMessage函數。

PostQuitMessage(0);

 

PostQuitMessage函數將一個WM_QUIT消息放入隊列,WM_QUIT是一條特殊的消息,它引起GetMessage函數返回零值,消息循環結束的信號。這是修改後的消息循環:

// 正確的消息循環

MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}


只要GetMessage函數返回非零值,在while循環中表達式的值就是真值,在調用PostQuitMessage函數後,表達式值爲假值程序跳出循環。(這種行爲有一個有趣的結果,你的窗口過程從未收到WM_QUIT消息,所以並不需要在窗口過程中爲這個消息使用case語句。)

下一個明顯的問題是:你應該在什麼時候調用PostQuitMessage函數?我們回到本主題中的窗口關閉的問題。但我們首先要寫我們的窗口過程。

 發佈消息和發送消息

上一節談到進入隊列的消息,在某些情況下,操作系統將繞過消息隊列,直接調用窗口過程。

進行這種區分的術語有可能造成混淆:

發佈(Post)一條消息意味着消息在消息隊列中,並通過消息循環調度(GetMessage和DispatchMessage)。

發送(Send)一條消息意味着跳過消息隊列,操作系統直接調用窗口過程。

現在,區別不是很重要。窗口過程處理所有消息,但某些消息繞過消息隊列,直接進入你的窗口過程。然而,如果你的應用程序窗口之間進行通訊它可以有所作爲。你可以在關於消息和消息隊列(About Messages and Message Queues)的主題中找到更深入的討論。

 

下一節:書寫窗口過程

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