移除標題欄(WS_CAPTION)後,視窗失去原生拖曳移動能力。傳統做法需在 WM_LBUTTONDOWN/WM_MOUSEMOVE/WM_LBUTTONUP 中手動計算座標、調用 MoveWindow 或 SetWindowPos,代碼繁瑣且易出錯。
Windows 判斷滑鼠點擊位置是否屬於「標題欄」是透過 WM_NCHITTEST 回傳值:若回傳 HTCAPTION,系統自動進入視窗移動模式。我們只需讓客戶區也回傳 HTCAPTION,即可「欺騙」系統認為使用者點擊了標題欄。
// 在 BEGIN_MESSAGE_MAP 中添加
ON_WM_NCHITTEST()
// 頭文件聲明
afx_msg UINT OnNcHitTest(CPoint point);
UINT CMyDialog::OnNcHitTest(CPoint point) {
UINT nHitTest = CDialog::OnNcHitTest(point); // 先讓基類判斷
// 若基類認為在客戶區 (HTCLIENT),我們強制改為 HTCAPTION
return (nHitTest == HTCLIENT) ? HTCAPTION : nHitTest;
}
void CMyDialog::OnLButtonDown(UINT nFlags, CPoint point) {
CDialog::OnLButtonDown(nFlags, point); // 保留基類行為
// 關鍵:發送 WM_NCLBUTTONDOWN,wParam=HTCAPTION,系統即啟動拖曳
PostMessage(WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(point.x, point.y));
}
滑標點擊客戶區
│
▼
WM_NCHITTEST 返回 HTCAPTION (經過我們的攔截)
│
▼
系統認為點擊了標題欄,準備進入移動模式
│
▼
WM_LBUTTONDOWN 觸發
│
▼
PostMessage(WM_NCLBUTTONDOWN, HTCAPTION, ...)
│
▼
系統正式啟動視窗拖曳移動循環 (內部處理 WM_MOUSEMOVE/WM_LBUTTONUP)
| 方案 | 代碼量 | 相容性 | 副作用 |
|---|---|---|---|
| 手動計算座標 + MoveWindow | ~30 行 | 所有 Windows 版本 | 需自行處理邊界、多螢幕、DPI 縮放 |
| OnNcHitTest + PostMessage (本文) | ~8 行 | Win95 至 Win11 全支援 | 客戶區無法接收 WM_LBUTTONDOWN (被轉發),若需客戶區響應左鍵,需額外判斷 |
| Chrome/Firefox 風格:自定義標題欄區域 | ~50 行 | 現代 App 最佳 | 需自繪標題欄按鈕、處理最大化/最小化動畫 |
若視窗客戶區內有控件(按鈕、編輯框)需接收左鍵點擊,可在 OnNcHitTest 中進一步判斷:
UINT CMyDialog::OnNcHitTest(CPoint point) {
UINT nHitTest = CDialog::OnNcHitTest(point);
if (nHitTest == HTCLIENT) {
// 檢查滑鼠下是否有子控件
CWnd* pChild = ChildWindowFromPoint(point);
if (pChild && pChild != this) return HTCLIENT; // 有控件則不攔截
return HTCAPTION; // 空白區域才偽裝標題欄
}
return nHitTest;
}
掌握 WM_NCHITTEST 這一「非客戶區測試」機制,是 Windows 視窗進階定制的基石之一。