QTimer与QDateTime实战应用 - 从时钟到定时器的完整实现

📌 前言

QTimerQDateTime是Qt中处理时间相关功能的核心类。本文将深入讲解这两个类的使用方法,并通过实战示例展示如何实现时钟、倒计时、定时提醒等功能。


🎯 学习目标

  • ✅ 掌握QTimer的基本使用和高级特性
  • ✅ 理解QDateTime的时间处理方法
  • ✅ 学会实现实时时钟
  • ✅ 掌握倒计时功能实现
  • ✅ 了解定时任务和周期性任务
  • ✅ 掌握性能优化技巧

1️⃣ QTimer 核心概念

什么是QTimer?

QTimer 是Qt提供的定时器类,用于在特定时间间隔后执行代码。

基本工作原理

创建QTimer
    ↓
设置时间间隔 (ms)
    ↓
连接timeout信号到槽
    ↓
启动定时器
    ↓
每隔指定时间 → emit timeout()
    ↓
执行关联的槽函数

三种使用模式

模式1:单次触发(Single Shot)
// 3秒后执行一次
QTimer::singleShot(3000, this, [this]() {
    qDebug() << "3秒后执行";
});

// 延迟执行任务
QTimer::singleShot(0, this, &MyClass::heavyTask);  // 下一个事件循环执行
模式2:重复触发(Repeating)
QTimer *timer = new QTimer(this);
timer->setInterval(1000);  // 每1秒触发
connect(timer, &QTimer::timeout, this, &MyClass::onTimeout);
timer->start();
模式3:精确定时(Precise Timer)
QTimer *timer = new QTimer(this);
timer->setInterval(1000);
timer->setTimerType(Qt::PreciseTimer);  // 精确定时
timer->start();

2️⃣ 实战:实时时钟

完整实现

ClockModel.h
#ifndef CLOCKMODEL_H
#define CLOCKMODEL_H

#include <QObject>
#include <QTimer>
#include <QDateTime>
#include <QString>

class ClockModel : public QObject
{
    Q_OBJECT
    
    Q_PROPERTY(QString currentTime READ currentTime NOTIFY timeChanged)
    Q_PROPERTY(QString currentDate READ currentDate NOTIFY dateChanged)
    Q_PROPERTY(QString currentWeekDay READ currentWeekDay NOTIFY weekDayChanged)
    Q_PROPERTY(bool is24HourFormat READ is24HourFormat WRITE setIs24HourFormat NOTIFY formatChanged)
    Q_PROPERTY(bool isRunning READ isRunning NOTIFY runningChanged)
    
public:
    explicit ClockModel(QObject *parent = nullptr);
    ~ClockModel();
    
    // 属性访问器
    QString currentTime() const { return m_currentTime; }
    QString currentDate() const { return m_currentDate; }
    QString currentWeekDay() const { return m_currentWeekDay; }
    bool is24HourFormat() const { return m_is24HourFormat; }
    bool isRunning() const { return m_timer && m_timer->isActive(); }
    
    // 属性设置器
    void setIs24HourFormat(bool format);
    
    // QML可调用方法
    Q_INVOKABLE void start();        // 启动时钟
    Q_INVOKABLE void stop();         // 停止时钟
    Q_INVOKABLE void toggleFormat(); // 切换格式
    
signals:
    void timeChanged();
    void dateChanged();
    void weekDayChanged();
    void formatChanged();
    void runningChanged();
    
private slots:
    void updateTime();  // 定时器槽函数
    
private:
    void formatTime();
    void formatDate();
    void formatWeekDay();
    
    QTimer *m_timer;
    QDateTime m_dateTime;
    QString m_currentTime;
    QString m_currentDate;
    QString m_currentWeekDay;
    bool m_is24HourFormat;
};

#endif // CLOCKMODEL_H
ClockModel.cpp
#include "ClockModel.h"

ClockModel::ClockModel(QObject *parent)
    : QObject(parent)
    , m_timer(new QTimer(this))
    , m_is24HourFormat(true)
{
    // 初始化时间
    updateTime();
    
    // 连接定时器信号
    connect(m_timer, &QTimer::timeout, this, &ClockModel::updateTime);
    
    // 配置定时器
    m_timer->setInterval(1000);        // 1秒
    m_timer->setTimerType(Qt::PreciseTimer);  // 精确定时
    
    // 自动启动
    start();
}

