對話框啟動時:從中心點 (0x0) 逐漸放大至完整尺寸 (動畫彈出)。
對話框關閉時:從完整尺寸逐漸縮小至中心點消失 (動畫消隱)。
SetTimer 定時器驅動動畫幀 (如 10ms/幀,100 FPS)。MoveWindow 調整視窗大小與位置,保持視窗中心點固定。dx, dy) 增減,達到目標尺寸後殺死定時器。
class CAnimDlg : public CDialog
{
// ...
protected:
CPoint m_ptCenter; // 螢幕中心點 (視窗中心維持不變)
int m_nTargetW, m_nTargetH; // 目標寬高
int m_nStepW, m_nStepH; // 每幀寬高變化量
int m_nCloseStepW, m_nCloseStepH; // 關閉動畫步長
bool m_bAnimating = false; // 動畫狀態
enum { TIMER_OPEN = 1, TIMER_CLOSE = 2 };
};
BOOL CAnimDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// 1. 取得對話框設計尺寸
CRect dlgRect;
GetWindowRect(&dlgRect);
m_nTargetW = dlgRect.Width();
m_nTargetH = dlgRect.Height();
// 2. 計算螢幕中心點
CRect deskRect;
GetDesktopWindow()->GetWindowRect(&deskRect);
m_ptCenter = deskRect.CenterPoint();
// 3. 設定動畫步長 (可根據需求調整)
m_nStepW = 20; // 每幀寬度增加 20 像素
m_nStepH = 15; // 每幀高度增加 15 像素
m_nCloseStepW = 25; // 關閉時收縮較快
m_nCloseStepH = 20;
// 4. 將視窗移至中心點,尺寸設為 0 (隱藏)
MoveWindow(
m_ptCenter.x, m_ptCenter.y, // 左上角 = 中心點 (寬高為 0 時即為中心)
0, 0,
TRUE // 重繪
);
// 5. 啟動展開動畫定時器
SetTimer(TIMER_OPEN, 10, NULL); // 10ms ~ 100 FPS
m_bAnimating = true;
return TRUE; // 讓系統設定焦點
}
void CAnimDlg::OnTimer(UINT_PTR nIDEvent)
{
if (!m_bAnimating)
{
CDialog::OnTimer(nIDEvent);
return;
}
CRect curRect;
GetWindowRect(&curRect);
CRect deskRect;
GetDesktopWindow()->GetWindowRect(&deskRect);
CPoint center = deskRect.CenterPoint();
if (nIDEvent == TIMER_OPEN) // 展開動畫
{
int newW = curRect.Width() + m_nStepW;
int newH = curRect.Height() + m_nStepH;
// 限制不超過目標尺寸
if (newW >= m_nTargetW) newW = m_nTargetW;
if (newH >= m_nTargetH) newH = m_nTargetH;
// 計算新左上角 (保持中心點不變)
int newX = center.x - newW / 2;
int newY = center.y - newH / 2;
MoveWindow(newX, newY, newW, newH, TRUE);
// 達到目標尺寸,停止動畫
if (newW == m_nTargetW && newH == m_nTargetH)
{
KillTimer(TIMER_OPEN);
m_bAnimating = false;
}
}
else if (nIDEvent == TIMER_CLOSE) // 關閉動畫
{
int newW = curRect.Width() - m_nCloseStepW;
int newH = curRect.Height() - m_nCloseStepH;
// 限制不小於 0
if (newW <= 0) newW = 0;
if (newH <= 0) newH = 0;
int newX = center.x - newW / 2;
int newY = center.y - newH / 2;
MoveWindow(newX, newY, newW, newH, TRUE);
// 縮至 0,真正關閉對話框
if (newW == 0 && newH == 0)
{
KillTimer(TIMER_CLOSE);
m_bAnimating = false;
CDialog::OnOK(); // 或 OnCancel(),觸發正常關閉流程
}
}
CDialog::OnTimer(nIDEvent);
}
void CAnimDlg::OnOK()
{
// 若正在展開動畫,先停止
if (m_bAnimating)
{
KillTimer(TIMER_OPEN);
}
// 啟動關閉動畫
SetTimer(TIMER_CLOSE, 10, NULL);
m_bAnimating = true;
// 不呼叫 CDialog::OnOK(),等動畫結束再呼叫
}
void CAnimDlg::OnCancel()
{
OnOK(); // 同樣觸發關閉動畫
}
固定步長會有「機械感」。使用緩動函數讓動畫更自然:
// 緩動函數:Ease Out Quad (快開始、慢結束)
float EaseOutQuad(float t) { return 1 - (1 - t) * (1 - t); }
// OnTimer 中計算進度
float progress = static_cast(curRect.Width()) / m_nTargetW; // 0.0 ~ 1.0
float eased = EaseOutQuad(progress);
int newW = static_cast(m_nTargetW * eased);
// 同理計算 newH
| 問題 | 原因 | 解決 |
|---|---|---|
| 動畫閃爍/殘影 | MoveWindow 重繪過於頻繁、背景擦除 | 確保對話框類別風格含 CS_DBLCLKS,或處理 WM_ERASEBKGND 返回 TRUE |
| 關閉動畫後程式未結束 | OnOK 中未呼叫 CDialog::OnOK() | 動畫結束時 (newW==0) 呼叫 CDialog::OnOK() |
| 多螢幕中心點錯誤 | GetDesktopWindow() 範圍為虛擬螢幕 | 使用 MonitorFromWindow + GetMonitorInfo 取得當前螢幕工作區 |
| 模態對話框 DoModal 期間無法動畫 | DoModal 內部訊息迴圈阻塞 | 此技巧適用於 Create() 非模態對話框,或模態對話框在 OnInitDialog 內啟動定時器 (需確保訊息泵運行) |
對話框動畫彈出/消隱透過 SetTimer + MoveWindow 即可實現,核心在於保持中心點固定、分步調整寬高、定時器驅動狀態機。加入緩動函數可大幅提升視覺質感。現代 Windows 開發 (WPF/UWP/WinUI) 內建動畫系統 (Storyboard/Composition API),MFC 專案若需豐富動畫,建議引入 DirectComposition 或遷移至現代 UI 框架。