AVI 動畫按鈕:滑鼠移過按鈕時自動播放 AVI 視訊流,滑鼠移出停止。廣泛應用於遊戲介面、3D 軟體(如摩托英豪、Cool 3D),增強介面動感與專業度。
CAnimateCtrl 封裝 Windows 動畫控件,支援播放無聲、未壓縮或 RLE 壓縮的 AVI 檔案/資源。
BOOL Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID);
| 樣式 | 說明 |
|---|---|
| WS_CHILD | WS_VISIBLE | WS_BORDER | 基本視窗樣式(必須) |
| ACS_CENTER | 動畫置中,控件尺寸固定 |
| ACS_TRANSPARENT | 背景透明 |
| ACS_AUTOPLAY | 開啟後自動循環播放 |
典型建立:WS_CHILD|WS_VISIBLE|WS_BORDER|ACS_AUTOPLAY|ACS_CENTER|ACS_TRANSPARENT
| 函式 | 功能 | 參數說明 |
|---|---|---|
| Open(LPCTSTR lpszFileName) | 從檔案開啟 AVI | 檔案路徑,NULL=關閉 |
| Open(UINT nID) | 從資源開啟 AVI | 資源 ID |
| Play(UINT nFrom, UINT nTo, UINT nRep) | 播放 | 起始幀、結束幀(-1=結尾)、重複次數(-1=無限) |
| Seek(UINT nTo) | 靜態顯示指定幀 | 幀索引 (0=第一幀, -1=最後一幀) |
| Stop() | 停止播放 | - |
| Close() | 關閉並釋放記憶體 | - |
動畫控件只能播放符合以下條件的 AVI:
- 無聲(無音訊軌道)
- 未壓縮 或 RLE 壓縮
- 調色盤固定不變
- 最大 256 色,但實際顯示受限於系統預設調色盤(類 16 色效果)
若需播放高品質 AVI,請使用 MCI 介面或 DirectShow。
// AviButton.h
#pragma once
class CAviButton : public CButton
{
DECLARE_DYNAMIC(CAviButton)
public:
CAviButton();
virtual ~CAviButton();
protected:
CAnimateCtrl m_animCtrl; // 動畫控件
BOOL m_bMouseOver; // 滑鼠是否在按鈕上
UINT m_nAviResourceID; // AVI 資源 ID
CRect m_animRect; // 動畫顯示區域
// 重寫
virtual void PreSubclassWindow();
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);
// 訊息處理
afx_msg void OnMouseMove(UINT nFlags, CPoint point);
afx_msg void OnMouseLeave();
afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);
afx_msg void OnDestroy();
DECLARE_MESSAGE_MAP()
public:
void SetAviResource(UINT nResourceID);
void SetAviFile(LPCTSTR lpszFileName);
};
// AviButton.cpp
#include "stdafx.h"
#include "AviButton.h"
IMPLEMENT_DYNAMIC(CAviButton, CButton)
BEGIN_MESSAGE_MAP(CAviButton, CButton)
ON_WM_MOUSEMOVE()
ON_WM_MOUSELEAVE()
ON_WM_CREATE()
ON_WM_DESTROY()
END_MESSAGE_MAP()
CAviButton::CAviButton() : m_bMouseOver(FALSE), m_nAviResourceID(0) {}
CAviButton::~CAviButton() {}
int CAviButton::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CButton::OnCreate(lpCreateStruct) == -1) return -1;
// 設定 Owner-Draw
ModifyStyle(0, BS_OWNERDRAW);
// 建立動畫控件(隱藏,僅作為播放引擎)
CRect rect(0, 0, 0, 0);
DWORD dwStyle = WS_CHILD | ACS_AUTOPLAY | ACS_CENTER | ACS_TRANSPARENT;
if (!m_animCtrl.Create(dwStyle, rect, this, 100)) {
TRACE0("動畫控件建立失敗\n");
return -1;
}
m_animCtrl.ShowWindow(SW_HIDE); // 隱藏控件本身
return 0;
}
void CAviButton::OnDestroy()
{
m_animCtrl.Close();
CButton::OnDestroy();
}
void CAviButton::PreSubclassWindow()
{
ModifyStyle(0, BS_OWNERDRAW);
CButton::PreSubclassWindow();
}
void CAviButton::SetAviResource(UINT nResourceID)
{
m_nAviResourceID = nResourceID;
if (m_animCtrl.GetSafeHwnd()) {
m_animCtrl.Open(nResourceID);
}
}
void CAviButton::SetAviFile(LPCTSTR lpszFileName)
{
if (m_animCtrl.GetSafeHwnd()) {
m_animCtrl.Open(lpszFileName);
}
}
void CAviButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct)
{
CDC* pDC = CDC::FromHandle(lpDrawItemStruct->hDC);
CRect rect = lpDrawItemStruct->rcItem;
UINT nState = lpDrawItemStruct->itemState;
int nSavedDC = pDC->SaveDC();
// 1. 繪製按鈕背景
DrawButtonBackground(pDC, rect, nState);
// 2. 繪製按鈕文字
DrawButtonText(pDC, rect, nState);
// 3. 若滑鼠在上方且有 AVI,在按鈕內顯示動畫
if (m_bMouseOver && m_animCtrl.GetSafeHwnd()) {
DrawAnimationInButton(pDC, rect);
}
pDC->RestoreDC(nSavedDC);
}
void CAviButton::DrawButtonBackground(CDC* pDC, CRect rect, UINT nState)
{
BOOL bPressed = (nState & ODS_SELECTED);
BOOL bFocused = (nState & ODS_FOCUS);
BOOL bDisabled = (nState & ODS_DISABLED);
// 使用系統主題繪製(XP+)
#if (_WIN32_WINNT >= 0x0501)
if (IsAppThemed()) {
HTHEME hTheme = OpenThemeData(m_hWnd, L"BUTTON");
if (hTheme) {
int nStateId = bDisabled ? PBS_DISABLED : (bPressed ? PBS_PRESSED : (bFocused ? PBS_HOT : PBS_NORMAL));
DrawThemeBackground(hTheme, pDC->GetSafeHdc(), BP_PUSHBUTTON, nStateId, &rect, NULL);
CloseThemeData(hTheme);
return;
}
}
#endif
// 回退:傳統 3D 繪製
COLORREF clrFace = ::GetSysColor(COLOR_3DFACE);
CBrush brush(clrFace);
pDC->FillRect(rect, &brush);
if (bPressed) {
pDC->Draw3dRect(rect, ::GetSysColor(COLOR_3DSHADOW), ::GetSysColor(COLOR_3DHIGHLIGHT));
} else {
pDC->Draw3dRect(rect, ::GetSysColor(COLOR_3DHIGHLIGHT), ::GetSysColor(COLOR_3DSHADOW));
}
}
void CAviButton::DrawButtonText(CDC* pDC, CRect rect, UINT nState)
{
CString strText;
GetWindowText(strText);
if (strText.IsEmpty()) return;
COLORREF clrText = (nState & ODS_DISABLED) ? ::GetSysColor(COLOR_GRAYTEXT) : ::GetSysColor(COLOR_BTNTEXT);
pDC->SetTextColor(clrText);
pDC->SetBkMode(TRANSPARENT);
// 文字區域避開動畫區(假設動畫在右側)
CRect textRect = rect;
textRect.DeflateRect(8, 4);
if (m_bMouseOver) textRect.right -= 40; // 為動畫留空間
pDC->DrawText(strText, textRect, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
}
void CAviButton::DrawAnimationInButton(CDC* pDC, CRect rect)
{
// 在按鈕右側顯示動畫(40x40 區域)
CRect animRect(rect.right - 42, rect.top + (rect.Height()-40)/2, rect.right - 2, rect.top + (rect.Height()+40)/2);
// 將動畫控件移至該位置並顯示
m_animCtrl.SetWindowPos(NULL, animRect.left, animRect.top, animRect.Width(), animRect.Height(),
SWP_NOZORDER | SWP_SHOWWINDOW);
// 強制動畫控件重繪到按鈕 DC(進階:實際需子類別化動畫控件的 WM_PAINT)
// 簡易做法:動畫控件本身有視窗,會自動疊加顯示
}
void CAviButton::OnMouseMove(UINT nFlags, CPoint point)
{
if (!m_bMouseOver) {
m_bMouseOver = TRUE;
// 開始追蹤 MouseLeave
TRACKMOUSEEVENT tme;
tme.cbSize = sizeof(TRACKMOUSEEVENT);
tme.dwFlags = TME_LEAVE;
tme.hwndTrack = m_hWnd;
_TrackMouseEvent(&tme);
// 開始播放動畫
if (m_animCtrl.GetSafeHwnd()) {
m_animCtrl.Play(0, -1, -1); // 從頭播放到尾,無限循環
}
Invalidate(); // 重繪顯示動畫
}
CButton::OnMouseMove(nFlags, point);
}
void CAviButton::OnMouseLeave()
{
m_bMouseOver = FALSE;
// 停止動畫
if (m_animCtrl.GetSafeHwnd()) {
m_animCtrl.Stop();
m_animCtrl.Seek(0); // 回到第一幀
m_animCtrl.ShowWindow(SW_HIDE);
}
Invalidate(); // 重繪隱藏動畫
CButton::OnMouseLeave();
}
TestAviButtonCButton → 新類別 CAviButtonIDC_MYAVIBTNAVI → ID 為 IDR_AVICAviButton m_btnAvi;,DoDataExchange 中 DDX_Control(pDX, IDC_MYAVIBTN, m_btnAvi);m_btnAvi.SetAviResource(IDR_AVI);上述簡易做法讓動畫控件疊加顯示。專業做法是子類別化 CAnimateCtrl,攔截 WM_PAINT,將幀繪製到按鈕的 DC 上:
class CEmbeddedAnimateCtrl : public CAnimateCtrl {
CAviButton* m_pParentBtn;
public:
void SetParentBtn(CAviButton* p) { m_pParentBtn = p; }
protected:
afx_msg void OnPaint() {
if (m_pParentBtn) {
// 取得父按鈕 DC,在指定區域繪製當前幀
CDC* pDC = m_pParentBtn->GetDC();
CRect rect = m_pParentBtn->GetAnimationRect();
// 這裡需用 GetDIBits 等取得當前幀位圖並繪製
ReleaseDC(pDC);
}
}
DECLARE_MESSAGE_MAP()
};
enum TriggerMode { HOVER, CLICK, FOCUS };
TriggerMode m_triggerMode = HOVER;
// OnLButtonDown 中
if (m_triggerMode == CLICK) { PlayAnimation(); }
void CAviButton::PreloadAnimation()
{
// 在背景執行緒預載入
if (m_nAviResourceID) {
m_animCtrl.Open(m_nAviResourceID);
m_animCtrl.Seek(0);
m_animCtrl.Close(); // 關閉但快取於記憶體(視實作而定)
}
}
| 問題 | 原因 | 解決 |
|---|---|---|
| AVI 不播放 | 檔案格式不支援(有聲音、非 RLE 壓縮、調色盤變化) | 用視訊轉換工具輸出:無聲、未壓縮/RLE、固定調色盤、256 色以下 |
| 顏色失真 | 動畫控件使用系統預設調色盤 | 設計 AVI 時使用系統 20 色調色盤、或改用 MCI/DirectShow |
| 動畫不顯示 | 控件未建立、資源 ID 錯誤、OwnerDraw 未設定 | 檢查 Create 回傳值、確認資源類型為 AVI、BS_OWNERDRAW |
| 閃爍 | 重繪頻率過高 | 雙緩衝繪製、減少 Invalidate 範圍 |
利用 CAnimateCtrl 與 CButton Owner-Draw 機制,可輕鬆打造具備 AVI 動畫效果的按鈕。關鍵在於:理解 AVI 格式限制、正確處理滑鼠進入/離開事件控制播放狀態、妥善整合動畫控件與按鈕繪製邏輯。此技術可擴展至工具列按鈕、狀態指示器等多種介面元素。