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

VC 非客戶區繪製實戰:OnNcPaint 自定義窗口邊框、標題欄與標題按鈕

     編輯:關於VC++

核心消息處理

void CMyDialog::OnNcPaint()
{
    // 1. 獲取整個窗口 DC(含非客戶區)
    CWindowDC dc(this);  // 等價於 ::GetWindowDC(m_hWnd),自動 ReleaseDC
    
    // 2. 獲取窗口矩形(屏幕座標)→ 轉客戶區座標(左上角為 0,0)
    CRect rcWindow;
    GetWindowRect(&rcWindow);
    rcWindow.OffsetRect(-rcWindow.left, -rcWindow.top);  // 現在 rcWindow 為 (0,0,width,height)
    
    // 3. 可選:排除客戶區,僅繪製邊框/標題欄
    CRect rcClient;
    GetClientRect(&rcClient);
    // 客戶區在窗口座標中的位置需考慮邊框寬度
    // ClientToScreen(&rcClient); ScreenToClient 對非客戶區繪製較繁瑣
    // 簡化做法:直接在 dc 上繪製,Windows 會自動裁剪客戶區(WM_NCPAINT 特性)
    
    // 4. 自定義繪製邏輯
    // 繪製邊框
    dc.Draw3dRect(&rcWindow, RGB(0,120,215), RGB(0,120,215));  // 單色邊框
    
    // 繪製標題欄背景
    CRect rcCaption = rcWindow;
    rcCaption.bottom = rcCaption.top + GetSystemMetrics(SM_CYCAPTION);
    dc.FillSolidRect(&rcCaption, RGB(0, 90, 160));
    
    // 繪製標題文字
    CString strTitle;
    GetWindowText(strTitle);
    dc.SetBkMode(TRANSPARENT);
    dc.SetTextColor(RGB(255,255,255));
    CFont* pOldFont = dc.SelectObject(GetFont());
    dc.DrawText(strTitle, &rcCaption, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX);
    dc.SelectObject(pOldFont);
    
    // 繪製關閉/最大化/最小化按鈕(需自行計算位置並處理點擊)
    // 參考 GetSystemMetrics(SM_CXSIZE) 等獲取標準按鈕尺寸
}

關鍵點深度解析

1. CWindowDC vs GetWindowDC

  • CWindowDC dc(this):MFC 封裝,構造時 GetWindowDC,析構時自動 ReleaseDC,異常安全
  • 非客戶區 DC 範圍 = 整個窗口矩形(含邊框、標題欄、滾動條、菜單欄)

2. 座標系轉換

  • GetWindowRect 返回**屏幕座標**
  • 非客戶區繪製以**窗口左上角為原點 (0,0)** 的座標系
  • OffsetRect(-left, -top) 將屏幕座標轉為窗口座標

3. Windows 自動裁剪機制

WM_NCPAINT 觸發時,Windows 已在 DC 設置裁剪區,**僅包含非客戶區**(客戶區被排除)。因此:

  • 直接在整個窗口矩形繪製安全,客戶區不會被覆蓋
  • 若需精確控制,可用 GetClipBox 獲取實際裁剪矩形

4. 系統指標獲取標準尺寸


int cxBorder = GetSystemMetrics(SM_CXBORDER);       // 邊框寬度
int cyBorder = GetSystemMetrics(SM_CYBORDER);       // 邊框高度
int cxSizeFrame = GetSystemMetrics(SM_CXSIZEFRAME); // 可調整邊框寬度
int cyCaption = GetSystemMetrics(SM_CYCAPTION);     // 標題欄高度
int cxSmIcon = GetSystemMetrics(SM_CXSMICON);       // 小圖標寬度(標題欄圖標)

進階:響應非客戶區滑鼠消息

自定義標題按鈕需配合以下消息:

消息用途
WM_NCHITTEST告訴 Windows 滑鼠在非客戶區哪個部位(HTCAPTION/HTCLOSE/HTMAXBUTTON...),決定拖動/點擊行為
WM_NCLBUTTONDOWN非客戶區左鍵按下,處理按鈕按下狀態
WM_NCLBUTTONUP非客戶區左鍵彈起,觸發按鈕動作(關閉/最大化...)
WM_NCMOUSEMOVE非客戶區滑鼠移動,實現按鈕懸停高亮
WM_NCMOUSELEAVE滑鼠離開非客戶區,恢復按鈕常態

WM_NCHITTEST 實現關鍵


LRESULT CMyDialog::OnNcHitTest(CPoint point)
{
    // point 為屏幕座標,需轉客戶區座標判斷
    ScreenToClient(&point);
    
    CRect rcCloseBtn = CalculateCloseButtonRect(); // 自行計算關閉按鈕矩形(窗口座標)
    if (rcCloseBtn.PtInRect(point)) return HTCLOSE;
    
    CRect rcMaxBtn = CalculateMaxButtonRect();
    if (rcMaxBtn.PtInRect(point)) return HTMAXBUTTON;
    
    CRect rcMinBtn = CalculateMinButtonRect();
    if (rcMinBtn.PtInRect(point)) return HTMINBUTTON;
    
    CRect rcCaption = GetCaptionRect();
    if (rcCaption.PtInRect(point)) return HTCAPTION; // 允許拖動窗口
    
    return CWnd::OnNcHitTest(point); // 其他區域交給默認處理
}

完整消息映射


BEGIN_MESSAGE_MAP(CMyDialog, CDialogEx)
    ON_WM_NCPAINT()
    ON_WM_NCHITTEST()
    ON_WM_NCLBUTTONDOWN()
    ON_WM_NCLBUTTONUP()
    ON_WM_NCMOUSEMOVE()
    ON_WM_NCMOUSELEAVE()
END_MESSAGE_MAP()

避坑指南

  1. 不要在 OnNcPaint 中調用 Default():會觸發默認繪製覆蓋你的自定義內容
  2. 高 DPI 適配:所有 GetSystemMetrics 返回值需配合 GetDpiForWindow 縮放,或使用 GetSystemMetricsForDpi
  3. 主題兼容:啟用視覺主題時 (InitCommonControlsEx),系統可能忽略部分非客戶區繪製,需在清單文件聲明 dpiAware 或調用 SetProcessDpiAwareness
  4. 性能:非客戶區重繪頻率低,但避免在 OnNcPaint 做複雜計算,預計算按鈕矩形存成員變量
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved