在Qt Quick中阻止操作

时间:2016-02-06 13:58:39

标签: c++ qt qml qtquick2

我想在单击按钮时执行长任务。我希望此任务阻止UI,因为在任务完成之前应用程序无法运行。但是,我想向用户表明发生了一些事情,所以我有一个BusyIndicator(在渲染线程上运行)并设置为在操作开始之前显示。但是,它永远不会呈现。为什么呢?

main.cpp中:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QDateTime>
#include <QDebug>

class Task : public QObject
{
    Q_OBJECT
    Q_PROPERTY(bool running READ running NOTIFY runningChanged)

public:
    Task() : mRunning(false) {}

    Q_INVOKABLE void run() {
        qDebug() << "setting running property to true";
        mRunning = true;
        emit runningChanged();

        // Try to ensure that the scene graph has time to begin the busy indicator
        // animation on the render thread.
        Q_ASSERT(QMetaObject::invokeMethod(this, "doRun", Qt::QueuedConnection));
    }

    bool running() const {
        return mRunning;
    }

signals:
    void runningChanged();

private:
    Q_INVOKABLE void doRun() {
        qDebug() << "beginning long, blocking operation";
        QDateTime start = QDateTime::currentDateTime();
        while (start.secsTo(QDateTime::currentDateTime()) < 2) {
            // Wait...
        }
        qDebug() << "finished long, blocking operation";

        qDebug() << "setting running property to false";
        mRunning = false;
        emit runningChanged();
    }

    bool mRunning;
};

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    Task task;
    engine.rootContext()->setContextProperty("task", &task);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    return app.exec();
}

#include "main.moc"

main.qml:

import QtQuick 2.6
import QtQuick.Window 2.2
import Qt.labs.controls 1.0

Window {
    width: 600
    height: 400
    visible: true

    Shortcut {
        sequence: "Ctrl+Q"
        onActivated: Qt.quit()
    }

    Column {
        anchors.centerIn: parent
        spacing: 20

        Button {
            text: task.running ? "Running task" : "Run task"
            onClicked: task.run()
        }
        BusyIndicator {
            anchors.horizontalCenter: parent.horizontalCenter
            running: task.running
            onRunningChanged: print("BusyIndicator running =", running)
        }
    }
}

调试输出在事件顺序方面看起来是正确的:

setting running property to true
qml: BusyIndicator running = true
beginning long, blocking operation
finished long, blocking operation
setting running property to false
qml: BusyIndicator running = false

2 个答案:

答案 0 :(得分:1)

QML中的大多数动画都依赖于主线程中管理的属性,因此在主UI线程被阻止时会被阻止。查看http://doc.qt.io/qt-5/qml-qtquick-animator.html可以在主线程被阻止时运行的动画。如果可能的话,我会将操作转移到另一个线程中,这样做更简单,并且允许例如从UI取消操作。

答案 1 :(得分:0)

使用Qt::QueuedConnection调用函数并不能保证BusyIndicator有机会开始制作动画。它只是guarantees that

  

当控制返回到接收者线程的事件循环时,调用该槽。

可能听起来很有希望的

Another solutionQTimer::singleShot()

QTimer::singleShot(0, this, SLOT(doRun()));
  

作为一种特殊情况,一旦处理了窗口系统事件队列中的所有事件,超时为0的QTimer将超时。这可以用来做繁重的工作,同时提供一个活泼的用户界面[...]

然而,这也没有奏效。我不确定为什么。可能是,在内部,渲染/动画不是通过排队调用完成的,因此超时发生得太早。

您可以指定等待的任意时间量:

QTimer::singleShot(10, this, SLOT(doRun()));

这会起作用,但它并不那么好;它只是在猜测。

你需要的是reliable way知道场景图何时开始动画。

<强> main.cpp中:

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <QDateTime>
#include <QDebug>
#include <QTimer>
#include <QQuickWindow>

