使用 Win32 和 C++) (入門視窗訊息

GUI 應用程式必須回應使用者和作業系統的事件。

  • 來自使用者的事件 包括某人可以與您的程式互動的所有方式:按一下滑鼠、按鍵筆劃、觸控式螢幕手勢等等。
  • 來自作業系統的事件 包含會影響程式列為的任何「外部」程式。 例如,使用者可能會插入新的硬體裝置,或Windows可能會進入低電源狀態, (睡眠或休眠) 。

這些事件可以在程式執行時隨時發生,幾乎任何順序。 如何建構程式,其執行流程無法事先預測?

若要解決此問題,Windows使用訊息傳遞模型。 作業系統會透過將訊息傳遞至應用程式視窗來與應用程式視窗通訊。 訊息只是指定特定事件的數值程式碼。 例如,如果使用者按下滑鼠左鍵,視窗會收到具有下列訊息碼的訊息。

#define WM_LBUTTONDOWN    0x0201

有些訊息有與其相關聯的資料。 例如, WM_LBUTTONDOWN 訊息包含滑鼠游標的 x 座標和 Y 座標。

若要將訊息傳遞至視窗,作業系統會呼叫為該視窗註冊的視窗程式。 (現在您知道視窗程式適用于.)

訊息迴圈

應用程式會在執行時收到數千則訊息。 (請考慮每個按鍵和滑鼠按鍵按一下都會產生訊息。) 此外,應用程式可以有數個視窗,每個視窗都有自己的視窗程式。 程式如何接收所有這些訊息,並將其傳遞至正確的視窗程式? 應用程式需要迴圈來擷取訊息,並將其分派至正確的視窗。

針對每個建立視窗的執行緒,作業系統會建立視窗訊息的佇列。 此佇列會保存在該執行緒上建立之所有視窗的訊息。 佇列本身會隱藏在您的程式中。 您無法直接操作佇列。 不過,您可以藉由呼叫 GetMessage 函式,從佇列提取訊息。

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

此函式會從佇列的前端移除第一則訊息。 如果佇列是空的,函式會封鎖直到另一個訊息排入佇列為止。 GetMessage區塊不會讓您的程式沒有回應。 如果沒有訊息,程式就不會執行任何動作。 如果您必須執行背景處理,您可以建立其他執行緒,以在 GetMessage 等候另一則訊息時繼續執行。 (請參閱 避免視窗程式中的瓶頸。)

GetMessage的第一個參數是MSG結構的位址。 如果函式成功,它會在 MSG 結構中填入訊息的相關資訊。 這包括目標視窗和訊息碼。 其他三個參數可讓您篩選從佇列取得的訊息。 在幾乎所有情況下,您會將這些參數設定為零。

雖然 MSG 結構包含訊息的相關資訊,但您幾乎永遠不會直接檢查此結構。 相反地,您會將它直接傳遞至兩個其他函式。

TranslateMessage(&msg); 
DispatchMessage(&msg);

TranslateMessage函式與鍵盤輸入相關。 它會將按鍵 (向下轉譯成字元,將按鍵向上) 。 您不一定必須知道此函式的運作方式;只要記得在 DispatchMessage之前呼叫它即可。 如果您想要的話,MSDN 檔的連結會為您提供詳細資訊。

DispatchMessage函式會告知作業系統呼叫訊息目標視窗的視窗程式。 換句話說,作業系統會在視窗的資料表中查閱視窗控制碼、尋找與視窗相關聯的函式指標,並叫用函式。

例如,假設使用者按下滑鼠左鍵。 這會導致事件鏈結:

  1. 作業系統會將 WM_LBUTTONDOWN 訊息放在訊息佇列上。
  2. 您的程式會呼叫 GetMessage 函式。
  3. GetMessage 會從佇列提取 WM_LBUTTONDOWN 訊息,並填入 MSG 結構。
  4. 您的程式會呼叫 TranslateMessageDispatchMessage 函式。
  5. DispatchMessage內,作業系統會呼叫您的視窗程式。
  6. 您的視窗程式可以回應訊息或忽略它。

當視窗程式傳回時,它會返回 DispatchMessage。 這會傳回下一個訊息的訊息迴圈。 只要程式正在執行,訊息就會繼續抵達佇列。 因此,您必須有迴圈,該迴圈會持續從佇列提取訊息並加以分派。 您可以將迴圈視為執行下列動作:

// WARNING: Don't actually write your loop this way.

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

當然,如此一來,這個迴圈永遠不會結束。 這就是 GetMessage 函式的傳回值進入的位置。 一般而言, GetMessage 會傳回非零值。 當您想要結束應用程式並中斷訊息迴圈時,請呼叫 PostQuitMessage 函式。

        PostQuitMessage(0);

PostQuitMessage函式會將WM_QUIT訊息放在訊息佇列上。 WM_QUIT 是特殊的訊息:這會導致 GetMessage 傳回零,併發出訊息迴圈結尾的訊號。 以下是修訂的訊息迴圈。

// Correct.

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

只要 GetMessage 傳回非零值, while 迴圈中的運算式就會評估為 true。 呼叫 PostQuitMessage之後,運算式會變成 false,而且程式會中斷迴圈。 (此行為的其中一個有趣結果,就是您的視窗程式永遠不會收到 WM_QUIT 訊息。因此,您不需要在視窗 procedure.) 中擁有此訊息的 case 語句

下一個明顯的問題是何時呼叫 PostQuitMessage。 我們將在 關閉視窗主題中返回這個問題,但首先我們必須撰寫視窗程式。

已張貼的訊息與已傳送的訊息

上一節已討論進入佇列的訊息。 有時候,作業系統會直接呼叫視窗程式,略過佇列。

此區別的術語可能會造成混淆:

  • 張貼 訊息表示訊息會進入訊息佇列,並透過訊息迴圈分派, (GetMessageDispatchMessage) 。
  • 傳送 訊息表示訊息會略過佇列,而作業系統會直接呼叫視窗程式。

目前,差異並不重要。 視窗程式會處理所有訊息。 不過,有些訊息會略過佇列,並直接移至您的視窗程式。 不過,如果您的應用程式在視窗之間通訊,可能會產生差異。 您可以在 關於訊息和訊息佇列主題中找到此問題的更徹底討論。

下一個

撰寫視窗程式