使用訊息和消息佇列
下列程式代碼範例示範如何執行與 Windows 訊息和消息佇列相關聯的下列工作。
建立訊息迴圈
系統不會自動為每個線程建立消息佇列。 相反地,系統只會針對執行需要消息佇列之作業的線程建立消息佇列。 如果線程建立一或多個視窗,則必須提供訊息迴圈;此訊息循環會從線程的訊息佇列擷取訊息,並將其分派至適當的窗口程式。
因為系統會將訊息導向應用程式中的個別窗口,線程必須在啟動其訊息迴圈之前建立至少一個視窗。 大部分的應用程式都包含建立視窗的單一線程。 一般應用程式會為其主視窗註冊窗口類別、建立和顯示主視窗,然後啟動其訊息迴圈,全部都在 WinMain 函式中。
您可以使用 GetMessage 和 DispatchMessage 函式來建立訊息迴圈。 如果您的應用程式必須從使用者取得字元輸入,請在 迴圈中包含 TranslateMessage 函式。 TranslateMessage 將虛擬密鑰訊息轉譯成字元訊息。 下列範例顯示簡單 Windows 應用程式 WinMain 函式中的訊息迴圈。
HINSTANCE hinst;
HWND hwndMain;
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPSTR lpszCmdLine, int nCmdShow)
{
MSG msg;
BOOL bRet;
WNDCLASS wc;
UNREFERENCED_PARAMETER(lpszCmdLine);
// Register the window class for the main window.
if (!hPrevInstance)
{
wc.style = 0;
wc.lpfnWndProc = (WNDPROC) WndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = LoadIcon((HINSTANCE) NULL,
IDI_APPLICATION);
wc.hCursor = LoadCursor((HINSTANCE) NULL,
IDC_ARROW);
wc.hbrBackground = GetStockObject(WHITE_BRUSH);
wc.lpszMenuName = "MainMenu";
wc.lpszClassName = "MainWndClass";
if (!RegisterClass(&wc))
return FALSE;
}
hinst = hInstance; // save instance handle
// Create the main window.
hwndMain = CreateWindow("MainWndClass", "Sample",
WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_USEDEFAULT, (HWND) NULL,
(HMENU) NULL, hinst, (LPVOID) NULL);
// If the main window cannot be created, terminate
// the application.
if (!hwndMain)
return FALSE;
// Show the window and paint its contents.
ShowWindow(hwndMain, nCmdShow);
UpdateWindow(hwndMain);
// Start the message loop.
while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// Return the exit code to the system.
return msg.wParam;
}
下列範例顯示使用加速鍵並顯示無模式對話框之執行緒的訊息迴圈。 TranslateAccelerator 或 IsDialogMessage 傳回 TRUE(表明訊息已被處理)時,將不會呼叫 TranslateMessage 和 DispatchMessage。 這是因為 TranslateAccelerator 和 IsDialogMessage 執行了所有必要的翻譯和訊息傳遞。
HWND hwndMain;
HWND hwndDlgModeless = NULL;
MSG msg;
BOOL bRet;
HACCEL haccel;
//
// Perform initialization and create a main window.
//
while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
if (hwndDlgModeless == (HWND) NULL ||
!IsDialogMessage(hwndDlgModeless, &msg) &&
!TranslateAccelerator(hwndMain, haccel,
&msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
}
檢查消息佇列
有時候,應用程式必須從線程的訊息迴圈外部檢查線程消息佇列的內容。 例如,如果應用程式的視窗程序執行冗長的繪圖作業,您可能希望使用者能夠中斷作業。 除非應用程式在作期間定期檢查滑鼠和鍵盤訊息的訊息佇列,否則在作業完成之前不會回應用戶輸入。 原因是線程訊息迴圈中的 DispatchMessage 函式在視窗程式完成處理訊息之前不會傳回。
您可以使用 PeekMessage 函式,在長時間作業期間檢查消息佇列。 PeekMessage 類似於 getMessage函式;兩者都會檢查訊息佇列中是否有符合篩選準則的訊息,然後將訊息複製到 MSG 結構。 這兩個函式的主要差異在於,GetMessage 不會傳回,直到符合篩選準則的訊息放在佇列中,而 不論訊息是否在佇列中,PeekMessage 都會立即傳回。
下列範例示範如何在長時間作業期間,使用 PeekMessage 來檢查訊息佇列中的滑鼠點擊和鍵盤輸入。
HWND hwnd;
BOOL fDone;
MSG msg;
// Begin the operation and continue until it is complete
// or until the user clicks the mouse or presses a key.
fDone = FALSE;
while (!fDone)
{
fDone = DoLengthyOperation(); // application-defined function
// Remove any messages that may be in the queue. If the
// queue contains any mouse or keyboard
// messages, end the operation.
while (PeekMessage(&msg, hwnd, 0, 0, PM_REMOVE))
{
switch(msg.message)
{
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
case WM_KEYDOWN:
//
// Perform any required cleanup.
//
fDone = TRUE;
}
}
}
其他函式,包括 GetQueueStatus 和 GetInputState,也可讓您檢查線程消息佇列的內容。 GetQueueStatus 會傳回旗標陣列,指出佇列中的訊息類型;使用它是探索佇列是否包含任何訊息的最快速方式。 GetInputState 如果佇列包含滑鼠或鍵盤訊息,會傳回 TRUE。 這兩個函式都可以用來判斷佇列是否包含需要處理的訊息。
張貼訊息
您可以使用 PostMessage 函式,將訊息張貼至消息佇列。 PostMessage 在線程消息佇列結尾處放置訊息,並立即傳回,而不會等待線程處理訊息。 函式的參數包括視窗句柄、訊息標識碼和兩個訊息參數。 系統會將這些參數複製到 MSG 結構、填入 時間,並 pt 結構的成員,並將結構放在消息佇列中。
系統會使用與 PostMessage 函式一起傳遞的視窗句柄,來判斷應接收訊息的線程消息佇列。 如果句柄是 HWND_TOPMOST,系統會將訊息發送至所有頂層視窗的執行緒的訊息佇列。
您可以使用 PostThreadMessage 函式,將訊息張貼至特定線程消息佇列。 PostThreadMessage 類似於 PostMessage,但第一個參數是線程標識碼,而不是視窗句柄。 您可以呼叫 getCurrentThreadId函式來擷取線程標識碼。
使用 PostQuitMessage 函式結束訊息迴圈。 PostQuitMessage 將 WM_QUIT 訊息張貼至目前執行的線程。 線程的訊息迴圈會在遇到 WM_QUIT 訊息時終止,並將控制權傳回給系統。 應用程式通常會呼叫 PostQuitMessage,以回應 WM_DESTROY 訊息,如下列範例所示。
case WM_DESTROY:
// Perform cleanup tasks.
PostQuitMessage(0);
break;
傳送訊息
SendMessage 函式是用來將訊息直接傳送至視窗程式。 SendMessage 呼叫視窗程式,並等候該程式處理訊息並傳回結果。
訊息可以傳送至系統中的任何視窗,只需要提供一個視窗句柄。 系統會使用控制代碼來判斷哪個窗口處理程序應接收該訊息。
處理可能從另一個線程傳送的訊息之前,視窗程序應該先呼叫 InSendMessage 函式。 如果此函式傳回 TRUE ,則視窗過程應該在導致線程產生控件的任何函式之前呼叫 ReplyMessage,如下列範例所示。
case WM_USER + 5:
if (InSendMessage())
ReplyMessage(TRUE);
DialogBox(hInst, "MyDialogBox", hwndMain, (DLGPROC) MyDlgProc);
break;
一些訊息可以傳送至對話框中的控制件。 這些控件訊息會設定控件的外觀、行為和內容,或擷取控件的相關信息。 例如,CB_ADDSTRING 訊息可以將字串新增至聯合方塊,而 BM_SETCHECK 訊息可以設定核取方塊或單選按鈕的選擇狀態。
使用 SendDlgItemMessage 函式將訊息傳送至控件,並指定控件的識別碼,以及包含控件之對話框視窗的句柄。 下列範例取自對話框程式,會將下拉式方塊編輯控件中的字串複製到其清單框中。 此範例使用 SendDlgItemMessage 將 CB_ADDSTRING 訊息發送到組合框中。
HWND hwndCombo;
int cTxtLen;
PSTR pszMem;
switch (uMsg)
{
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDD_ADDCBITEM:
// Get the handle of the combo box and the
// length of the string in the edit control
// of the combo box.
hwndCombo = GetDlgItem(hwndDlg, IDD_COMBO);
cTxtLen = GetWindowTextLength(hwndCombo);
// Allocate memory for the string and copy
// the string into the memory.
pszMem = (PSTR) VirtualAlloc((LPVOID) NULL,
(DWORD) (cTxtLen + 1), MEM_COMMIT,
PAGE_READWRITE);
GetWindowText(hwndCombo, pszMem,
cTxtLen + 1);
// Add the string to the list box of the
// combo box and remove the string from the
// edit control of the combo box.
if (pszMem != NULL)
{
SendDlgItemMessage(hwndDlg, IDD_COMBO,
CB_ADDSTRING, 0,
(DWORD) ((LPSTR) pszMem));
SetWindowText(hwndCombo, (LPSTR) NULL);
}
// Free the memory and return.
VirtualFree(pszMem, 0, MEM_RELEASE);
return TRUE;
//
// Process other dialog box commands.
//
}
//
// Process other dialog box messages.
//
}