class Task : public QObject
{
    Q_OBJECT
    Q_PROPERTY(bool running READ running NOTIFY runningChanged)

public:
    Task(QObject *parent = 0) :
        QObject(parent),
        mRunning(false) {
    }

signals:
    void runningChanged();

public slots:
    void run() {
        qDebug() << "setting running property to true";
        mRunning = true;
        emit runningChanged();

        QQmlApplicationEngine *engine = qobject_cast<QQmlApplicationEngine*>(parent());
        QQuickWindow *window = qobject_cast<QQuickWindow*>(engine->rootObjects().first());
        connect(window, SIGNAL(afterSynchronizing()), this, SLOT(doRun()));
    }

    bool running() const {
        return mRunning;
    }

private slots:
    void doRun() {
        qDebug() << "beginning long, blocking operation";
        QDateTime start = QDateTime::currentDateTime();
        while (start.secsTo(QDateTime::currentDateTime()) < 2) {
            // Wait...
        }
        qDebug() << "finished long, blocking operation";

        QQmlApplicationEngine *engine = qobject_cast<QQmlApplicationEngine*>(parent());
        QQuickWindow *window = qobject_cast<QQuickWindow*>(engine->rootObjects().first());
        disconnect(window, SIGNAL(afterSynchronizing()), this, SLOT(doRun()));

        qDebug() << "setting running property to false";
        mRunning = false;
        emit runningChanged();
    }

private:
    bool mRunning;
};

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    Task task(&engine);
    engine.rootContext()->setContextProperty("task", &task);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));

    return app.exec();
}

#include "main.moc"

<强> main.qml

import QtQuick 2.6
import QtQuick.Window 2.2
import Qt.labs.controls 1.0

Window {
    width: 600
    height: 400
    visible: true

    Shortcut {
        sequence: "Ctrl+Q"
        onActivated: Qt.quit()
    }

    Column {
        anchors.centerIn: parent
        spacing: 20

        Button {
            text: task.running ? "Running task" : "Run task"
            onClicked: task.run()
        }
        BusyIndicator {
            anchors.horizontalCenter: parent.horizontalCenter
            running: task.running
            onRunningChanged: print("BusyIndicator running =", running)
        }
    }
}

此解决方案依赖于访问应用程序窗口,该窗口 很好,但它消除了任何猜测。请注意,如果我们之后不再断开信号,则每次场景图完成同步时都会继续调用它,因此执行此操作非常重要。

如果您有多个需要此类解决方案的操作,请考虑创建可重用的类:

class BlockingTask : public QObject
{
    Q_OBJECT
    Q_PROPERTY(bool running READ running NOTIFY runningChanged)

public:
    BlockingTask(QQmlApplicationEngine *engine) :
        mEngine(engine),
        mRunning(false) {
    }

    bool running() const {
        return mRunning;
    }

signals:
    void runningChanged();

public slots:
    void run() {
        qDebug() << "setting running property to true";
        mRunning = true;
        emit runningChanged();

        QQuickWindow *window = qobject_cast<QQuickWindow*>(mEngine->rootObjects().first());
        connect(window, SIGNAL(afterSynchronizing()), this, SLOT(doRun()));
    }

protected:
    virtual void execute() = 0;

private slots:
    void doRun() {
        QQuickWindow *window = qobject_cast<QQuickWindow*>(mEngine->rootObjects().first());
        disconnect(window, SIGNAL(afterSynchronizing()), this, SLOT(doRun()));

        execute();

        qDebug() << "setting running property to false";
        mRunning = false;
        emit runningChanged();
    }

private:
    QQmlApplicationEngine *mEngine;
    bool mRunning;
};

然后,子类只需要担心它们的逻辑:

class Task : public BlockingTask
{
    Q_OBJECT

public:
    Task(QQmlApplicationEngine *engine) :
        BlockingTask(engine) {
    }

protected:
    void execute() Q_DECL_OVERRIDE {
        qDebug() << "beginning long, blocking operation";
        QDateTime start = QDateTime::currentDateTime();
        while (start.secsTo(QDateTime::currentDateTime()) < 2) {
            // Wait...
        }
        qDebug() << "finished long, blocking operation";
    }
};