像 QuickTime、金山詞霸等軟體擁有獨特視覺風格,核心在於自繪非客戶區:標題列、邊框、系統按鈕 (最小化/最大化/關閉) 全由程式繪製。本文分享自繪視窗核心原理與實作細節。
攔截 WM_NCPAINT (非客戶區重繪)、WM_NCLBUTTONDOWN (非客戶區滑鼠按下)、WM_NCMOUSEMOVE (滑鼠移動)、WM_SIZE (大小改變) 等訊息,完全接管視窗外觀繪製與互動邏輯。
需提供以下位圖資源 (紅框標示區域):

void DrawBitmap(CDC* pDC, int x, int y, UINT nResID)
{
CBitmap bmp; bmp.LoadBitmap(nResID);
BITMAP bm; bmp.GetBitmap(&bm);
CDC memDC; memDC.CreateCompatibleDC(pDC);
CBitmap* pOld = memDC.SelectObject(&bmp);
pDC->BitBlt(x, y, bm.bmWidth, bm.bmHeight, &memDC, 0, 0, SRCCOPY);
memDC.SelectObject(pOld);
}
void CMyDialog::OnNcPaint()
{
CWindowDC dc(this);
CRect rcWindow; GetWindowRect(&rcWindow);
CRect rcClient; GetClientRect(&rcClient);
ClientToScreen(&rcClient);
// 計算非客戶區矩形
CRect rcTitle(0, 0, rcWindow.Width(), GetSystemMetrics(SM_CYCAPTION));
CRect rcLeftBorder(0, rcTitle.bottom, GetSystemMetrics(SM_CXFRAME), rcWindow.Height() - rcTitle.bottom - GetSystemMetrics(SM_CYFRAME));
// ... 類似計算右邊框、底邊框、系統按鈕區域
// 繪製標題列背景 (左中右三段)
DrawBitmap(&dc, rcTitle.left, rcTitle.top, IDB_TITLE_LEFT);
for (int x = rcTitle.left + LEFT_W; x < rcTitle.right - RIGHT_W; x += MID_W)
DrawBitmap(&dc, x, rcTitle.top, IDB_TITLE_MID);
DrawBitmap(&dc, rcTitle.right - RIGHT_W, rcTitle.top, IDB_TITLE_RIGHT);
// 繪製系統按鈕 (依滑鼠狀態選擇位圖)
// 繪製邊框 ...
}
void CMyDialog::OnNcLButtonDown(UINT nHitTest, CPoint point)
{
// 判斷點擊位置:關閉/最大化/最小化按鈕
// 設定按鈕狀態為「按下」,Invalidate 觸發重繪
// 若點擊標題列非按鈕區域,呼叫 Default() 讓系統處理拖曳移動
}
void CMyDialog::OnNcMouseMove(UINT nHitTest, CPoint point)
{
// 追蹤滑鼠是否進入系統按鈕區域
// 更新按鈕狀態 (正常/懸停/按下),局部重繪
}
void CMyDialog::OnSize(UINT nType, int cx, int cy)
{
CDialog::OnSize(nType, cx, cy);
// 重新計算客戶區內控制項位置大小
RepositionControls(cx, cy);
}

WM_NCPAINT 等)ClientToScreen、ScreenToClient、GetWindowRect、GetClientRect 正確運用ReleaseDC,記憶體 DC 需 DeleteDC將自繪邏輯子類化為可重用基類,支援多視窗統一風格、控制項自動佈局、隨視窗縮放調整。敬請期待續集。
作者聯繫:QQ 34544052 / Email: Song_0962@sina.com