ClockModel::~ClockModel()
{
    stop();
}

void ClockModel::start()
{
    if (!m_timer->isActive()) {
        m_timer->start();
        emit runningChanged();
    }
}

void ClockModel::stop()
{
    if (m_timer->isActive()) {
        m_timer->stop();
        emit runningChanged();
    }
}

void ClockModel::toggleFormat()
{
    setIs24HourFormat(!m_is24HourFormat);
}

void ClockModel::setIs24HourFormat(bool format)
{
    if (m_is24HourFormat != format) {
        m_is24HourFormat = format;
        formatTime();  // 立即更新时间显示
        emit formatChanged();
    }
}

void ClockModel::updateTime()
{
    // 获取当前时间
    m_dateTime = QDateTime::currentDateTime();
    
    // 更新各部分
    formatTime();
    formatDate();
    formatWeekDay();
}

void ClockModel::formatTime()
{
    QString newTime;
    
    if (m_is24HourFormat) {
        // 24小时制:HH:mm:ss
        newTime = m_dateTime.toString("HH:mm:ss");
    } else {
        // 12小时制:h:mm:ss AP
        newTime = m_dateTime.toString("h:mm:ss AP");
    }
    
    // 只在值改变时发送信号
    if (m_currentTime != newTime) {
        m_currentTime = newTime;
        emit timeChanged();
    }
}

void ClockModel::formatDate()
{
    // 日期格式:yyyy年MM月dd日
    QString newDate = m_dateTime.toString("yyyy年MM月dd日");
    
    if (m_currentDate != newDate) {
        m_currentDate = newDate;
        emit dateChanged();
    }
}

void ClockModel::formatWeekDay()
{
    // 获取星期几
    int dayOfWeek = m_dateTime.date().dayOfWeek();  // 1-7 (Monday-Sunday)
    
    static const QStringList weekDays = {
        "星期一", "星期二", "星期三", "星期四",
        "星期五", "星期六", "星期日"
    };
    
    QString newWeekDay = weekDays[dayOfWeek - 1];
    
    if (m_currentWeekDay != newWeekDay) {
        m_currentWeekDay = newWeekDay;
        emit weekDayChanged();
    }
}

QML界面

import QtQuick 2.15

Rectangle {
    anchors.fill: parent
    color: "#0F2027"
    
    Column {
        anchors.centerIn: parent
        spacing: 20
        
        // 时间显示
        Text {
            text: clockModel.currentTime
            font.pixelSize: 120
            font.bold: true
            color: "#FFFFFF"
            anchors.horizontalCenter: parent.horizontalCenter
        }
        
        // 日期和星期
        Column {
            spacing: 10
            anchors.horizontalCenter: parent.horizontalCenter
            
            Text {
                text: clockModel.currentDate
                font.pixelSize: 36
                color: "#B0E0E6"
                anchors.horizontalCenter: parent.horizontalCenter
            }
            
            Text {
                text: clockModel.currentWeekDay
                font.pixelSize: 28
                color: "#87CEEB"
                anchors.horizontalCenter: parent.horizontalCenter
            }
        }
        
        // 控制按钮
        Row {
            spacing: 20
            anchors.horizontalCenter: parent.horizontalCenter
            
            Rectangle {
                width: 100
                height: 40
                color: "#00D4FF"
                radius: 5
                
                Text {
                    text: clockModel.isRunning ? "暂停" : "启动"
                    anchors.centerIn: parent
                }
                
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        if (clockModel.isRunning) {
                            clockModel.stop()
                        } else {
                            clockModel.start()
                        }
                    }
                }
            }
            
            Rectangle {
                width: 100
                height: 40
                color: "#00D4FF"
                radius: 5
                
                Text {
                    text: "切换格式"
                    anchors.centerIn: parent
                }
                
                MouseArea {
                    anchors.fill: parent
                    onClicked: clockModel.toggleFormat()
                }
            }
        }
    }
}

3️⃣ QDateTime 完全指南

创建QDateTime对象

