Windows 預設視窗為矩形,若要做出圓形、動漫角色造型、邊緣羽化等不規則介面,最經典做法是區域視窗 (Window Region):載入位圖、掃描像素、以透明色建立 HRGN、SetWindowRgn。本文封裝一個 CTransparentWnd 類別,三行程式碼即可將任意位圖變成可點擊、可拖曳、可疊加的不規則視窗。
RGB(255,0,255) 洋紅)。WM_CREATE 時:GetPixel(x,y)。CreateRectRgn,CombineRgn(RGN_OR) 合併。SetWindowRgn 套用至視窗。WM_ERASEBKGND:StretchBlt 繪製位圖填滿客戶區。SetPic(UINT nID, int x, int y):載入資源位圖、計算大小、Create 視窗、ShowWindow。
// TransparentWnd.h
#pragma once
class CTransparentWnd : public CWnd {
DECLARE_DYNAMIC(CTransparentWnd)
CRect m_rcWnd; CBitmap m_bmp; BITMAP m_bm;
static constexpr COLORREF TRANS_COLOR = RGB(255,0,255);
public:
CTransparentWnd(); virtual ~CTransparentWnd();
void SetPic(UINT nIDPic, int nLeft, int nTop); // 外部呼叫入口
protected:
afx_msg int OnCreate(LPCREATESTRUCT lpCS);
afx_msg BOOL OnEraseBkgnd(CDC* pDC);
DECLARE_MESSAGE_MAP()
};
// TransparentWnd.cpp
#include "stdafx.h"
#include "TransparentWnd.h"
IMPLEMENT_DYNAMIC(CTransparentWnd, CWnd)
BEGIN_MESSAGE_MAP(CTransparentWnd, CWnd)
ON_WM_CREATE()
ON_WM_ERASEBKGND()
END_MESSAGE_MAP()
CTransparentWnd::CTransparentWnd() {}
CTransparentWnd::~CTransparentWnd() {}
int CTransparentWnd::OnCreate(LPCREATESTRUCT lpCS) {
if (CWnd::OnCreate(lpCS) == -1) return -1;
GetWindowRect(&m_rcWnd);
CRgn wndRgn, rgnPixel;
wndRgn.CreateRectRgn(0, 0, m_rcWnd.Width(), m_rcWnd.Height());
CDC* pDC = GetWindowDC(); CDC dcMem; dcMem.CreateCompatibleDC(pDC);
CBitmap* pOld = dcMem.SelectObject(&m_bmp);
for (int x = 0; x < m_rcWnd.Width(); ++x) {
for (int y = 0; y < m_rcWnd.Height(); ++y) {
if (dcMem.GetPixel(x, y) == TRANS_COLOR) {
rgnPixel.CreateRectRgn(x, y, x+1, y+1);
wndRgn.CombineRgn(&wndRgn, &rgnPixel, RGN_XOR); // 排除透明像素
rgnPixel.DeleteObject();
}
}
}
SetWindowRgn((HRGN)wndRgn, TRUE);
dcMem.SelectObject(pOld); ReleaseDC(pDC); ReleaseDC(&dcMem);
return 0;
}
BOOL CTransparentWnd::OnEraseBkgnd(CDC* pDC) {
CDC dcMem; dcMem.CreateCompatibleDC(pDC);
CBitmap* pOld = dcMem.SelectObject(&m_bmp);
pDC->StretchBlt(0, 0, m_bm.bmWidth, m_bm.bmHeight, &dcMem, 0, 0, m_bm.bmWidth, m_bm.bmHeight, SRCCOPY);
dcMem.SelectObject(pOld); ReleaseDC(&dcMem);
return TRUE;
}
void CTransparentWnd::SetPic(UINT nIDPic, int nLeft, int nTop) {
if (m_bmp.m_hObject) m_bmp.DeleteObject();
m_bmp.LoadBitmap(nIDPic); m_bmp.GetBitmap(&m_bm);
CRect rc(nLeft, nTop, nLeft + m_bm.bmWidth, nTop + m_bm.bmHeight);
Create(nullptr, nullptr, WS_CHILD | WS_VISIBLE, rc, AfxGetApp()->m_pMainWnd, nIDPic);
ShowWindow(SW_SHOWNORMAL);
}
// 主對話框頭檔
class CMainDlg : public CDialogEx {
CTransparentWnd m_wndSkin; // 成員變數
// ...
};
// OnInitDialog
BOOL CMainDlg::OnInitDialog() {
CDialogEx::OnInitDialog();
m_wndSkin.SetPic(IDB_BITMAP_IRREGULAR, 10, 10); // 資源 ID、左上角座標
return TRUE;
}

| 問題 | 優化方案 |
|---|---|
| 雙重迴圈 GetPixel 極慢 (O(n²)) | ① LockBits (GDI+) / GetDIBits 一次讀全圖像素陣列 → 記憶體掃描。② 僅掃描邊緣 (Marching Squares) 產生多邊形區域。 ③ 離線預計算區域資料嵌入資源,執行期直接 CreatePolygonRgn。 |
| 高 DPI 模糊 | 位圖內建多解析度版本 (IDB_BITMAP_100, _125, _150),依 GetDpiForWindow 載入對應資源。 |
| 動畫/影片透明 | 需逐幀切換區域 → 改用 Layered Window (WS_EX_LAYERED + UpdateLayeredWindow + 32-bit Alpha 通道 PNG),支援半透明、淡入淡出、GPU 加速。 |
| 點擊穿透 | Layered Window 可設 LWA_ALPHA + HitTest 區域,或 WM_NCHITTEST 回傳 HTTRANSPARENT 實現點擊穿透。 |
| 方案 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|
| 本文區域視窗 (GDI) | 相容 XP+、無額外依賴、邏輯簡單 | 無半透明、建立區域慢、高 DPI 模糊 | 簡易工具、啟動畫面、不規則按鈕 |
| Layered Window (WS_EX_LAYERED) | 支援 Alpha、GPU 加速、可點擊穿透 | Win2K+、需 32-bit DIB、GDI 繪圖較繁瑣 | 半透明視窗、OSD、拖曳預覽 |
| DWM 縮圖/即時預覽 | 系統級合成、效能最佳 | Vista+、API 複雜 | 任務列縮圖、Aero Peek |
| DirectComposition / WinUI 3 | 現代圖形管線、動畫豐富、XAML 整合 | Win10 1809+、學習曲線陡 | 新專案、複雜 UI、高效能動畫 |
CTransparentWnd 以 50 行核心程式碼實現經典不規則視窗,適合快速原型與舊專案維護。新專案建議直接採用 Layered Window + PNG Alpha 或 WinUI 3 獲得完整半透明與動畫支援。完整專案 (含優化版 LockBits 掃描、多解析度資源、拖曳移動、右鍵選單) 請參考文章附件。