2016-02-06 45 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