首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

標題列加入按鈕:非客戶區訊息處理 (WM_NCPAINT/WM_NCHITTEST) 完整實作

     編輯:關於C語言

核心觀念

標題列屬於**非客戶區 (Non-Client Area)**,無法直接放置子視窗控制項。所謂「標題列按鈕」實為在非客戶區繪製按鈕外觀,並透過滑鼠訊息判斷點擊區域模擬按鈕行為。

關鍵非客戶區訊息

MFC 實作架構 (以 CFrameWnd 衍生類為例)

1. 成員變數


class CMainFrame : public CFrameWnd {
    CRect m_rectCaptionBtn;   // 按鈕在非客戶區座標 (螢幕座標)
    BOOL m_bBtnPressed;       // 按下狀態
    BOOL m_bBtnHot;           // 懸停狀態
    BOOL m_bBtnEnabled;       // 啟用狀態
    // ...
};

2. WM_NCCALCSIZE 保留按鈕空間 (可選)

若需在標題列右側系統按鈕 (最小/最大/關閉) 左側放置按鈕,需調整非客戶區客戶矩形,或直接在 WM_NCPAINT 計算位置 (較簡單)。

3. WM_NCHITTEST 定義按鈕熱區


LRESULT CMainFrame::OnNcHitTest(CPoint point) {
    // point 為螢幕座標
    if (m_bBtnEnabled && m_rectCaptionBtn.PtInRect(point)) {
        return HTBUTTON; // 告知系統此區域為按鈕
    }
    return CFrameWnd::OnNcHitTest(point); // 其他區域預設處理
}

4. WM_NCPAINT 繪製按鈕


