Qt 倒计时功能从入门到弃坑:一个老码农的实战笔记

张开发
2026/6/22 13:12:13 15 分钟阅读
Qt 倒计时功能从入门到弃坑:一个老码农的实战笔记
Qt 倒计时功能从入门到弃坑一个老码农的实战笔记事情是这样的上周接到一个需求做个倒计时功能要能传秒数、能弹窗、能触发信号还得在多个地方同时用。我一听这不挺简单吗结果写着写着发现坑还不少…先说说为啥要自己写Qt 自带QTimer但你要做个像样的倒计时光靠它还真不够。比如定时器会有累积误差别问我是怎么发现的需要同时管理多个倒计时实例还得支持 QML 绑定产品经理临时加的所以干脆封装一个一劳永逸。最终成品长这样头文件countdowntimer.h#ifndefCOUNTDOWNTIMER_H#defineCOUNTDOWNTIMER_H#includeQObject#includeQTimer#includeQElapsedTimer#includeQStringclassCountdownTimer:publicQObject{Q_OBJECTQ_PROPERTY(intremainingSeconds READ remainingSeconds NOTIFY remainingSecondsChanged)public:explicitCountdownTimer(QObject*parentnullptr);// 开始倒计时// seconds: 倒计时秒数必须 0// finishMessage: 倒计时结束时的弹窗文本// showPopup: 是否显示倒计时结束弹窗voidstart(intseconds,constQStringfinishMessage,boolshowPopuptrue);// 停止倒计时voidstop();boolisActive()const;intremainingSeconds()const;signals:voidfinished();// 倒计时结束信号voidtick(intsecondsLeft);// 每秒触发一次voidremainingSecondsChanged();// 剩余秒数变化信号privateslots:voidonTimeout();private:QTimer m_timer;QElapsedTimer m_elapsedTimer;intm_totalSeconds;intm_lastRemaining;QString m_finishMessage;boolm_showPopup;boolm_isActive;};#endif实现文件countdowntimer.cpp#includecountdowntimer.h#includeQMessageBox#includeQApplicationCountdownTimer::CountdownTimer(QObject*parent):QObject(parent),m_totalSeconds(0),m_lastRemaining(0),m_showPopup(true),m_isActive(false){connect(m_timer,QTimer::timeout,this,CountdownTimer::onTimeout);m_timer.setSingleShot(false);m_timer.setInterval(1000);}voidCountdownTimer::start(intseconds,constQStringfinishMessage,boolshowPopup){if(seconds0){qWarning()CountdownTimer: seconds must be 0;return;}if(m_isActive){stop();}m_totalSecondsseconds;m_finishMessagefinishMessage;m_showPopupshowPopup;m_isActivetrue;m_elapsedTimer.start();m_lastRemainingseconds;emitremainingSecondsChanged();emittick(seconds);if(seconds0){onTimeout();}else{m_timer.start();}}voidCountdownTimer::stop(){if(!m_isActive)return;m_timer.stop();m_isActivefalse;m_totalSeconds0;m_finishMessage.clear();}boolCountdownTimer::isActive()const{returnm_isActive;}intCountdownTimer::remainingSeconds()const{if(!m_isActive)return0;qint64 elapsedMsm_elapsedTimer.elapsed();intremainingm_totalSeconds-static_castint(elapsedMs/1000);returnremaining0?0:remaining;}voidCountdownTimer::onTimeout(){if(!m_isActive)return;qint64 elapsedMsm_elapsedTimer.elapsed();intremainingm_totalSeconds-static_castint(elapsedMs/1000);if(remaining0){m_timer.stop();m_isActivefalse;emitfinished();if(m_showPopup!m_finishMessage.isEmpty()){QMessageBox::information(qApp-activeWindow(),tr(倒计时结束),m_finishMessage);}m_totalSeconds0;m_finishMessage.clear();}else{if(remaining!m_lastRemaining){m_lastRemainingremaining;emitremainingSecondsChanged();emittick(remaining);}}}用起来啥感觉基础用法CountdownTimer*timernewCountdownTimer(this);// 60秒倒计时结束弹窗休息一下timer-start(60,休息一下,true);// 结束干点别的connect(timer,CountdownTimer::finished,this,MyClass::onTimeout);// 实时显示剩余时间connect(timer,CountdownTimer::tick,[](intsecondsLeft){qDebug()还剩secondsLeft秒;});多个倒计时同时跑// 支付倒计时15分钟弹窗提醒autopaymentTimernewCountdownTimer(this);paymentTimer-start(900,支付超时订单已取消,true);// 验证码倒计时60秒不弹窗只更新按钮文字autocodeTimernewCountdownTimer(this);codeTimer-start(60,,false);connect(codeTimer,CountdownTimer::tick,[](intleft){ui-getCodeBtn-setText(QString(重新获取(%1)).arg(left));});踩过的坑和经验1. 定时器误差问题QTimer不是精确定时器累积误差会越来越大。用QElapsedTimer记录实际经过的时间每次根据实际耗时计算剩余秒数完美解决。// 错误做法依赖定时器次数累加m_remaining--;if(m_remaining0){/* 结束 */}// 正确做法根据实际耗时计算qint64 elapsedMsm_elapsedTimer.elapsed();intremainingm_totalSeconds-(elapsedMs/1000);2. 弹窗的父窗口传什么// 传 nullptr弹窗独立可能不在最前QMessageBox::information(nullptr,提示,时间到);// 传 activeWindow跟随当前窗口体验更好QMessageBox::information(qApp-activeWindow(),提示,时间到);// 最佳实践允许调用方传入父窗口voidstart(intseconds,constQStringmsg,boolshowPopup,QWidget*parentnullptr);3. 信号别乱用// tick每秒都触发适合刷新UIconnect(timer,CountdownTimer::tick,ui-label,QLabel::setNum);// remainingSecondsChanged值变化才触发适合QML绑定// QML里可以直接写 timer.remainingSeconds// finished只触发一次适合业务逻辑connect(timer,CountdownTimer::finished,this,Order::cancel);Q_PROPERTY 是什么鬼很多新手看到这个一脸懵简单说Q_PROPERTY(intremainingSeconds READ remainingSeconds NOTIFY remainingSecondsChanged)这句话的意思是我把remainingSeconds这个属性暴露给 Qt 的元对象系统了。有啥用QML 可以直接绑定这个属性值变了 UI 自动更新Qt Designer 里能显示和编辑可以配合 QPropertyAnimation 做动画什么时候用要做 QML 界面 → 必须用纯 C Widgets 项目 → 可以不用我加这个是考虑到产品经理随时可能让上 QML提前写好省的回头改。几个真实使用场景场景考试系统倒计时classExamSystem:publicQWidget{CountdownTimer*m_timer;voidstartExam(){m_timernewCountdownTimer(this);m_timer-start(3600,考试时间到系统自动交卷,true);// 实时显示connect(m_timer,CountdownTimer::tick,this,ExamSystem::updateDisplay);// 自动交卷connect(m_timer,CountdownTimer::finished,this,ExamSystem::autoSubmit);// 每分钟自动保存connect(m_timer,CountdownTimer::remainingSecondsChanged,[this](){if(m_timer-remainingSeconds()%600){saveProgress();}});}};场景游戏技能冷却classSkillCoolDown{QMapQString,CountdownTimer*m_cooldowns;voidcastSkill(constQStringskillName,intcooldownSec){if(isOnCooldown(skillName)){QMessageBox::warning(nullptr,提示,技能还在冷却中);return;}autotimernewCountdownTimer(this);timer-start(cooldownSec,,false);m_cooldowns[skillName]timer;// 更新UI按钮文字connect(timer,CountdownTimer::tick,[](intleft){updateButtonText(skillName,left);});connect(timer,CountdownTimer::finished,[](){m_cooldowns.remove(skillName);enableButton(skillName);});}};写在最后这个倒计时类我已经在三个项目里用过了目前没出过问题。核心就两点用 QElapsedTimer 保证精度用 QTimer 做触发但别依赖它的计次

更多文章