0
votes

I have built a small test example to better understand the QML/C++ bindings offered by Qt 5.

The QML side is basically a StackLayout with several Page that display informations on the console or in a Label. The user is able to navigate through these pages using a Button.

As long as everything runs in a single thread, it's fine. The QML/C++ bindings through QObject's signals and slots are working as expected.

But when i try to move the QObject exposed to the QML in another thread, i get this message and the application is killed :

QQmlEngine: Illegal attempt to connect to BackendWorker(0xbe8a7c28) that is in a different thread than the QML engine QQmlApplicationEngine(0xbe8a7c44.

Here is the main of the application :

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    BackendWorker backendWorker; // expose a signal and a slot only

    QQmlApplicationEngine engine;

    QQmlContext *ctx = engine.rootContext();

    ctx->setContextProperty("backendWorker", &backendWorker);

    QThread t1;
    backendWorker.moveToThread(&t1); // here is the offending part
    t1.start();

    engine.load(QUrl(QStringLiteral("qrc:/UI/main.qml")));

    if (engine.rootObjects().isEmpty())
        return -1;

    return app.exec();
}

Does all QObject's exposed to QML ( with properties, signals or slots or Q_INVOKABLE's) have to live in the same thread as the QGuiApplication ?

EDIT :

Here is a smaller example that shows a QStringListModel living in a separate thread than the QML ListView and it works, so how is this possible ?

// main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QStringListModel>
#include <QQmlContext>
#include <QTimer>
#include <QThread>

class Functor
{
public:
    Functor(QStringListModel *model) : m_model(model) { }
    void operator()() {
        QStringList list;
        list << "item 5" << "item 6" << "item 7" ;
        m_model->setStringList(list);
    }
private:
    QStringListModel *m_model;
};

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);

    QGuiApplication app(argc, argv);

    QStringListModel listModel;

    Functor functor(&listModel);

    QQmlApplicationEngine engine;

    QQmlContext *ctx = engine.rootContext();

    ctx->setContextProperty("listModel", &listModel);

    QThread t1;
    QStringList list;
    list << "item 1" << "item 2" << "item 3" << "item 4" ;
    listModel.setStringList(list);
    listModel.moveToThread(&t1);
    t1.start();

    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;

    QTimer::singleShot(5000, functor);

    return app.exec();
}

QML side :

// main.qml
import QtQuick 2.9
import QtQuick.Window 2.2

Window {
    visible: true

    ListView {
        width: 200
        height: 500
        anchors.centerIn: parent
        model: listModel
        delegate: Rectangle {
            height: 50
            width: 200
            Text {
                text : display
            }
        }
    }
}

Thank you.

1
What is your problem now? Have you used the @talamaki solution?eyllanesc
Please tell me what is not clear for you in my question so i can clarify it if neededFryz
In your update, you point out that the problem does not happen with the model, but I do not see anything interesting, it will not generate errors because you have not done anything in the new thread. And then you point out that it is wrong in BackendWorker but I do not see any progress, I do not see any comments either regarding the solution that you have been proposed.eyllanesc
The answer as they point out is that QML does not allow interacting with objects that are in another thread, if you want to interact you must create a proxy object that is connected by signals to the object that are in the other thread and resends that data to QML.eyllanesc
I have not proposed any solution in my edit. But now i am confused because the listView is updated when the listModel that lives in the other thread change.Fryz

1 Answers

1
votes

Connections across thread boundaries are supported by Qt in C++ level but not in QML level. It's probable QML engine won't support them in a near future (if ever). See the bug report It's not possible to connect two QML objects living in different threads which was closed as out of scope.

Below, you can see a simplified example to implement a worker and register it to QML engine as a context property:

class BackendWorker : public QObject {
    Q_OBJECT
  public slots:
    void operate() {
      int i = 0;
      while (i < 1000) {
        report(i++);
        std::this_thread::sleep_for(std::chrono::milliseconds(100));
      }
    }
  signals:
    void report(int x);
};

class BackendController : public QObject {
    Q_OBJECT
  public:
    BackendController() {
      w.moveToThread(&t);
      connect(this, &BackendController::start, &w, &BackendWorker::operate);
      connect(&w, &BackendWorker::report, this, &BackendController::report);
      t.start();
    }
  signals:
    void start();
    void report(int x);
  private:
    BackendWorker w;
    QThread t;
};

int main(int argc, char *argv[])
{
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
    BackendController c;
    engine.rootContext()->setContextProperty("BackendController", &c);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;
    return app.exec();
}