Windows 2000+ 引入分層視窗 (WS_EX_LAYERED) 支援半透明、透明色鍵、Alpha 混合。核心 API SetLayeredWindowAttributes 定義於 Winuser.h,需最新 Platform SDK。舊版 VC6 預設 SDK 無此定義,升級 SDK 體積龐大 (200MB+)。本文展示動態載入 User32.dll 取得函式指標技巧,免升級 SDK 即可使用新 API。
// Winuser.h 定義 (手動補充)
#define WS_EX_LAYERED 0x00080000
#define LWA_COLORKEY 0x00000001 // 使用透明色鍵
#define LWA_ALPHA 0x00000002 // 使用 Alpha 混合
BOOL WINAPI SetLayeredWindowAttributes(
HWND hwnd, // 分層視窗句柄
COLORREF crKey, // 透明色鍵 (LWA_COLORKEY 時使用)
BYTE bAlpha, // Alpha 值 0-255 (LWA_ALPHA 時使用),0=全透明, 255=不透明
DWORD dwFlags // LWA_COLORKEY | LWA_ALPHA
);
typedef BOOL (WINAPI *PFN_SetLayeredWindowAttributes)(
HWND, COLORREF, BYTE, DWORD
);
BOOL CTransparentDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// 步驟 1:加上 WS_EX_LAYERED 擴充風格
// 使用 XOR 切換位元,避免覆蓋現有擴充風格
DWORD exStyle = GetWindowLong(GetSafeHwnd(), GWL_EXSTYLE);
SetWindowLong(GetSafeHwnd(), GWL_EXSTYLE, exStyle ^ WS_EX_LAYERED);
// 步驟 2:動態載入 User32.dll 並取得函式指標
HMODULE hUser32 = LoadLibrary(_T("User32.dll"));
if (hUser32)
{
PFN_SetLayeredWindowAttributes pfn =
(PFN_SetLayeredWindowAttributes)GetProcAddress(hUser32, "SetLayeredWindowAttributes");
if (pfn)
{
// 範例:設定 50% 半透明 (Alpha = 128)
// dwFlags = LWA_ALPHA (0x2),crKey 忽略 (設 0)
pfn(GetSafeHwnd(), 0, 128, LWA_ALPHA);
// 替代方案:設定透明色鍵 (如將白色背景變透明)
// pfn(GetSafeHwnd(), RGB(255,255,255), 0, LWA_COLORKEY);
}
FreeLibrary(hUser32);
}
return TRUE;
}
// 成員變數
BYTE m_bCurrentAlpha = 0;
bool m_bFadingIn = true;
CButton m_btnToggle;
// OnInitDialog 追加
SetTimer(TIMER_FADE, 50, NULL); // 50ms 更新一次
// OnTimer
void CTransparentDlg::OnTimer(UINT_PTR nIDEvent)
{
if (nIDEvent == TIMER_FADE)
{
HMODULE hUser32 = LoadLibrary(_T("User32.dll"));
if (hUser32)
{
auto pfn = (PFN_SetLayeredWindowAttributes)GetProcAddress(hUser32, "SetLayeredWindowAttributes");
if (pfn)
{
if (m_bFadingIn)
{
m_bCurrentAlpha += 10;
if (m_bCurrentAlpha >= 255) { m_bCurrentAlpha = 255; m_bFadingIn = false; KillTimer(TIMER_FADE); }
}
else
{
if (m_bCurrentAlpha >= 10) m_bCurrentAlpha -= 10;
else { m_bCurrentAlpha = 0; m_bFadingIn = true; KillTimer(TIMER_FADE); }
}
pfn(GetSafeHwnd(), 0, m_bCurrentAlpha, LWA_ALPHA);
}
FreeLibrary(hUser32);
}
}
CDialog::OnTimer(nIDEvent);
}
| 問題 | 原因 | 解決 |
|---|---|---|
| 視窗完全消失 (找不到) | Alpha 設為 0 (全透明) 且無色鍵 | Alpha 最小設 10-20,或保留標題列/邊框 (WS_CAPTION) 可見 |
| 子控制項 (按鈕、編輯框) 也變透明 | 分層視窗預設整個視窗 (含子控制項) 混合 | 無法分離。需求「僅背景透明、控制項不透明」請改用 UpdateLayeredWindow + 手繪背景,或使用 DWM (Vista+) DwmExtendFrameIntoClientArea |
| GDI 繪圖異常 (文字模糊、位圖錯位) | 分層視窗使用預乘 Alpha,標準 GDI 不支援 | 使用 UpdateLayeredWindow 配合 32-bit 位圖 (含 Alpha 通道) 繪製 |
| Windows 9x/Me 不支援 | 分層視窗需 Win2000+ | 執行檢查:if (GetVersion() & 0x80000000) { /* 9x 系列,降級處理 */ } |
| 動態載入 GetProcAddress 失敗 | 系統版本過舊 (Win2000 前) 或 User32.dll 未載入 | LoadLibrary 確保載入,檢查回傳值,優雅降級 (不啟用透明) |
#include
#pragma comment(lib, "dwmapi.lib")
// 擴展客戶區至整個視窗 (玻璃邊框)
MARGINS margins = {-1}; // -1 表示整個客戶區
DwmExtendFrameIntoClientArea(GetSafeHwnd(), &margins);
// 客戶區繪製時背景透明 (需 WS_EX_LAYERED 配合)
優點:原生支援 Aero 玻璃、標題列整合、子控制項正常不透明。缺點:需 Vista+、主題開啟、相容性測試較複雜。
動態載入 SetLayeredWindowAttributes 是 VC6 時代免升級 SDK 使用新 API 的經典技巧,核心思想:**LoadLibrary + GetProcAddress + 函式指標呼叫**。雖然現代開發環境 (VS2017+/Windows SDK 10) 已內建完整標頭庫,但此技巧對插件開發、舊專案維護、跨版本相容性測試仍具參考價值。理解分層視窗限制 (子控制項同步透明、GDI 繪圖問題),在需求複雜時果斷遷移至 UpdateLayeredWindow 或 DWM API。