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

MFC 自繪視窗框架實現換膚功能:CSkinWin + CSkinButton 完整方案

     編輯:關於VC++

專案目標

實現類似 Windows Blinds 的視窗換膚:自繪標題列、邊框、最小化/最大化/關閉按鈕、自訂按鈕狀態 (正常/游標/按下/失效),支援執行期切換皮膚,相容 Windows Blinds .ini 格式。

核心類別架構

類別職責
CSkinWin自繪視窗非客戶區:標題列、四邊邊框、系統按鈕。讀取 .ini 載入位圖與參數
CSkinButton自繪按鈕控制項:四狀態位圖 (正常/焦點/按下/失效)。共享資源由 CSkinButtonResource 管理
CSkinButtonResource按鈕皮膚資源快取,避免重複載入位圖
CMyBitmap 等輔助類別 (部分來自 CodeProject)

整合步驟 (對話框專案)

  1. 主對話框類別加入成員:
    
    CSkinButtonResource m_btnRes;  // 按鈕資源
    CSkinWin m_skinWin;            // 視窗皮膚
    BOOL m_bFirst = TRUE;          // 首次標記
    CObList m_wndList;             // 按鈕實例清單 (用於銷毀時釋放)
        
  2. OnInitDialog 初始化:
    
    BOOL CMyDlg::OnInitDialog() {
        CDialog::OnInitDialog();
        m_bFirst = TRUE;
        SetSkin(_T("skin\\neostyle\\theme.ini"));
        return TRUE;
    }
        
  3. 換膚核心函式:
    
    BOOL CMyDlg::SetSkin(CString file) {
        m_skinWin.LoadSkin(file);
        m_btnRes.LoadSkin(file);
    
        if (m_bFirst) {
            m_skinWin.InstallSkin(this); // 子類別化視窗,攔截 NC 繪製訊息
            // 遞迴子視窗子類別化所有按鈕
            EnumChildWindows(m_hWnd, EnumChildProc, (LPARAM)this);
            m_bFirst = FALSE;
        }
        // 觸發非客戶區重繪
        SetWindowPos(NULL, 0, 0, 0, 0, SWP_NOSIZE|SWP_NOMOVE|SWP_FRAMECHANGED);
        return TRUE;
    }
        
  4. 子視窗枚舉與按鈕子類別化:
    
    BOOL CALLBACK EnumChildProc(HWND hwnd, LPARAM lParam) {
        char className[200];
        GetClassName(hwnd, className, 200);
        if (strcmp(className, "Button") == 0) {
            DWORD style = GetWindowLong(hwnd, GWL_STYLE) & 0xFF;
            if (style == BS_PUSHBUTTON || style == BS_DEFPUSHBUTTON) {
                ((CMyDlg*)lParam)->SubClassButton(hwnd);
            }
        }
        return TRUE;
    }
    
    BOOL CMyDlg::SubClassButton(HWND hwnd) {
        CSkinButton* btn = new CSkinButton();
        CWnd* pWnd = CWnd::FromHandlePermanent(hwnd);
        if (!pWnd) {
            btn->SubclassWindow(hwnd);
            btn->SetResource(&m_btnRes);
            m_wndList.AddTail(btn);
            return TRUE;
        }
        return FALSE;
    }
        
  5. 資源釋放:
    
    void CMyDlg::OnDestroy() {
        CDialog::OnDestroy();
        POSITION pos = m_wndList.GetHeadPosition();
        while (pos) {
            CObject* obj = m_wndList.GetNext(pos);
            if (obj->IsKindOf(RUNTIME_CLASS(CSkinButton)))
                delete (CSkinButton*)obj;
        }
    }
        

皮膚 .ini 格式 (兼容 Windows Blinds)


[Skin]
TitleHeight=28
LeftWidth=4
RightWidth=4
BottomHeight=4

[TitleBar]
Bitmap=title.bmp
CloseBtn=close.bmp
MaxBtn=max.bmp
MinBtn=min.bmp
BtnTop=2
BtnRight=4

[Button]
Normal=btn_n.bmp
Hover=btn_h.bmp
Pressed=btn_p.bmp
Disabled=btn_d.bmp

執行效果

換膚示範視窗

關鍵技術點

  • CSkinWin::InstallSkin 透過 SetWindowLong(GWL_WNDPROC) 或 MFC SubclassWindow 攔截 WM_NCPAINT, WM_NCHITTEST, WM_NCLBUTTONDOWN 等非客戶區訊息
  • CSkinButton 處理 WM_DRAWITEM (Owner-Draw) 或 WM_PAINT 自繪四狀態
  • 資源共用:CSkinButtonResource 以參考計數管理位圖,避免重複載入
  • 動態切換:SetWindowPos(..., SWP_FRAMECHANGED) 強制非客戶區重繪,無需重建視窗

已知限制

  • 僅在 Windows XP 測試 (作者環境)
  • 高 DPI 縮放下位圖可能模糊,需提供多解析度資源
  • MDI 子視窗、Common Dialog 等特殊視窗需額外處理

完整原始碼請參考本文配套下載。歡迎自由使用、修改與回饋。

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