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

VC++ 視窗客製化繪製實戰:零基礎實現非標準標題列、按鈕與邊框美化

     編輯:關於VC++

前言

Windows 預設視窗樣式單一且難以美化。許多開發者嘗試透過 GetSystemMetrics 計算非客戶區尺寸、攔截 WM_NCPAINT 繪製,卻因座標換算複雜而望而卻步。本文提供一套「以客戶區座標為基準、直接在 WM_PAINT 中繪製整個視窗外觀」的簡易方案,無需處理非客戶區訊息,即可實現自定標題列、最小/最大/關閉按鈕、邊框陰影等效果。

核心思路:放棄非客戶區,全客戶區繪製

  1. 視窗建立時指定 WS_POPUP 樣式(無標題列、無邊框),客戶區即為整個視窗。
  2. 在 OnPaint 中手動劃分區域:標題列、左/右邊框、底部邊框、三大功能按鈕。
  3. 使用位圖資源 (CBitmap) + StretchBlt 填充各區域,再繪製標題文字。
  4. 攔截 WM_NCHITTEST 回傳 HTCAPTION/HTMINBUTTON/HTMAXBUTTON/HTCLOSE/HTLEFT... 讓系統正確處理拖移、縮放、按鈕點擊。

一、區域座標計算(於 OnPaint 或 OnSize 中預先計算)

// 假設視窗客戶區寬度 cx, 高度 cy
// 標題列高度 30px,按鈕寬 20px,間距 6px
CRect rcTitle(6, 0, cx - 6, 30);                    // 標題列區域(左右留 6px 給邊框)

// 三顆按鈕由右至左排列
m_rcBtnClose.SetRect(cx - 26, 0, cx - 6, 30);
m_rcBtnMax.SetRect(cx - 46, 0, cx - 26, 30);
m_rcBtnMin.SetRect(cx - 66, 0, cx - 46, 30);

視窗內部佈局示意圖

二、OnPaint 完整繪製流程

void CMyDlg::OnPaint()
{
    CPaintDC dc(this);
    CRect rcClient; GetClientRect(&rcClient);
    int cx = rcClient.Width(), cy = rcClient.Height();

    CDC memDC; memDC.CreateCompatibleDC(&dc);
    CBitmap bmp, *pOldBmp;
    BITMAP bmInfo;

    // 1. 繪製標題列背景(拉伸貼圖)
    bmp.LoadBitmap(IDB_TITLE_TOP);
    bmp.GetBitmap(&bmInfo);
    pOldBmp = memDC.SelectObject(&bmp);
    dc.StretchBlt(6, 0, cx - 12, 30, &memDC, 0, 0, bmInfo.bmWidth, bmInfo.bmHeight, SRCCOPY);
    memDC.SelectObject(pOldBmp); bmp.DeleteObject();

    // 2. 繪製標題文字
    WriteCaption(m_clrCaption, m_strCaption); // 自訂函數,TextOut 至 rcTitle 區域

    // 3. 繪製左右兩側標題列邊框裝飾
    bmp.LoadBitmap(IDB_DOWN);
    bmp.GetBitmap(&bmInfo);
    pOldBmp = memDC.SelectObject(&bmp);
    dc.StretchBlt(0, 0, 6, 30, &memDC, 0, 0, bmInfo.bmWidth, bmInfo.bmHeight, SRCCOPY); // 左
    dc.StretchBlt(cx - 6, 0, 6, 30, &memDC, 0, 0, bmInfo.bmWidth, bmInfo.bmHeight, SRCCOPY); // 右
    memDC.SelectObject(pOldBmp); bmp.DeleteObject();

    // 4. 繪製三大按鈕(以常態圖示為例)
    bmp.LoadBitmap(IDB_NORMAL);
    bmp.GetBitmap(&bmInfo);
    pOldBmp = memDC.SelectObject(&bmp);
    dc.StretchBlt(m_rcBtnMin.left, m_rcBtnMin.top + 4, 20, 21, &memDC, 0, 0, bmInfo.bmWidth, bmInfo.bmHeight, SRCCOPY);
    dc.StretchBlt(m_rcBtnMax.left, m_rcBtnMax.top + 4, 20, 21, &memDC, 0, 0, bmInfo.bmWidth, bmInfo.bmHeight, SRCCOPY);
    dc.StretchBlt(m_rcBtnClose.left, m_rcBtnClose.top + 4, 20, 21, &memDC, 0, 0, bmInfo.bmWidth, bmInfo.bmHeight, SRCCOPY);
    memDC.SelectObject(pOldBmp); bmp.DeleteObject();

    // 5. 繪製左右、底部邊框(同理 StretchBlt 邊框位圖)
    // ...
}

三、WM_NCHITTEST 實現拖移與縮放

LRESULT CMyDlg::OnNcHitTest(CPoint point)
{
    ScreenToClient(&point); // 轉為客戶區座標

    if (m_rcBtnClose.PtInRect(point)) return HTCLOSE;
    if (m_rcBtnMax.PtInRect(point)) return HTMAXBUTTON;
    if (m_rcBtnMin.PtInRect(point)) return HTMINBUTTON;
    if (rcTitle.PtInRect(point)) return HTCAPTION;

    // 邊框縮放區域(寬 4px)
    CRect rcClient; GetClientRect(&rcClient);
    if (point.x < 4) return point.y < 4 ? HTTOPLEFT : (point.y > rcClient.bottom - 4 ? HTBOTTOMLEFT : HTLEFT);
    if (point.x > rcClient.right - 4) return point.y < 4 ? HTTOPRIGHT : (point.y > rcClient.bottom - 4 ? HTBOTTOMRIGHT : HTRIGHT);
    if (point.y < 4) return HTTOP;
    if (point.y > rcClient.bottom - 4) return HTBOTTOM;

    return HTCLIENT;
}

四、按鈕狀態視覺回饋

在 OnLButtonDown/OnLButtonUp/OnMouseMove 中追蹤滑鼠是否在按鈕矩形內,切換按鈕位圖(IDB_NORMAL, IDB_HOVER, IDB_PRESSED),並呼叫 InvalidateRect(&rcBtn) 重繪該區域。

五、優缺點分析與適用場景

優點缺點
邏輯直觀,座標全在客戶區,無需處理 NC 訊息失去系統原生視窗動畫(最小化/最大化動畫)
完全掌控像素級外觀,支援不規則形狀、透明、漸層需自行實現 Aero Snap、任務列縮圖預覽等現代特性
相容性極佳(Win2000+),無 DWM 依賴高 DPI 下位圖拉伸可能模糊,建議準備多解析度資源

適用於:工具類軟體、播放器、即時通訊主面板、遊戲啟動器等追求強品牌視覺識別的應用。

結語

「全客戶區繪製 + WM_NCHITTEST 擊中測試」是實現高度客製化視窗外觀的經典且高效方案。配合位圖資源與 StretchBlt,幾十行代碼即可構建專業級非標準視窗。源碼中關鍵位圖資源:IDB_TITLE_TOP(標題列背景)、IDB_DOWN(側邊裝飾)、IDB_NORMAL/HOVER/PRESSED(按鈕三態)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved