2016-02-06 3 views
-1

Я хочу выполнить длинную задачу при нажатии кнопки. Я хочу, чтобы эта задача блокировала пользовательский интерфейс, потому что приложение не может работать до тех пор, пока задача не будет выполнена. Тем не менее, я хочу указать пользователю, что что-то происходит, поэтому у меня есть 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

Я просто не понимаю, почему вы настаиваете на блокировке основной нити? Просто используйте рабочий поток и блокируйте события в пользовательском интерфейсе, когда он выполняется. Большинство ОС даст вам «приложение не отвечает», если вы блокируете основной поток, обычно просите пользователя немедленно прекратить или прекратить действие. – dtech

+0

Я использую поток GUI, потому что мне еще не нужно переключаться на отдельный поток. Я не слышал сообщение «приложение не отвечающее». – Mitch

+0

Если это не оп, который включает элементы пользовательского интерфейса, вы можете и определенно должны сделать это асинхронно через рабочий поток. Правило большого пальца - это любая операция, которая занимает дольше 25 мс. Вы не получаете «не отвечаете», потому что знаете, что происходит, и вы не можете касаться приложения, как только произойдет событие ввода, которое ОС не сможет передать в цикл событий приложения, вы получите его. Определенно не то, что вы хотели бы передать клиенту. – dtech

ответ

0

Вызов функции с Qt::QueuedConnection не гарантирует, что у BusyIndicator появилась возможность начать анимацию. Это просто guarantees that:

Слот вызывается, когда управление возвращается в цикл событий потока получателя.

Another solution, что может показаться перспективным QTimer::singleShot():

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

В качестве специального случае QTimer с тайм-аут 0 будет тайм-аут, как только все события в очереди событий оконной системы были обработаны. Это может быть использовано для выполнения тяжелых работ при обеспечении быстрого пользовательского интерфейса [...]

Однако это также не сработает. Я не знаю, почему. Может быть, внутренне рендеринг/анимация не выполняется посредством очереди вызова, поэтому таймаут происходит слишком рано.

Вы можете указать произвольное количество времени, чтобы ждать:

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

Это будет работать, но это не так хорошо; это просто догадки.

Что вам нужно, это знать, когда графа сцены начала анимацию.

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 зависит от свойств, управляемых в основном потоке и, таким образом, блокируется при блокировке основного потока пользовательского интерфейса. Посмотрите на http://doc.qt.io/qt-5/qml-qtquick-animator.html для анимаций, которые могут запускаться при блокировке основного потока. Если возможно, я бы переместил операцию в другой поток, хотя это намного проще, а также позволяет, например, отмена операции из пользовательского интерфейса.

+0

[BusyIndicator] (http://code.qt.io/cgit/qt/qtquickcontrols2.git/tree/src/imports/controls/qquickbusyindicatorring.cpp) реализует «Аниматор» (вот что я имел в виду, когда сказал: работает на поток рендеринга "), так что это не проблема. Я не уверен, что согласен с «намного проще». Можете ли вы изменить пример, который я представил, чтобы продемонстрировать, что вы имеете в виду? – Mitch

Смежные вопросы