標準 MDI 應用中,客戶區 (MDI Client Window) 由框架自動建立,負責管理子視窗。若需自訂背景繪製 (動畫旗幟、漸層、圖片),需建立 CWnd 派生類 (如 CMdiMainClient) 並替換預設客戶區。
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()
};
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();
}
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; // 已處理背景擦除,系統不再預設填充
}
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);
}
}
// 右鍵:啟動/停止動畫
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);
}
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);
}
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) 獲得更精準幀率。透過自訂 CMdiMainClient 替換 MDI 客戶區,結合資源型位圖、定時器幀動畫、局部 StretchBlt 重繪,即可實現專業級動畫背景旗幟啟動畫面。此架構清晰分離「背景動畫」與「子視窗管理」,擴充性極佳。現代專案建議配合 GDI+/Direct2D 實現更豐富視覺效果 (粒子、著色器、向量動畫)。