透過重寫 OnNcHitTest 讓客戶區回傳 HTCAPTION 可實現整視窗拖動,但會導致 WM_CONTEXTMENU (右鍵選單) 失效。本文提供改良方案:在 OnNcRButtonUp 中判斷滑鼠實際位置並手動發送 WM_CONTEXTMENU,完美兼容拖動與右鍵選單。
OnNcHitTest 強制回傳 HTCAPTION 會誤導系統認為滑鼠在標題列,導致系統不發送 WM_CONTEXTMENU。且 GetKeyState 檢測右鍵狀態的方案需自行處理左右鍵交換,不夠穩健。
關鍵洞察:WM_NCHITTEST 發生時滑鼠鍵已釋放,但 WM_NCRBUTTONUP 發生時滑鼠仍在客戶區。利用此時機手動觸發選單。
// 標題列點擊測試:客戶區視同標題列以啟用拖動
UINT CMyDialog::OnNcHitTest(CPoint point) {
UINT uHit = CDialog::OnNcHitTest(point);
if (uHit == HTCLIENT) return HTCAPTION; // 核心:客戶區偽裝標題列
return uHit;
}
// 非客戶區右鍵釋放:檢測是否為「偽裝」標題列點擊
void CMyDialog::OnNcRButtonUp(UINT nHitTest, CPoint point) {
// nHitTest==HTCAPTION 且實際座標在客戶區內 → 手動發送右鍵選單
if (nHitTest == HTCAPTION && PtInClientRect(point)) {
SendMessage(WM_CONTEXTMENU, (WPARAM)m_hWnd, MAKELPARAM(point.x, point.y));
} else {
CDialog::OnNcRButtonUp(nHitTest, point); // 真正標題列:預設處理 (系統選單)
}
}
// 輔助:判斷螢幕座標是否落在客戶區
BOOL CMyDialog::PtInClientRect(CPoint ptScreen) {
CRect rcClient;
GetClientRect(&rcClient);
ClientToScreen(&rcClient);
return rcClient.PtInRect(ptScreen);
}
OnNcHitTest 回傳 HTCAPTION → 系統啟動視窗移動WM_NCRBUTTONUP (nHitTest=HTCAPTION, point 在客戶區)OnNcRButtonUp 檢測到「偽裝標題列」 → 手動 SendMessage(WM_CONTEXTMENU) → 選單正常彈出OnNcRButtonUp 呼叫基類 → 系統選單正常彈出CDialog、CFrameWnd、CView 等所有視窗類別
圖一 程式運行畫面:客戶區拖動與右鍵選單雙重功能正常運作
同樣原理可用於:
WM_NCLBUTTONDBLCLK)此技巧是 MFC 視窗訊息機制深度理解的典型應用,展示了「攔截-判斷-轉發」模式在解決系統行為衝突時的優雅性。