如何在 C++ 代码中的一些“繁重”操作之前使 QML 对象可见

Posted

技术标签:

【中文标题】如何在 C++ 代码中的一些“繁重”操作之前使 QML 对象可见【英文标题】:How to make QML object visible before some "heavy" operation in C++ code 【发布时间】:2019-04-23 18:04:55 【问题描述】:

我在 QML 中有以下逻辑:

button click handler in QML:

1) rectangle.visible=true

2) call some C++ method

在 C++ 方法中,我调用 QFile::copy,它从 USB 存储复制文件,并将日志打印到上面的矩形(必须已经可见)。但据我了解,QML 仅在执行按钮单击处理程序后才使元素可见,但 QFile::copy 太慢,所以我只有在复制所有文件后才能看到日志(矩形变为可见)。所以我的问题是,如何在调用 QFile::copy 之前使包含日志的矩形可见(真正“可见”)。当然我可以实现异步复制,但我是 Qt 新手,所以可能有一些解决方案。

谢谢

【问题讨论】:

我建议您在不同的线程中运行繁重的任务,WorkerScript(QML) 或 QThread (C++),这样 GUI 线程就不会被阻塞。 将您在main.cpp 中的消息记录信号连接到您的window(引擎上下文的根对象),因此每当发出新文本时,您都可以将其添加到您的rectangle。还要考虑上面的注释(在你的主线程中创建一个 c++ 类并使用moveToThread,这样你就可以将它与主线程解耦。 【参考方案1】:

把你的大任务放在QThread

任务.hpp

#ifndef THETASK_HPP
#define THETASK_HPP

#include <QThread>

class TheTask: public QThread

    Q_OBJECT

    public:
        TheTask(/* Put operation args here, or through some setters. */);

    protected:
        void run() override;

        // Put stuff for storing operation's arguments and results here.
;

#endif  // THETASK_HPP

任务.cpp

#include "thetask.hpp"

TheTask::TheTask() :
    QThread()
    // Init stuff for storing operation's arguments and results.


void TheTask::run() 
    // Put your heavy C++ operation here

heavy.hpp

#ifndef HEAVY_HPP
#define HEAVY_HPP

#include <QObject>
#include <QList>
class TheTask;

class Heavy: public QObject

    Q_OBJECT

    public:
        Heavy();
        virtual ~Heavy();

        /// @brief QML registration
        static void declareQML();

        /// @brief Your heavy operation
        Q_INVOKABLE void doTheBigOperation(/* Operation args */);

    protected:
        /// @brief Storing the running threads for objects lifecycle reasons.
        /// 
        /// The bigOp object would be destroyed at the end of the
        /// Heavy::doTheBigOperation() method. In order to keep it alive,
        /// let's store it in a list.
        QList<TheTask *> bigOps;

    signals:
        /// @brief Emitted when everything is finished.
        void afterBigOp(/* Put operation results here */);

    protected slots:
        /// @brief Treatments to do when the operation is finished.
        void bigOpFinished();
;

#endif  // HEAVY_HPP

heavy.cpp

#include "anchor.hpp"
#include <QQmlEngine>
#include "thetask.hpp"

Heavy::Heavy() :
    QObject(),
    bigOps()


Heavy::~Heavy() 
    // Delete threads pointers properly.
    while (!bigOps.isEmpty()) 
        TheTask * thread = bigOps.takeLast();

        // Stopping threads. Be careful on consequences.
        thread->quit();
        thread->wait();

        thread->deleteLater();
    


void Heavy::declareQML() 
    qmlRegisterType<Heavy>("CppGates", 13, 37, "Heavy");


void Heavy::doTheBigOperation(/* Operation args */) 
    TheTask * bigOp = new TheTask(/* Operation args */);

    // A thread emits the QThread::finised() signal when its task is finished.
    connect(bigOp, &TheTask::finished,
            this,  &Heavy::bigOpFinished);

    // Keeping the thread alive (cf. bigOps documentation).
    bigOps << bigOp;

    // Start executing the heavy operation.
    bigOp->start();


void Heavy::bigOpFinished() 
    // Retrieving the thread, which is the signal sender.
    TheTask * thread = qobject_cast<TheTask *>(sender());

    // The treatment is over: let's broke communication with its thread.
    disconnect(thread, &TheTask::finished,
               this,   &Heavy::bigOpFinished);

    // Extract operation results from the thread.

    // Removing the thread from the running threads list.
    int threadIndex = bigOps.indexOf(thread);
    bigOps.removeAt(threadIndex);

    thread->deleteLater();

    // Telling QML that the heavy operation is over.
    emit afterBigOp(/* Operation results */);

RectComp.qml

import QtQuick 2.12
import CppGates 13.37

Item 
    id: qml_comp

    Heavy  id: controller 

    Rectangle 
        id: rectangle

        // ...
    

    function doItHeavy() 
        rectangle.visible = false
        controller.doTheBigOperation(/* Operation arguments */)
    

    function afterTheBigOp(/* Operation results */) 
        // Put here things you would like to do after controller.doTheBigOperation()
    

    Component.onCompleted: 
        // Connecting a callback to the signal emitted after the heavy operation.
        controller.afterBigOp.connect(qml_comp.afterTheBigOp)
    


main.cpp

#include <QApplication>
#include <QQmlApplicationEngine>
#include "heavy.hpp"

int main(int argc, char ** argv() 
    QApplication app(argc, argv);

    // ...

    Heavy::declareQML();

    // ...

    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
    int res = engine.rootObjects().isEmpty() ? -1 : app.exec();

    // ...

    return res;

更多信息,请查看QThread的参考:https://doc.qt.io/qt-5/qthread.html

【讨论】:

【参考方案2】:

不涉及使用线程的一种可能的“简单”解决方案是使用 qml 计时器延迟调用您的 c++ 方法:

QML 中的按钮点击处理程序:

    rectangle.visible=true 延迟“d”的呼叫计时器 在“d”之后,计时器将触发并调用您的 C++ 方法(届时, 矩形应该已经可见

代码:

Rectangle 
   id: rectangle

Button 
   onClicked: 
      rectangle.visible = true
      timer.start()
   

Timer 
   id: timer
   interval: 100
   onTriggered: myCppMethod()

请注意,这不会阻止您的应用程序在 c++ 方法执行期间变得无响应。更好的方法是将您的 cpp 方法移动到另一个线程并使用信号和插槽从主线程调用它。

【讨论】:

以上是关于如何在 C++ 代码中的一些“繁重”操作之前使 QML 对象可见的主要内容,如果未能解决你的问题,请参考以下文章

在繁重的模拟代码中,c++ 类/结构会明显慢于 c-array [关闭]

如何使用房间执行繁重的数据库操作?

如何在构建之前使CMake运行python脚本,以便为我的项目生成文件以在构建中使用?

我的 C++ 程序中的一些代码使程序崩溃。我正在实现 BFS 算法

如何使托管代码中的 c++ dll 中的其他 dll 可以使用方法?

如何保护 QThread 函数,使其在完成之前的工作之前不会再次被调用?