void CTimerDlg::OnTimer(UINT_PTR nIDEvent) {
CDialogEx::OnTimer(nIDEvent);
CString szTimeFormat;
if (iSecond == -1) return;
while (iSecond >= 0) { // ❌ 錯誤:while 會阻塞 UI 執行緒,導致介面凍結
szTimeFormat = ToTimeFormat(iSecond);
SetDlgItemText(IDC_OUT, szTimeFormat);
}
iSecond--; // ❌ 在 while 結束後才遞減,邏輯倒置
}
OnTimer 在 UI 執行緒執行,while 會佔用整個執行緒,導致視窗無法重繪、無法響應按鈕、甚至系統判定「未回應」。KillTimer。// 成員變數
int m_nRemainingSeconds = 0;
const UINT ID_TIMER_COUNTDOWN = 1;
void CTimerDlg::OnBnClickedBegin() {
CString str;
GetDlgItemText(IDC_IN, str);
m_nRemainingSeconds = ToSecond(str); // 解析輸入秒數
SetDlgItemText(IDC_OUT, ToTimeFormat(m_nRemainingSeconds));
SetTimer(ID_TIMER_COUNTDOWN, 1000, NULL); // 1 秒觸發一次
}
void CTimerDlg::OnTimer(UINT_PTR nIDEvent) {
if (nIDEvent != ID_TIMER_COUNTDOWN) {
CDialogEx::OnTimer(nIDEvent);
return;
}
if (m_nRemainingSeconds > 0) {
m_nRemainingSeconds--;
SetDlgItemText(IDC_OUT, ToTimeFormat(m_nRemainingSeconds));
} else {
// 倒數結束
KillTimer(ID_TIMER_COUNTDOWN);
MessageBox(_T("時間到!"), _T("提示"), MB_ICONINFORMATION);
}
}
void CTimerDlg::OnBnClickedStop() {
KillTimer(ID_TIMER_COUNTDOWN);
m_nRemainingSeconds = 0;
SetDlgItemText(IDC_OUT, _T("00:00:00"));
}
| 錯誤做法 | 正確做法 |
|---|---|
while 迴圈在 OnTimer 內阻塞 | OnTimer **只做一次** 更新,依賴系統每秒回呼 |
| 遞減邏輯在迴圈外 | 每次 OnTimer **先遞減/判斷,再顯示** |
| 未處理倒數結束 | 秒數歸零時 KillTimer + 提示 |
| 硬編碼 Timer ID | 定義 const UINT ID_TIMER = 1 避免衝突 |
timeSetEvent (multimedia timer) 或 CreateTimerQueueTimer。KillTimer 保存剩餘秒數,繼續時 SetTimer 恢復。MFC 倒數計時器核心:**OnTimer 只執行單次更新**,靠系統週期性觸發。**切勿在 OnTimer 使用迴圈**,否則凍結 UI。正確流程:啟動 → SetTimer → OnTimer 遞減顯示 → 歸零 KillTimer。