void CMainFrame::OnNcPaint() {
    CWindowDC dc(this); // 非客戶區 DC
    // 或使用 GetWindowDC(m_hWnd) + ReleaseDC

    // 計算按鈕矩形 (右上角系統按鈕左側)
    CRect rectWindow;
    GetWindowRect(&rectWindow);
    // 系統按鈕寬度 GetSystemMetrics(SM_CXSIZE) ~ 系統邊框寬度
    int btnWidth = 20, btnHeight = 18;
    m_rectCaptionBtn.SetRect(
        rectWindow.right - GetSystemMetrics(SM_CXSIZE)*3 - btnWidth - 4,
        rectWindow.top + GetSystemMetrics(SM_CYSIZEFRAME) + 2,
        rectWindow.right - GetSystemMetrics(SM_CXSIZE)*3 - 4,
        rectWindow.top + GetSystemMetrics(SM_CYSIZEFRAME) + 2 + btnHeight
    );

    // 繪製按鈕邊框
    UINT uEdge = m_bBtnPressed ? EDGE_SUNKEN : (m_bBtnHot ? EDGE_RAISED : EDGE_RAISED);
    if (!m_bBtnEnabled) uEdge = EDGE_RAISED; // 停用態同凸起但灰色文字
    dc.DrawEdge(&m_rectCaptionBtn, uEdge, BF_RECT | BF_SOFT | BF_MIDDLE);

    // 繪製按鈕文字/圖示 (如 "?" 說明圖示)
    CFont* pOldFont = dc.SelectObject(&afxGlobalData.fontDefault); // 或自訂字型
    dc.SetBkMode(TRANSPARENT);
    dc.SetTextColor(m_bBtnEnabled ? GetSysColor(COLOR_BTNTEXT) : GetSysColor(COLOR_GRAYTEXT));
    dc.DrawText(_T("?"), &m_rectCaptionBtn, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
    dc.SelectObject(pOldFont);
}

5. 滑鼠互動狀態機


void CMainFrame::OnNcLButtonDown(UINT nHitTest, CPoint point) {
    if (nHitTest == HTBUTTON && m_bBtnEnabled) {
        m_bBtnPressed = TRUE;
        SetCapture(); // 捕獲滑鼠,確保收到 Up 即使移出區域
        RedrawWindow(NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW); // 重繪非客戶區
    } else {
        CFrameWnd::OnNcLButtonDown(nHitTest, point);
    }
}

void CMainFrame::OnNcLButtonUp(UINT nHitTest, CPoint point) {
    if (m_bBtnPressed) {
        m_bBtnPressed = FALSE;
        ReleaseCapture();
        if (m_rectCaptionBtn.PtInRect(point)) {
            // 執行按鈕動作
            OnCaptionButtonClick();
        }
        RedrawWindow(NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW);
    } else {
        CFrameWnd::OnNcLButtonUp(nHitTest, point);
    }
}

void CMainFrame::OnNcMouseMove(UINT nHitTest, CPoint point) {
    BOOL bHot = m_bBtnEnabled && m_rectCaptionBtn.PtInRect(point);
    if (bHot != m_bBtnHot) {
        m_bBtnHot = bHot;
        RedrawWindow(NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW);
    }
    CFrameWnd::OnNcMouseMove(nHitTest, point);
}

void CMainFrame::OnNcLButtonDblClk(UINT nHitTest, CPoint point) {
    if (nHitTest == HTBUTTON) return; // 忽略按鈕區域雙擊
    CFrameWnd::OnNcLButtonDblClk(nHitTest, point);
}

void CMainFrame::OnNcRButtonDown(UINT nHitTest, CPoint point) {
    if (nHitTest == HTBUTTON) return; // 阻擋系統選單
    CFrameWnd::OnNcRButtonDown(nHitTest, point);
}

void CMainFrame::OnNcActivate(BOOL bActive) {
    CFrameWnd::OnNcActivate(bActive);
    // 可根據 bActive 更新按鈕啟用狀態或視覺
    RedrawWindow(NULL, NULL, RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW);
}

6. 按鈕動作處理


void CMainFrame::OnCaptionButtonClick() {
    // 例如:顯示說明、切換工具列、關於對話框...
    AfxGetApp()->WinHelp(HID_BASE_RESOURCE + IDR_MAINFRAME, HELP_CONTEXT);
}

訊息映射


BEGIN_MESSAGE_MAP(CMainFrame, CFrameWnd)
    ON_WM_NCHITTEST()
    ON_WM_NCPAINT()
    ON_WM_NCLBUTTONDOWN()
    ON_WM_NCLBUTTONUP()
    ON_WM_NCMOUSEMOVE()
    ON_WM_NCLBUTTONDBLCLK()
    ON_WM_NCRBUTTONDOWN()
    ON_WM_NCACTIVATE()
END_MESSAGE_MAP()

高 DPI / 主題 考量

  • 使用 GetSystemMetricsForDpi (Win10 1607+) 取得正確系統按鈕/邊框尺寸。
  • 啟用視覺主題 (Manifest) 時,標題列繪製由主題引擎處理,WM_NCPAINT 可能不被呼叫。需呼叫 SetWindowTheme(m_hWnd, L"", L"") 禁用標題列主題,或使用 DwmExtendFrameIntoClientArea 擴展客戶區至標題列 (現代做法)。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
訊息用途
WM_NCPAINT繪製非客戶區 (標題列、邊框),此處繪製按鈕外觀
WM_NCHITTEST告知系統滑鼠位於非客戶區何處,**關鍵:返回 HTBUTTON 自定義區域**
WM_NCLBUTTONDOWN非客戶區左鍵按下,模擬按鈕按下狀態
WM_NCLBUTTONUP非客戶區左鍵釋放,觸發按鈕動作
WM_NCLBUTTONDBLCLK非客戶區雙擊,防止標題列雙擊最大化/還原干擾按鈕區域
WM_NCRBUTTONDOWN非客戶區右鍵,防止按鈕區域彈出系統選單
WM_NCMOUSEMOVE非客戶區滑鼠移動,追蹤懸停/離開按鈕區域更新外觀
WM_NCACTIVATE視窗啟用/停用切換,更新按鈕啟用/停用視覺狀態