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) 等獲取標準按鈕尺寸
}
CWindowDC vs GetWindowDCCWindowDC dc(this):MFC 封裝,構造時 GetWindowDC,析構時自動 ReleaseDC,異常安全GetWindowRect 返回**屏幕座標**OffsetRect(-left, -top) 將屏幕座標轉為窗口座標WM_NCPAINT 觸發時,Windows 已在 DC 設置裁剪區,**僅包含非客戶區**(客戶區被排除)。因此:
GetClipBox 獲取實際裁剪矩形
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()
OnNcPaint 中調用 Default():會觸發默認繪製覆蓋你的自定義內容GetSystemMetrics 返回值需配合 GetDpiForWindow 縮放,或使用 GetSystemMetricsForDpiInitCommonControlsEx),系統可能忽略部分非客戶區繪製,需在清單文件聲明 dpiAware 或調用 SetProcessDpiAwarenessOnNcPaint 做複雜計算,預計算按鈕矩形存成員變量