實現類似 Windows Blinds 的視窗換膚:自繪標題列、邊框、最小化/最大化/關閉按鈕、自訂按鈕狀態 (正常/游標/按下/失效),支援執行期切換皮膚,相容 Windows Blinds .ini 格式。
| 類別 | 職責 |
|---|---|
CSkinWin | 自繪視窗非客戶區:標題列、四邊邊框、系統按鈕。讀取 .ini 載入位圖與參數 |
CSkinButton | 自繪按鈕控制項:四狀態位圖 (正常/焦點/按下/失效)。共享資源由 CSkinButtonResource 管理 |
CSkinButtonResource | 按鈕皮膚資源快取,避免重複載入位圖 |
CMyBitmap 等 | 輔助類別 (部分來自 CodeProject) |
CSkinButtonResource m_btnRes; // 按鈕資源
CSkinWin m_skinWin; // 視窗皮膚
BOOL m_bFirst = TRUE; // 首次標記
CObList m_wndList; // 按鈕實例清單 (用於銷毀時釋放)
OnInitDialog 初始化:
BOOL CMyDlg::OnInitDialog() {
CDialog::OnInitDialog();
m_bFirst = TRUE;
SetSkin(_T("skin\\neostyle\\theme.ini"));
return TRUE;
}
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;
}
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;
}
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;
}
}
[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) 強制非客戶區重繪,無需重建視窗完整原始碼請參考本文配套下載。歡迎自由使用、修改與回饋。