訊息迴圈是 Windows GUI 程式的心臟。看似簡單的 GetMessage → TranslateMessage → DispatchMessage,實則暗藏健壯性陷阱、快速鍵處理、對話方塊導航、ActiveX 整合與閒置處理等進階需求。本文分五個版本層層遞進,最終呈現 WTL CMessageLoop 的工業級實作細節。
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
缺陷:GetMessage 失敗時回傳 -1,但迴圈條件視為 TRUE,導致錯誤訊息被重複處理。
BOOL bRet;
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
if (bRet == -1) { /* 錯誤處理/退出 */ }
else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
BOOL PreTranslateMessage(LPMSG pMsg) { return FALSE; }
while ((bRet = GetMessage(&msg, NULL, 0, 0)) != 0) {
if (bRet == -1) continue;
if (!PreTranslateMessage(&msg)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
PreTranslateMessage 三大職責:
TranslateAccelerator)
HACCEL hAcc = LoadAccelerator(hInst, MAKEINTRESOURCE(IDA_XXX));
BOOL PreTranslateMessage(LPMSG pMsg) {
if (TranslateAccelerator(hWnd, hAcc, pMsg)) return TRUE;
// ActiveX (如 WebBrowser) 必須轉發快速鍵
IOleInPlaceActiveObject *pObj;
if (SUCCEEDED(m_pBrowser->QueryInterface(IID_IOleInPlaceActiveObject, &pObj)) &&
S_OK == pObj->TranslateAccelerator(pMsg)) {
pObj->Release(); return TRUE;
}
return FALSE;
}
未轉發會導致 Tab 導航、Ctrl+C/V 等在內嵌瀏覽器失效。
BOOL PreTranslateMessage(LPMSG pMsg) {
if (IsDialogMessage(hDlg, pMsg)) return TRUE; // 處理 Tab/Enter/ESC 等
return FALSE;
}
IsDialogMessage 非單純查詢,會主動處理焦點切換、預設按鈕觸發等。WTL 提供可擴充的訊息迴圈類別,核心 Run() 邏輯:
PeekMessage(PM_NOREMOVE) 檢查佇列空時觸發 OnIdle,支援多 CIdleHandler。GetMessage 回傳 -1 記錄追蹤並 continue;0 則 break 退出。m_aMsgFilter 陣列,任一 PreTranslateMessage 回傳 TRUE 即攔截。IsIdleMessage 過濾 WM_MOUSEMOVE, WM_PAINT, WM_SYSTIMER 等不應觸發閒置的訊息。PreTranslateMessage、OnIdle 皆可覆寫,支援複雜 UI 框架需求。
// 關鍵片段
for(;;) {
while(bDoIdle && !::PeekMessage(&m_msg, NULL, 0, 0, PM_NOREMOVE)) {
if(!OnIdle(nIdleCount++)) bDoIdle = FALSE;
}
bRet = ::GetMessage(&m_msg, NULL, 0, 0);
if(bRet == -1) { ATLTRACE(...); continue; }
if(!bRet) break;
if(!PreTranslateMessage(&m_msg)) {
::TranslateMessage(&m_msg);
::DispatchMessage(&m_msg);
}
if(IsIdleMessage(&m_msg)) { bDoIdle = TRUE; nIdleCount = 0; }
}