Qt Quick 定时技巧全攻略:从底层原理到高级应用(一)https://developer.aliyun.com/article/1464354
四、多线程计时器的应用(Application of multithread timer)
4.1 使用 QTimer 和 QThread 实现多线程计时器
在多线程环境中使用 QTimer
可以在不阻塞主线程的同时执行定时任务。以下是如何使用 QTimer
和 QThread
实现多线程计时器的步骤。
1. 创建一个自定义 QObject 类
首先,创建一个自定义 QObject
类 Worker
,用于包装定时器,并在其内部创建槽以响应定时器超时信号。
#include <QObject> #include <QTimer> class Worker : public QObject { Q_OBJECT public: explicit Worker(QObject* parent = nullptr): QObject(parent) {} public slots: void start() { QTimer* timer = new QTimer(this); connect(timer, &QTimer::timeout, this, &Worker::handleTimeout); timer->start(1000); } void handleTimeout() { // 你的超时处理代码 } };
2. 将 Worker 类移至新线程并启动定时器
在接下来的步骤中,创建一个 Worker
对象实例并将其移至新线程。然后,连接线程的 started
信号和 Worker
的 start
槽,以便在线程启动时启动计时器。
#include <QCoreApplication> #include <QThread> #include "worker.h" int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QThread workerThread; Worker worker; worker.moveToThread(&workerThread); QObject::connect(&workerThread, &QThread::started, &worker, &Worker::start); QObject::connect(&workerThread, &QThread::finished, &app, &QCoreApplication::quit); workerThread.start(); return app.exec(); }
现在你已成功实现一个多线程计时器。当线程启动时,Worker
类中的 start()
槽将被调用,从而启动定时器。每次定时器触发时,handleTimeout()
槽被调用并执行所需的任务。
通过这种方法,我们将定时任务分配到了独立的线程,避免了可能影响应用程序性能的主线程阻塞。在需要执行耗时任务的应用程序中,这种多线程计时器实现方法非常有用。
4.2 使用 QQmlApplicationEngine 和多线程定时器
当你需要在 QML 和 C++ 之间共享和同步定时任务时,可以使用 QQmlApplicationEngine
和多线程定时器。这种方法可以确保长时间运行的计时器不阻塞主线程和用户界面。
1. 在 Worker 类中搭建 QTimer 和 QQmlApplicationEngine
首先,在自定义 QObject
类中搭建定时器(QTimer
),并将其移入子线程。同时,创建一个用于保存 QQmlApplicationEngine
引用的对象,以便在槽函数中访问 QML 的对象:
#include <QObject> #include <QQmlApplicationEngine> #include <QTimer> class Worker : public QObject { Q_OBJECT public: explicit Worker(QQmlApplicationEngine* engine, QObject* parent = nullptr) : QObject(parent), m_engine(engine) {} public slots: void start() { QTimer* timer = new QTimer(this); timer->setInterval(1000); connect(timer, &QTimer::timeout, this, &Worker::handleTimeout); timer->start(); } void handleTimeout() { // 处理定时器超时信号和引用 QQmlApplicationEngine 的代码 } private: QQmlApplicationEngine* m_engine; };
2. 将 Worker 类移至新线程并启动定时器
接下来的步骤类似于前面的示例。不过,在这里,我们需要将 QQmlApplicationEngine
的引用传递给 Worker
类,并将其移至新线程。然后,连接新线程的 started
信号与 Worker
的 start
槽,以便在线程启动时启动定时器。
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QThread> #include "worker.h" int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); QQmlApplicationEngine engine; const QUrl url(QStringLiteral("qrc:/main.qml")); engine.load(url); QThread workerThread; Worker worker(&engine); worker.moveToThread(&workerThread); QObject::connect(&workerThread, &QThread::started, &worker, &Worker::start); QObject::connect(&workerThread, &QThread::finished, &worker, &QObject::deleteLater); workerThread.start(); int exitCode = app.exec(); workerThread.quit(); workerThread.wait(); return exitCode; }
现在你已成功实现在 QML 和 C++ 之间共享和同步的多线程计时器。当线程启动时,Worker
类中的 start()
槽将被调用,从而启动定时器。每次定时器触发时,handleTimeout()
槽被调用,可访问 QML 中的对象并执行所需的任务。这种方法将计时器任务分配到了独立的线程,同时保持了 QML 和 C++ 的交互能力。
4.3 在 C++ 和 QML 之间同步计时器和子线程的属性
在某些情况下,你会在 C++ 代码中处理定时器任务,但还希望以同步属性的方式将 QML 和 C++ 连接起来。
1. 基于属性改进 Worker 类
首先,为 Worker 类添加一个属性。例如,这里我们添加一个名为 counter
的属性,每次定时器触发时增加。确保为 Worker 类添加 Q_PROPERTY 宏,以实现数据绑定:
#include <QObject> #include <QTimer> #include <QQmlEngine> class Worker : public QObject { Q_OBJECT Q_PROPERTY(int counter READ counter NOTIFY counterChanged) public: explicit Worker(QObject* parent = nullptr) : QObject(parent), m_counter(0) { QTimer* timer = new QTimer(this); timer->setInterval(1000); connect(timer, &QTimer::timeout, this, &Worker::handleTimeout); timer->start(); } int counter() const { return m_counter; } signals: void counterChanged(int counter); private slots: void handleTimeout() { m_counter++; emit counterChanged(m_counter); } private: int m_counter; };
2. 将 Worker 类的对象实例作为 QML 上下文属性
要将 C++ 和 QML 连接起来,请在 QML 引擎加载之后添加一个叫做 “worker” 的上下文属性:
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include "worker.h" int main(int argc, char* argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); QQmlApplicationEngine engine; // 创建 Worker 对象并将其设置为 QML 上下文的属性 Worker worker; engine.rootContext()->setContextProperty("worker", &worker); const QUrl url(QStringLiteral("qrc:/main.qml")); engine.load(url); return app.exec(); }
3. 在 QML 中绑定 Worker 对象属性
现在,你可以访问已在 QML 中定义的 Worker 对象的属性。这里我们创建一个文本框,显示 counter
属性:
import QtQuick 2.12 import QtQuick.Window 2.12 Window { id: root width: 640 height: 480 visible: true Text { id: counterText text: "Counter: " + worker.counter anchors.centerIn: parent font.pixelSize: 24 } }
每次定时器触发时,C++ 端的 Worker 对象会更新其 counter
属性,这会立即反映在 QML 界面上。这种方法允许你更好地同步 C++ 和 QML 之间使用定时器的属性。
4.4 使用 QML 和 C++ 的多线程计时器刷新大量数据
当处理大量数据或频繁更新的数据流时,多线程计时器可以确保界面在刷新数据时仍保持流畅。下面是一个使用 QML 和 C++ 的多线程计时器实现的示例。
1. 创建用于处理数据的 DataProcessor 类
在 C++ 中定义一个用于处理数据的 DataProcessor
类:
#include <QObject> #include <QVector> #include <QRandomGenerator> #include <QMutex> class DataProcessor : public QObject { Q_OBJECT public: explicit DataProcessor(QObject* parent = nullptr) : QObject(parent), m_data(500) {} Q_INVOKABLE QVector<double> getData() { QMutexLocker locker(&m_mutex); return m_data; } public slots: void processData() { QMutexLocker locker(&m_mutex); for (int i = 0; i < m_data.size(); ++i) { m_data[i] = QRandomGenerator::global()->generateDouble(); } emit dataUpdated(); } signals: void dataUpdated(); private: QVector<double> m_data; QMutex m_mutex; };
2. 创建一个 Worker 类以使用多线程计时器
接着,创建一个包含定时器和槽函数的 QObject “Worker” 类,用于将 DataProcessor
类以多线程方式运行。
#include <QObject> #include <QTimer> #include "dataprocessor.h" class Worker : public QObject { Q_OBJECT public: explicit Worker(DataProcessor* dataProcessor, QObject* parent = nullptr) : QObject(parent), m_dataProcessor(dataProcessor) { QTimer* timer = new QTimer(this); timer->setInterval(1000); connect(timer, &QTimer::timeout, this, &Worker::handleTimeout); timer->start(); } public slots: void handleTimeout() { m_dataProcessor->processData(); } private: DataProcessor* m_dataProcessor; };
3. 将 DataProcessor 和 Worker 注册到 QML 中
在主函数中,将 DataProcessor
和 Worker
注册到 QML 中,并将它们添加到上下文属性中。
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlContext> #include <QThread> #include "dataprocessor.h" #include "worker.h" int main(int argc, char* argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); QQmlApplicationEngine engine; DataProcessor dataProcessor; engine.rootContext()->setContextProperty("dataProcessor", &dataProcessor); QThread workerThread; Worker worker(&dataProcessor); worker.moveToThread(&workerThread); connect(&workerThread, &QThread::finished, &worker, &QObject::deleteLater); workerThread.start(); const QUrl url(QStringLiteral("qrc:/main.qml")); engine.load(url); int exitCode = app.exec(); workerThread.quit(); workerThread.wait(); return exitCode; }
4. 在 QML 中显示数据
最后,在 QML 中创建一个 ListView
以显示数据,并在 dataUpdated
信号发出时将该视图刷新。
import QtQuick 2.12 import QtQuick.Window 2.12 import QtQuick.Controls 2.12 Window { id: root width: 640 height: 480 visible: true property int length: 500 Component { id: numberDelegate Text { text: modelData.toFixed(2) font.family: "Courier" font.pixelSize: 12 } } ListView { id: dataListView anchors.fill: parent spacing: 5 model: dataProcessor.getData() delegate: numberDelegate Connections { target: dataProcessor onDataUpdated: { dataListView.model = dataProcessor.getData(); } } } }
现在,当定时器触发时,Worker
将在子线程中执行数据处理,并在 QML 列表视图中刷新该数据。这种多线程计时器的实现方法确保在刷新大量数据时界面不会受到干扰。
第五章:Qt Quick 定时器使用场景和优化
在 Qt Quick 应用程序的开发过程中,计时器在方便用户使用、提升应用程序性能方面有着重要作用。本章将探讨 Qt Quick 定时器在实际应用中的使用场景和优化方法。
5.1 动画和渐变
动画和渐变在许多 Qt Quick 应用程序中都会出现。它们能够让交互变得流畅而有趣。以下是在应用程序中使用计时器实现动画和渐变的方法。
使用 QML PropertyAnimation 进行简单动画
在 QML 中,与使用定时器硬编码动画相比,PropertyAnimation
提供了一种更简便、高效的方式。以下代码展示了如何使用 PropertyAnimation
在 1秒内将矩形的透明度从 0 改变到 1:
import QtQuick 2.12 Rectangle { id: rect width: 100 height: 100 color: "red" opacity: 0 PropertyAnimation { id: animation target: rect property: "opacity" from: 0 to: 1 duration: 1000 } MouseArea { anchors.fill: parent onClicked: { animation.start() } } }
在这个示例中,我们创建了一个关联到矩形对象透明度属性的 PropertyAnimation
。当用户点击矩形时,单击事件触发动画开始,实现了透明度的跃迁效果。
使用 QML SequentialAnimation 和__ParallelAnimation__组合动画
当你需要按特定顺序触发一系列动画时,可以使用 SequentialAnimation
和 ParallelAnimation
。SequentialAnimation
允许你按顺序播放一组动画,而 ParallelAnimation
则同时播放多个动画。
以下示例展示了一个上下移动的矩形,移动过程中跳出一层渐变颜色:
import QtQuick 2.12 Rectangle { id: rect width: 100 height: 100 color: "red" y: 0 SequentialAnimation { id: animation ParallelAnimation { PropertyAnimation { target: rect property: "y" to: 300 duration: 1000 } ColorAnimation { target: rect property: "color" to: "blue" duration: 1000 } } ParallelAnimation { PropertyAnimation { target: rect property: "y" to: 0 duration: 1000 } ColorAnimation { target: rect property: "color" to: "red" duration: 1000 } } loops: Animation.Infinite } MouseArea { anchors.fill: parent onClicked: { animation.start() } } }
在这个示例中,点击矩形时,启动一个 SequentialAnimation
,它顺序播放两个 ParallelAnimation
。每个 ParallelAnimation
同时执行改变矩形的 Y 坐标和颜色的动画。
通过在 QML 中使用计时器和动画,可以实现丰富的动态效果,开发者可以创建更具交互性和吸引力的应用程序。
5.2 避免定时器性能问题
在 Qt Quick 应用程序中,使用定时器时可能会遇到性能问题。为了确保应用程序运行流畅,避免性能下降,以下是一些建议和最佳实践。
1. 将高性能需求任务移到子线程
将耗时任务移到一个独立的子线程可以避免主线程被阻塞,确保用户界面和交互保持流畅。参考前面章节中提及的有关在 C++ 中使用 QTimer
和 QThread
的示例,将定时器任务分配到子线程中,以充分利用多核处理器的能力。
2. 优化定时器间隔和精度
不要设定太短的定时器间隔。设置正确的间隔可以避免CPU集中处理过多更新而影响性能。过短的间隔会导致 CPU 高速运行,这对于某些低优先级的计时器任务是不必要的。另外,考虑定时器的精度和作用,为低优先级任务设置较长的间隔可能更合适。
3. 在 QML 中使用 Timer 对象替代 JavaScript setInterval
在 QML 中,建议使用 Timer
对象而不是 JavaScript 的 setInterval
函数,因为 setInterval
是 JavaScript 特性,其性能可能受到 JavaScript 引擎的限制。Timer
对象通过 Qt Quick C++ 内核进行实现,一般情况下性能更优。
4. 适当使用动画和定时器
当需要执行动画任务时,优先使用 QML 的动画类型(如 PropertyAnimation
、ColorAnimation
等),因为它们通常具有更高的性能。而定时器更适合在定时执行任务时使用,如,定时更新数据或启动/暂停动画等。
5. 避免不必要的定时器运行
有些定时器任务在程序后台运行时并不需要持续更新,例如更新用户界面的任务。对于这种情况,可以在程序失去焦点或切换到后台时暂停定时器。这样可以避免不必要的任务执行,节省系统资源。
通过遵循这些最佳实践,结合性能测试和调优,你可以创建高性能、低耗能的 Qt Quick 应用程序。
5.3 实时数据可视化与定时器
在实时数据可视化应用中,定时器在保持数据更新以及与用户交互时起着关键作用。以下是如何在 Qt Quick 应用程序中实现实时数据可视化和使用定时器的方法。
1. 获取和更新实时数据
要显示实时数据,需要首先获取数据,然后在每个刷新周期内更新界面。可以将从数据源获取数据的函数放在 C++ 侧并通过定时器按间隔更新数据。
更新数据时,建议在 C++ 侧对数据进行处理并将处理后的数据发送至 QML 界面。这样可以减轻 QML 引擎的负担,提高性能。
2. 在 QML 中进行数据可视化
将数据发送至 QML 后,可使用 Qt Quick 提供的各种可视化组件展示数据。以下是一个简单的示例,使用 QML 的 ChartView
展示一个简单的实时折线图。
import QtQuick 2.12 import QtCharts 2.3 ChartView { id: chart anchors.fill: parent legend.visible: false antialiasing: true LineSeries { id: lineSeries axisX: DateTimeAxis { format: "hh:mm:ss" } axisY: ValueAxis {} XYPoint { x: someCppObject.timestamp; y: someCppObject.value } } }
在此示例中,someCppObject
是由 C++ 层提供的包含实时数据的对象。请确保向 QML 上下文注册该对象,以便 QML 中访问。
3. 使用定时器更新可视化数据
要在每个刷新周期内更新界面,可以将定时器任务放在 C++ 侧。当定时器触发时,从数据源获取新数据,然后将新数据追加到 QML 中的 LineSeries
。
此外,也可以在 QML 中使用 Timer
对象触发 C++ 侧的数据更新函数。当使用这种方法时,确保在触发 Timer
的 onTriggered
信号时请求新数据,并将数据追加到 QML 中的图表系列。
通过这些方法,可以在 Qt Quick 应用程序中实现实时数据可视化。使用定时器和嵌套的组件,开发者可以创建自定义界面和组件以满足各种用例和需求。
6.2 控件状态与定时器的协作
在创建定制控件时,管理控件的状态变化对于实现丰富的交互和视觉效果至关重要。结合定时器,我们可以实现更具动态的控件状态变化。本节将介绍如何在 Qt Quick 控件中使用定时器创建基于时间的状态变化。
定时更改控件状态
以下示例展示了如何使用 Timer
在指定的时间间隔内循环切换按钮状态:
import QtQuick 2.12 import QtQuick.Controls 2.12 Button { id: button text: "State: " + state anchors.centerIn: parent states: [ State { name: "state1" PropertyChanges { target: button text: "State: " + button.state } }, State { name: "state2" PropertyChanges { target: button text: "State: " + button.state } } ] Timer { id: timer interval: 1000 running: true repeat: true onTriggered: { button.state = button.state === "state1" ? "state2" : "state1" } } }
在这个示例中,我们首先定义了两个按钮状态 state1
和 state2
。然后使用 Timer
定义了一个间隔为 1000 毫秒的计时器,当计时器触发时切换按钮的状态。在这种方式下,按钮状态会自动在每个循环周期内更改。
基于时间的状态过渡动画
控件状态变化时,可以为过渡添加动画效果使其更具活力。以下示例展示了如何在按钮状态切换时添加淡入淡出效果:
import QtQuick 2.12 import QtQuick.Controls 2.12 Button { id: button text: "State: " + state anchors.centerIn: parent opacity: 1 states: [ State { name: "state1" PropertyChanges { target: button text: "State: " + button.state } }, State { name: "state2" PropertyChanges { target: button text: "State: " + button.state } } ] transitions: [ Transition { from: "state1" to: "state2" SequentialAnimation { NumberAnimation { target: button; property: "opacity" from: 1; to: 0 duration: 500 } NumberAnimation { target: button; property: "opacity" from: 0; to: 1 duration: 500 } } }, Transition { reversible: true from: "state2" to: "state1" SequentialAnimation { NumberAnimation { target: button; property: "opacity" from: 1; to: 0 duration: 500 } NumberAnimation { target: button; property: "opacity" from: 0; to: 1 duration: 500 } } } ] Timer { id: timer interval: 2000 running: true repeat: true onTriggered: { button.state = button.state === "state1" ? "state2" : "state1"; } } }
在此示例中,当按钮状态从 state1
切换到 state2
,或从 state2
切换到 state1
时,按钮文本会通过一个淡入淡出效果实现过渡。定时器循环触发时,将自动切换状态并播放过渡动画。
结合定时器和基于时间的状态变化,我们可以创建具有动态交互效果的定制控件。这有助于增强用户体验,提升应用程序的吸引力。
6.3 为控件添加超时和自动隐藏功能
在某些情况下,你可能希望控件在用户交互后一段时间内完成某项任务,或控件在一定时间内未被使用时自动隐藏。本节将介绍如何使用定时器为 Qt Quick 控件添加超时和自动隐藏功能。
控件超时示例
以下示例展示了如何为按钮添加一个超时功能,点击按钮后,按钮会在2秒内更改其文本并在时间结束后恢复:
import QtQuick 2.12 import QtQuick.Controls 2.12 Button { id: button text: "Click me" anchors.centerIn: parent property string defaultText: "Click me" property string timeoutText: "Timeout in progress" Timer { id: timeoutTimer interval: 2000 onTriggered: { button.text = button.defaultText; } } onClicked: { text = timeoutText; timeoutTimer.restart(); } }
在这个示例中,我们定义了一个计时器 timeoutTimer
来实现超时功能。点击按钮后,文本会更改为 “Timeout in progress”,并开始计时。2秒后,计时器触发并将按钮文本恢复到 “Click me”。
控件自动隐藏示例
以下示例展示了如何为按钮添加自动隐藏功能,当按钮在3秒内未被点击时自动隐藏:
import QtQuick 2.12 import QtQuick.Controls 2.12 Button { id: button text: "Click to reset hide timer" anchors.centerIn: parent opacity: 1 Timer { id: hideTimer interval: 3000 running: true repeat: false onTriggered: { button.opacity = 0; } } onClicked: { opacity = 1; hideTimer.restart(); } }
在这个示例中,我们使用计时器 hideTimer
实现自动隐藏功能。当按钮在3秒内未被点击时,计时器触发,将按钮的不透明度设置为0使其隐藏。点击按钮时,不透明度恢复为1,按钮重新显示,并重启计时器。
通过使用定时器,我们可以为 Qt Quick 控件添加超时和自动隐藏功能。这些功能有助于实现更丰富的交互效果,提升用户体验。