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

CBitmapButton 動態載入四態位圖:突破資源限制實現換膚功能

     編輯:關於VC++

問題背景

CBitmapButton::LoadBitmaps 僅能載入**專案內嵌資源**的四態位圖(Normal/Selected/Focused/Disabled),無法直接載入磁碟檔案。若需實現「運行時換膚」、使用者自訂按鈕圖片等功能,必須繞過此限制。

解決思路:記憶體佈局計算 + 位圖分割

  1. CBitmapButton 以 protected 儲存四個 CBitmap 成員(m_bitmap, m_bitmapSel, m_bitmapFocus, m_bitmapDisabled)。
  2. 定義 public 繼承類 CGetBitmaps,利用指標算術計算四個成員在物件中的偏移,取得其位址。
  3. 用 LoadImage(LR_LOADFROMFILE|LR_CREATEDIBSECTION) 載入磁碟上**單一檔案包含四態橫向拼接**的位圖。
  4. 建立四個相容 DC,依寬度分割 BitBlt 到四個 CBitmap。

關鍵代碼

1. 取得 protected 成員位址


class CGetBitmaps : public CBitmapButton {
    CBitmapButton* m_pBtn;
public:
    CGetBitmaps(CBitmapButton* btn) : m_pBtn(btn) {}
    inline CBitmap* Nor() { return (CBitmap*)((BYTE*)m_pBtn + offsetof(CBitmapButton, m_bitmap)); }
    inline CBitmap* Sel() { return (CBitmap*)((BYTE*)m_pBtn + offsetof(CBitmapButton, m_bitmapSel)); }
    inline CBitmap* Foc() { return (CBitmap*)((BYTE*)m_pBtn + offsetof(CBitmapButton, m_bitmapFocus)); }
    inline CBitmap* Dis() { return (CBitmap*)((BYTE*)m_pBtn + offsetof(CBitmapButton, m_bitmapDisabled)); }
};

使用標準 offsetof 宏替代原文手動指標運算,更安全可移植。

2. 動態載入與分割


BOOL ChangeBitmapBtnImages(CBitmapButton& btn, LPCTSTR lpszFilename) {
    // 1. 載入磁碟四態拼接圖
    HBITMAP hBmp = (HBITMAP)LoadImage(NULL, lpszFilename, IMAGE_BITMAP, 0, 0,
        LR_LOADFROMFILE | LR_CREATEDIBSECTION);
    if (!hBmp) return FALSE;
    CBitmap src; src.Attach(hBmp);

    BITMAP bm; src.GetBitmap(&bm);
    int w = bm.bmWidth / 4;  // 假設四態等寬橫向拼接
    int h = bm.bmHeight;

    // 2. 取得四個目標 CBitmap 指標
    CGetBitmaps gb(&btn);
    CBitmap* pDst[4] = { gb.Nor(), gb.Sel(), gb.Foc(), gb.Dis() };

    // 3. 分割複製
    CDC srcDC, memDC;
    srcDC.CreateCompatibleDC(NULL); srcDC.SelectObject(&src);
    memDC.CreateCompatibleDC(NULL);

    BOOL ok = TRUE;
    for (int i = 0; i < 4; ++i) {
        pDst[i]->CreateCompatibleBitmap(&srcDC, w, h);
        memDC.SelectObject(pDst[i]);
        if (!memDC.BitBlt(0, 0, w, h, &srcDC, i * w, 0, SRCCOPY)) {
            ok = FALSE; break;
        }
    }
    src.Detach(); // 避免重複刪除
    return ok;
}

使用範例


// 對話框初始化
m_btn.SubclassDlgItem(IDC_BUTTON1, this);
m_btn.LoadBitmaps(IDB_INIT_NORM, IDB_INIT_SEL, IDB_INIT_FOC, IDB_INIT_DIS); // 初始資源
m_btn.SizeToContent();

// 使用者點選「換膚」按鈕
CFileDialog dlg(TRUE, _T("bmp"), NULL, OFN_FILEMUSTEXIST, _T("四態拼接位圖(*.bmp)|*.bmp||"));
if (dlg.DoModal() == IDOK) {
    if (ChangeBitmapBtnImages(m_btn, dlg.GetPathName())) {
        m_btn.Invalidate(); // 觸發重繪
        AfxMessageBox(_T("換膚成功!"));
    } else {
        AfxMessageBox(_T("圖片格式不符或載入失敗"));
    }
}

擴展建議

  • 支援四個獨立檔案:修改 ChangeBitmapBtnImages 接受四個路徑參數,分別 LoadImage 後 CopyImage 到目標 CBitmap。
  • 高 DPI 適配:載入前檢測系統 DPI,對應載入 @2x、@3x 版本位圖。
  • 封裝為成員函式:將 ChangeBitmapBtnImages 移入 CGetBitmaps 或自訂 CSkinBitmapButton : public CBitmapButton,介面更乾淨。
  • PNG 支援:改用 Gdiplus::Bitmap 載入 PNG,再 GetHBITMAP 轉 HBITMAP 後續流程不變。

總結

通過「偏移量計算取得 protected 成員」+「LoadImage 載入磁碟檔」+「BitBlt 分割四態」,成功突破 CBitmapButton 僅能載入資源的限制,為 MFC 專案提供了零成本的運行時換膚/使用者自訂按鈕圖片能力。完整範例代碼已包含於附件,可直接整合至現有專案。

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