// 方式1:当前时间
QDateTime now = QDateTime::currentDateTime();

// 方式2:指定日期和时间
QDateTime dt = QDateTime(QDate(2025, 11, 8), QTime(14, 30, 0));

// 方式3:从字符串解析
QDateTime dt = QDateTime::fromString("2025-11-08 14:30:00", "yyyy-MM-dd HH:mm:ss");

// 方式4:从时间戳
QDateTime dt = QDateTime::fromSecsSinceEpoch(1699437600);

时间格式化

QDateTime dt = QDateTime::currentDateTime();

// 标准格式
dt.toString("yyyy-MM-dd HH:mm:ss");      // 2025-11-08 14:30:25
dt.toString("yyyy年MM月dd日");            // 2025年11月08日
dt.toString("HH:mm:ss");                 // 14:30:25
dt.toString("h:mm:ss AP");               // 2:30:25 PM

// 完整格式
dt.toString(Qt::ISODate);                // 2025-11-08T14:30:25
dt.toString(Qt::TextDate);               // Fri Nov 8 14:30:25 2025
dt.toString(Qt::RFC2822Date);            // Fri, 08 Nov 2025 14:30:25 +0800

// 自定义格式
dt.toString("ddd MMM d yyyy");           // Fri Nov 8 2025
dt.toString("dddd, MMMM d, yyyy");       // Friday, November 8, 2025

格式化符号说明

符号 含义 示例
yyyy 4位年份 2025
yy 2位年份 25
MM 月份(01-12) 11
M 月份(1-12) 11
MMM 月份简写 Nov
MMMM 月份全名 November
dd 日期(01-31) 08
d 日期(1-31) 8
ddd 星期简写 Fri
dddd 星期全名 Friday
HH 小时(00-23) 14
H 小时(0-23) 14
hh 小时(01-12) 02
h 小时(1-12) 2
mm 分钟(00-59) 30
m 分钟(0-59) 30
ss 秒(00-59) 25
s 秒(0-59) 25
zzz 毫秒 123
AP AM/PM PM
ap am/pm pm

时间计算

QDateTime now = QDateTime::currentDateTime();

// 加减时间
QDateTime tomorrow = now.addDays(1);
QDateTime nextWeek = now.addDays(7);
QDateTime nextMonth = now.addMonths(1);
QDateTime nextYear = now.addYears(1);
QDateTime later = now.addSecs(3600);  // 1小时后

// 时间差
QDateTime startTime = QDateTime::currentDateTime();
// ... 执行某些操作 ...
QDateTime endTime = QDateTime::currentDateTime();

qint64 secondsDiff = startTime.secsTo(endTime);
qint64 daysDiff = startTime.daysTo(endTime);

qDebug() << "耗时:" << secondsDiff << "秒";

时间比较

QDateTime dt1 = QDateTime::currentDateTime();
QDateTime dt2 = dt1.addSecs(60);

if (dt1 < dt2) {
    qDebug() << "dt1早于dt2";
}

if (dt1 == dt2) {
    qDebug() << "时间相同";
}

// 判断是否为有效时间
if (dt1.isValid()) {
    qDebug() << "时间有效";
}

4️⃣ 实战:倒计时功能

完整实现

class CountdownModel : public QObject
{
    Q_OBJECT
    
    Q_PROPERTY(int remainingSeconds READ remainingSeconds NOTIFY remainingChanged)
    Q_PROPERTY(QString displayTime READ displayTime NOTIFY displayChanged)
    Q_PROPERTY(bool isRunning READ isRunning NOTIFY runningChanged)
    
public:
    explicit CountdownModel(QObject *parent = nullptr)
        : QObject(parent)
        , m_timer(new QTimer(this))
        , m_remainingSeconds(0)
    {
        connect(m_timer, &QTimer::timeout, this, &CountdownModel::tick);
    }
    
    int remainingSeconds() const { return m_remainingSeconds; }
    bool isRunning() const { return m_timer->isActive(); }
    
    QString displayTime() const {
        int hours = m_remainingSeconds / 3600;
        int minutes = (m_remainingSeconds % 3600) / 60;
        int seconds = m_remainingSeconds % 60;
        
        return QString("%1:%2:%3")
            .arg(hours, 2, 10, QChar('0'))
            .arg(minutes, 2, 10, QChar('0'))
            .arg(seconds, 2, 10, QChar('0'));
    }
    
