2016-02-06 44 views
-1

单击按钮时,我想执行长时间的任务。我想要这个任务来阻止UI,因为应用程序在任务完成之前无法运行。但是,我想向用户指出发生了什么,因此我有一个BusyIndicator(在渲染线程上运行),并且在操作开始之前设置为显示。但是,它从不呈现。为什么?Qt Quick中的阻止操作

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 
+0

我只是不明白为什么你坚持锁定主线程?只需使用工作线程并在执行时将事件阻塞到UI。大多数操作系统会给你一个“应用程序没有响应”,如果你阻塞主线程,通常会要求用户终止或立即终止。 – dtech

+0

我正在使用GUI线程,因为我不需要切换到单独的线程。我没有遇到过“应用程序没有响应”的消息。 – Mitch

+0

除非它是涉及UI元素的操作,否则您绝对可以通过工作线程异步执行它。经验法则是每个需要超过25毫秒的操作应该以这种方式处理。你不会得到“没有回应”,因为你知道发生了什么事情,并且当操作系统未能传递给应用程序事件循环的输入事件发生时,你会远离接触应用程序,你会得到它。绝对不是你想要传递给客户的东西。 – dtech

回答

0

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

当控制权返回到接收方线程的事件循环时,将调用此插槽。

Another solution这听起来可能看好的是QTimer::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"; 
    } 
}; 
1

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

+0

[BusyIndi​​cator](http://code.qt.io/cgit/qt/qtquickcontrols2.git/tree/src/imports/controls/qquickbusyindicatorring.cpp)实现了一个'Animator'(这就是我的意思,当我说“在渲染线程上运行“),所以这不是问题。我不确定我是否同意“更简单”。你能改变我提供的例子来证明你的意思吗? – Mitch