首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> VC 教學

MFC MDI 應用程式實現動畫背景旗幟:CWnd 派生客戶區類 + 定時器幀動畫 + 滑鼠互動

     編輯:VC 教學

專案架構:CMdiMainClient 取代預設 MDI 客戶區

標準 MDI 應用中,客戶區 (MDI Client Window) 由框架自動建立,負責管理子視窗。若需自訂背景繪製 (動畫旗幟、漸層、圖片),需建立 CWnd 派生類 (如 CMdiMainClient) 並替換預設客戶區。

核心類別設計 (MdiMainClient.h)


class CMdiMainClient : public CWnd
{
    DECLARE_DYNAMIC(CMdiMainClient)

public:
    CMdiMainClient();
    virtual ~CMdiMainClient();

    // 動畫控制
    void DisplayNextLogoFrame();
    void DisplayBlankBitmap();

protected:
    // 背景位圖
    CBitmap m_bmpBackground;   // 靜態背景 (紅色底)
    CBitmap m_bmpLogo;         // 動畫旗幟位圖 (IDB_BITMAP0 ~ 9)

    // 動畫狀態
    int m_nCurrentFrame = 0;   // 0-9
    bool m_bStopRotation = false;
    bool m_bHideLogo = false;

    // 視窗尺寸追蹤
    CSize m_sizeClient;

    // 訊息映射
    afx_msg BOOL OnEraseBkgnd(CDC* pDC);
    afx_msg void OnTimer(UINT_PTR nIDEvent);
    afx_msg void OnSize(UINT nType, int cx, int cy);
    afx_msg void OnRButtonDown(UINT nFlags, CPoint point);
    afx_msg void OnLButtonDown(UINT nFlags, CPoint point);

    DECLARE_MESSAGE_MAP()
};

關鍵實作 (MdiMainClient.cpp)

1. 建構/解構:載入初始位圖


CMdiMainClient::CMdiMainClient() : m_sizeClient(0,0)
{
    VERIFY(m_bmpBackground.LoadBitmap(IDB_BCKGRD)); // 紅色背景
    ASSERT(m_bmpBackground.GetSafeHandle());

    VERIFY(m_bmpLogo.LoadBitmap(IDB_BITMAP0));      // 旗幟第 0 幀
    ASSERT(m_bmpLogo.GetSafeHandle());
}

CMdiMainClient::~CMdiMainClient()
{
    if (m_bmpBackground.GetSafeHandle()) m_bmpBackground.DeleteObject();
    if (m_bmpLogo.GetSafeHandle()) m_bmpLogo.DeleteObject();
}

2. 背景擦除與繪製:OnEraseBkgnd (核心繪製邏輯)


BOOL CMdiMainClient::OnEraseBkgnd(CDC* pDC)
{
    CRect rect;
    GetClientRect(&rect);

    // 啟動動畫定時器 (300ms/幀)
    SetTimer(0, 300, NULL);

    // 1. 繪製靜態背景 (拉伸填滿客戶區)
    BITMAP bmBg;
    CDC dcMemBg;
    m_bmpBackground.GetObject(sizeof(BITMAP), &bmBg);
    dcMemBg.CreateCompatibleDC(NULL);
    CBitmap* pOldBg = dcMemBg.SelectObject(&m_bmpBackground);
    pDC->StretchBlt(0, 0, rect.Width(), rect.Height(),
                    &dcMemBg, 0, 0, bmBg.bmWidth, bmBg.bmHeight, SRCCOPY);
    dcMemBg.SelectObject(pOldBg);

    // 2. 繪製動畫旗幟 (固定位置 200,100 大小 400x200)
    if (!m_bHideLogo)
    {
        BITMAP bmLogo;
        CDC dcMemLogo;
        m_bmpLogo.GetObject(sizeof(BITMAP), &bmLogo);
        dcMemLogo.CreateCompatibleDC(NULL);
        dcMemLogo.SelectObject(&m_bmpLogo);
        pDC->StretchBlt(200, 100, 400, 200,
                        &dcMemLogo, 0, 0, bmLogo.bmWidth, bmLogo.bmHeight, SRCCOPY);
    }

    // 3. 繪製操作提示文字
    pDC->SetBkColor(RGB(255,0,0));
    pDC->SetTextColor(RGB(192,192,192));
    pDC->TextOut(480, 500, _T("Mouse Right Click to Stop/Start Animation"));
    pDC->TextOut(480, 515, _T("Mouse Left Click to Show Child Window"));

    return TRUE; // 已處理背景擦除,系統不再預設填充
}

3. 定時器驅動幀動畫:OnTimer + DisplayNextLogoFrame


void CMdiMainClient::OnTimer(UINT_PTR nIDEvent)
{
    if (nIDEvent == 0) // 背景定時器
    {
        DisplayNextLogoFrame();
    }
    CWnd::OnTimer(nIDEvent);
}

