想在桌面放置一個會唱歌、會動畫、造型不規則的小精靈嗎?關鍵技術只有三項:視窗區域裁切 實現透明不規則外型、定時器切換位圖 產生動畫、PlaySound 播放內嵌 WAV 資源。本文提供完整 CTransparentWnd 類別,可直接整合至任何 MFC 專案。
class CTransparentWnd : public CWnd
{
public:
CTransparentWnd();
void CreateTransparent(LPCTSTR pszTitle, CRect& rect);
void SetupRegion(CDC* pDC); // 依遮色建立視窗區域
void DoChange(); // 切換動畫幀
void SoundPlay(); // 播放 WAV 資源
protected:
CBitmap m_bmpDraw; // 目前顯示位圖
int m_nAniIndex = 0; // 動畫幀索引
afx_msg int OnCreate(LPCREATESTRUCT);
afx_msg BOOL OnEraseBkgnd(CDC*);
afx_msg void OnTimer(UINT_PTR);
afx_msg void OnLButtonDown(UINT, CPoint); // 拖曳移動
afx_msg void OnRButtonDown(UINT, CPoint); // 右鍵關閉
DECLARE_MESSAGE_MAP()
};
void CTransparentWnd::CreateTransparent(LPCTSTR pszTitle, CRect& rect)
{
CreateEx(0,
AfxRegisterWndClass(0, AfxGetApp()->LoadStandardCursor(IDC_ARROW)),
pszTitle,
WS_POPUP, // 無標題、無邊框
rect,
NULL, NULL, NULL);
DoChange(); // 載入第一幀並建立區域
}
void CTransparentWnd::SetupRegion(CDC* pDC)
{
CDC memDC; memDC.CreateCompatibleDC(pDC);
CBitmap* pOld = memDC.SelectObject(&m_bmpDraw);
BITMAP bm; m_bmpDraw.GetObject(sizeof(bm), &bm);
COLORREF clrMask = memDC.GetPixel(0, 0); // 左上角像素作為透明色
CRgn wndRgn, tempRgn;
wndRgn.CreateRectRgn(0, 0, bm.bmWidth, bm.bmHeight);
for (int x = 0; x <= bm.bmWidth; x++)
for (int y = 0; y <= bm.bmHeight; y++)
if (memDC.GetPixel(x, y) == clrMask)
{
tempRgn.CreateRectRgn(x, y, x+1, y+1);
wndRgn.CombineRgn(&wndRgn, &tempRgn, RGN_XOR);
tempRgn.DeleteObject();
}
memDC.SelectObject(pOld);
SetWindowRgn((HRGN)wndRgn.Detach(), TRUE); // 系統接管區域物件
MoveWindow(0, 0, bm.bmWidth, bm.bmHeight); // 調整視窗大小符合位圖
}
原理: 掃描每個像素,將透明色像素從矩形區域中 RGN_XOR 剔除,剩餘區域即為可見非矩形視窗。
void CTransparentWnd::DoChange()
{
CString strRes; strRes.Format(_T("WEST%d"), m_nAniIndex % 2 + 1); // WEST1, WEST2
m_bmpDraw.DeleteObject();
m_bmpDraw.LoadBitmap(strRes);
CWindowDC dc(this);
SetupRegion(&dc);
Invalidate(); // 觸發 OnEraseBkgnd 重繪
}
void CTransparentWnd::SoundPlay()
{
// 停止上一輪播放
PlaySound(_T("WEST"), AfxGetResourceHandle(),
SND_RESOURCE | SND_PURGE | SND_NODEFAULT);
// 設定播放長度計時器 (依實際 WAV 長度調整,單位 ms)
SetTimer(2, 61080, NULL);
// 非同步播放
PlaySound(_T("WEST"), AfxGetResourceHandle(),
SND_RESOURCE | SND_ASYNC | SND_NODEFAULT);
}
BEGIN_MESSAGE_MAP(CTransparentWnd, CWnd)
ON_WM_CREATE()
ON_WM_ERASEBKGND()
ON_WM_TIMER()
ON_WM_LBUTTONDOWN()
ON_WM_RBUTTONDOWN()
END_MESSAGE_MAP()
int CTransparentWnd::OnCreate(LPCREATESTRUCT lpCS)
{
if (CWnd::OnCreate(lpCS) == -1) return -1;
SetTimer(1, 1000, NULL); // 動畫幀切換間隔
SetTimer(2, 500, NULL); // 啟動聲音播放
SetWindowPos(&wndTopMost, 0,0,0,0, SWP_NOSIZE|SWP_NOMOVE);
return 0;
}
BOOL CTransparentWnd::OnEraseBkgnd(CDC* pDC)
{
CRect rc; GetWindowRect(&rc);
CDC memDC; memDC.CreateCompatibleDC(pDC);
CBitmap* pOld = memDC.SelectObject(&m_bmpDraw);
pDC->BitBlt(0, 0, rc.Width(), rc.Height(), &memDC, 0, 0, SRCCOPY);
memDC.SelectObject(pOld);
return TRUE; // 禁止預設背景擦除,減少閃爍
}
void CTransparentWnd::OnTimer(UINT_PTR nIDEvent)
{
switch (nIDEvent)
{
case 1: DoChange(); break; // 動畫
case 2: SoundPlay(); break; // 聲音循環
}
m_nAniIndex++;
CWnd::OnTimer(nIDEvent);
}
void CTransparentWnd::OnLButtonDown(UINT nFlags, CPoint point)
{
// 模擬標題列拖曳
PostMessage(WM_NCLBUTTONDOWN, HTCAPTION, MAKELPARAM(point.x, point.y));
CWnd::OnLButtonDown(nFlags, point);
}
void CTransparentWnd::OnRButtonDown(UINT nFlags, CPoint point)
{
DestroyWindow(); // 右鍵關閉精靈
CWnd::OnRButtonDown(nFlags, point);
}
BOOL CMyApp::InitInstance()
{
srand((unsigned)time(NULL));
// 單一實例檢查
if (::FindWindow(NULL, _T("我的桌面精靈"))) return FALSE;
CTransparentWnd* pWnd = new CTransparentWnd;
m_pMainWnd = pWnd;
CRect rect(0, 0, 100, 100); // 初始大小,SetupRegion 會自動調整
pWnd->CreateTransparent(_T("我的桌面精靈"), rect);
pWnd->CenterWindow();
pWnd->ShowWindow(SW_SHOW);
return TRUE;
}
IDB_WEST1、IDB_WEST2 (同尺寸、同透明色左上角像素)IDR_WEST (類型 WAVE,資源名稱 WEST).rc 需包含:WEST WAVE "west.wav"SetupRegion,可將 HRGN 快取於 std::map。OnEraseBkgnd 改用記憶體 DC 完整繪製後一次 BitBlt 至螢幕。SetProcessDpiAwarenessContext。結合 SetWindowRgn 透明區域、BitBlt 位圖動畫、PlaySound 資源音效,不到 200 行代碼即可打造生動桌面精靈。此技術同樣適用於不規則視窗 UI、遊戲啟動器、桌面小工具等場景,是 MFC 開發者掌握視窗進階繪圖的經典練習專案。