    Q_INVOKABLE void start(int seconds) {
        if (seconds > 0) {
            m_remainingSeconds = seconds;
            emit remainingChanged();
            emit displayChanged();
            
            m_timer->start(1000);  // 每秒触发
            emit runningChanged();
        }
    }
    
    Q_INVOKABLE void stop() {
        m_timer->stop();
        emit runningChanged();
    }
    
    Q_INVOKABLE void reset() {
        stop();
        m_remainingSeconds = 0;
        emit remainingChanged();
        emit displayChanged();
    }
    
signals:
    void remainingChanged();
    void displayChanged();
    void runningChanged();
    void finished();  // 倒计时结束
    
private slots:
    void tick() {
        if (m_remainingSeconds > 0) {
            m_remainingSeconds--;
            emit remainingChanged();
            emit displayChanged();
            
            if (m_remainingSeconds == 0) {
                stop();
                emit finished();  // 通知倒计时结束
            }
        }
    }
    
private:
    QTimer *m_timer;
    int m_remainingSeconds;
};

QML界面

Column {
    spacing: 20
    
    // 倒计时显示
    Text {
        text: countdownModel.displayTime
        font.pixelSize: 80
        color: countdownModel.remainingSeconds < 10 ? "red" : "white"
    }
    
    // 控制按钮
    Row {
        spacing: 10
        
        Button {
            text: "5分钟"
            onClicked: countdownModel.start(300)
        }
        
        Button {
            text: "10分钟"
            onClicked: countdownModel.start(600)
        }
        
        Button {
            text: "停止"
            onClicked: countdownModel.stop()
        }
        
        Button {
            text: "重置"
            onClicked: countdownModel.reset()
        }
    }
    
    // 监听倒计时结束
    Connections {
        target: countdownModel
        onFinished: {
            console.log("倒计时结束!")
            // 播放提示音、弹出通知等
        }
    }
}

5️⃣ 性能优化

1. 选择合适的定时器类型

// Qt::PreciseTimer - 精确定时(默认,1ms精度)
timer->setTimerType(Qt::PreciseTimer);

// Qt::CoarseTimer - 粗糙定时(5%误差,省电)
timer->setTimerType(Qt::CoarseTimer);

// Qt::VeryCoarseTimer - 非常粗糙(1秒误差,最省电)
timer->setTimerType(Qt::VeryCoarseTimer);

选择建议:

  • 动画、游戏 → PreciseTimer
  • 时钟显示 → CoarseTimer(1秒误差可接受)
  • 后台任务 → VeryCoarseTimer

2. 避免频繁启停

// ❌ 不好:频繁启停
void updateDisplay() {
    m_timer->stop();
    // 做一些事情
    m_timer->start(1000);
}

// ✅ 好:保持运行
void updateDisplay() {
    // 定时器持续运行
    // 在timeout槽中处理更新
}

3. 批量更新

// ❌ 不好:每次都发信号
void tick() {
    m_seconds--;
    emit secondsChanged();
    
    m_minutes = m_seconds / 60;
    emit minutesChanged();
    
    m_hours = m_seconds / 3600;
    emit hoursChanged();
}

// ✅ 好:一次更新
void tick() {
    m_seconds--;
    updateTimeComponents();  // 内部更新所有值
    emit timeChanged();      // 只发一次信号
}

🎓 总结

本文详细讲解了QTimer和QDateTime的使用:

  1. ✅ QTimer的三种使用模式
  2. ✅ 实时时钟的完整实现
  3. ✅ QDateTime的创建和格式化
  4. ✅ 时间计算和比较
  5. ✅ 倒计时功能实现
  6. ✅ 性能优化技巧

核心要点:

  • QTimer是基于事件循环的定时器
  • 使用精确定时器类型获得更好精度
  • QDateTime提供丰富的时间处理方法
  • 合理使用信号避免频繁更新
  • 根据场景选择合适的定时器类型

作者: 坚果派
日期: 2025-11-08
系列: Qt for HarmonyOS 深度解析

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