CBitmapButton::LoadBitmaps 僅能載入**專案內嵌資源**的四態位圖(Normal/Selected/Focused/Disabled),無法直接載入磁碟檔案。若需實現「運行時換膚」、使用者自訂按鈕圖片等功能,必須繞過此限制。
CBitmapButton 以 protected 儲存四個 CBitmap 成員(m_bitmap, m_bitmapSel, m_bitmapFocus, m_bitmapDisabled)。public 繼承類 CGetBitmaps,利用指標算術計算四個成員在物件中的偏移,取得其位址。LoadImage(LR_LOADFROMFILE|LR_CREATEDIBSECTION) 載入磁碟上**單一檔案包含四態橫向拼接**的位圖。BitBlt 到四個 CBitmap。
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 宏替代原文手動指標運算,更安全可移植。
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。@2x、@3x 版本位圖。ChangeBitmapBtnImages 移入 CGetBitmaps 或自訂 CSkinBitmapButton : public CBitmapButton,介面更乾淨。Gdiplus::Bitmap 載入 PNG,再 GetHBITMAP 轉 HBITMAP 後續流程不變。通過「偏移量計算取得 protected 成員」+「LoadImage 載入磁碟檔」+「BitBlt 分割四態」,成功突破 CBitmapButton 僅能載入資源的限制,為 MFC 專案提供了零成本的運行時換膚/使用者自訂按鈕圖片能力。完整範例代碼已包含於附件,可直接整合至現有專案。