void CMdiMainClient::DisplayNextLogoFrame()
{
    if (m_bStopRotation) return;

    // 1. 切換到下一幀位圖
    m_nCurrentFrame = (m_nCurrentFrame + 1) % 10; // 0-9 循環
    UINT nResID = IDB_BITMAP0 + m_nCurrentFrame;

    // 2. 釋放舊位圖,載入新位圖
    if (m_bmpLogo.GetSafeHandle()) m_bmpLogo.DeleteObject();
    VERIFY(m_bmpLogo.LoadBitmap(nResID));
    ASSERT(m_bmpLogo.GetSafeHandle());

    // 3. 立即重繪旗幟區域 (不需整個客戶區 Invalidate)
    CDC* pDC = GetDC();
    if (pDC)
    {
        BITMAP bm;
        CDC dcMem;
        m_bmpLogo.GetObject(sizeof(BITMAP), &bm);
        dcMem.CreateCompatibleDC(NULL);
        dcMem.SelectObject(&m_bmpLogo);
        pDC->StretchBlt(200, 100, 400, 200,
                        &dcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY);
        ReleaseDC(pDC);
    }
}

4. 滑鼠互動控制


// 右鍵:啟動/停止動畫
void CMdiMainClient::OnRButtonDown(UINT nFlags, CPoint point)
{
    m_bStopRotation = !m_bStopRotation;
    if (!m_bStopRotation)
    {
        // 重新啟動時確保定時器運行
        SetTimer(0, 300, NULL);
    }
    CWnd::OnRButtonDown(nFlags, point);
}

// 左鍵:隱藏旗幟、建立子視窗 (進入應用主功能)
void CMdiMainClient::OnLButtonDown(UINT nFlags, CPoint point)
{
    m_bHideLogo = true;
    KillTimer(0); // 停止背景動畫定時器

    // 建立 MDI 子視窗 (標準文檔模板)
    ((CMyApp*)AfxGetApp())->m_pDocTemplate->OpenDocumentFile(NULL);

    CWnd::OnLButtonDown(nFlags, point);
}

5. 尺寸變更處理:OnSize


void CMdiMainClient::OnSize(UINT nType, int cx, int cy)
{
    CWnd::OnSize(nType, cx, cy);

    // 首次建立或尺寸未變直接返回
    if (m_sizeClient == CSize(cx, cy)) return;
    if (m_sizeClient == CSize(0,0)) { m_sizeClient = CSize(cx, cy); return; }

    m_sizeClient = CSize(cx, cy);

    // 強制整個客戶區重繪 (含子視窗)
    RedrawWindow(NULL, NULL,
        RDW_INVALIDATE | RDW_ERASE | RDW_ERASENOW | RDW_ALLCHILDREN);
}

替換預設 MDI 客戶區 (MainFrm.cpp)


BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT lpcs, CCreateContext* pContext)
{
    // 不呼叫基類,改用自訂客戶區
    m_wndClient.Create(NULL, NULL, WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN,
                       CRect(0,0,0,0), this, AFX_IDW_PANE_FIRST, pContext);
    return TRUE;
}

資源準備

  • 位圖資源 IDB_BCKGRD:紅色純色背景 (任意尺寸,會拉伸)。
  • 位圖資源 IDB_BITMAP0 ~ IDB_BITMAP9:10 幀旗幟動畫序列圖 (建議相同尺寸,如 400x200)。

技術亮點總結

技術點實作方式優勢
自訂 MDI 客戶區CWnd 派生 + OnCreateClient 替換完全控制背景繪製、不干擾子視窗管理
幀動畫定時器 + LoadBitmap 切換資源 + StretchBlt 繪製資源型位圖管理簡單、無需外部檔案
局部重繪GetDC + StretchBlt 僅更新旗幟區域避免整個客戶區 Invalidate 閃爍
滑鼠互動OnRButtonDown 切換動畫、OnLButtonDown 進入主功能直觀使用者體驗,符合啟動畫面慣例
尺寸自適應OnSize 追蹤 + StretchBlt 拉伸背景視窗最大化/調整大小背景自動適應

現代化改進建議

  • 使用 CImageList 或 CImage (MFC/ATL) 管理動畫幀,支援 PNG/透明通道。
  • 改用 UpdateLayeredWindow 實現旗幟淡入淡出、透明背景疊加。
  • 動畫定時器改用多媒體定時器 (timeSetEvent) 或高解析度計時器 (QueryPerformanceCounter) 獲得更精準幀率。
  • 背景繪製移至獨立執行緒,雙緩衝交換,避免 UI 執行緒阻塞 (需同步機制)。

結語

透過自訂 CMdiMainClient 替換 MDI 客戶區,結合資源型位圖、定時器幀動畫、局部 StretchBlt 重繪,即可實現專業級動畫背景旗幟啟動畫面。此架構清晰分離「背景動畫」與「子視窗管理」,擴充性極佳。現代專案建議配合 GDI+/Direct2D 實現更豐富視覺效果 (粒子、著色器、向量動畫)。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved