2

I have some issues changing the text of a QML window in Qt. I have a C++ file that calls a thread and from there I'm trying to change the value of the text label. The thread is running correctly but the text value from the QML is not changing. Below is part of my code:

main.cpp:

int main(int argc, char *argv[]) {
    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:///template.qml")));
    QQuickItem *label     = engine.rootObjects().at(0)->findChild<QQuickItem*>("myLabel");
    thread2 thread(label);
    thread.start();
}

Thread.cpp:

thread2::thread2(QQuickItem *label) {
    this->label = label;
}

void thread2::run() {

    int test = 0;
    char buffer[10];
    int speed = 100;

    while(1) {
        speed++;
        sprintf(buffer,"%d km/h",speed);          
        this->label->setProperty("text", QString(buffer));
        QThread::msleep(1000);
        qDebug()<<"tic: "<<buffer<<endl;           
    }

template.qml:

Window {
    id: window
    visible: true
    width: 360
    height: 360

    Component {
        id: fruitDelegate
        Row {
            spacing: 10
            Text { text: name }
            Text { text: '$' + cost }
        }
    }
    Text {
        width: 99
        height: 19
        text: qsTr("Speed: ")
        anchors.verticalCenterOffset: 1
        anchors.horizontalCenterOffset: 0
        anchors.centerIn: parent
        objectName: "lab"
    }
    Text {
        width: 38
        height: 19
        text: qsTr(" 0 ")
        anchors.verticalCenterOffset: 1
        anchors.horizontalCenterOffset: 46
        anchors.centerIn: parent
        objectName: "myLabel"
    }
}

Can anyone tell me why is not working? Where is my mistake?

Thanks!

TrebledJ
  • 8,713
  • 7
  • 26
  • 48
  • @TrebledJ To start the thead you must call the start method and it will invoke the run method on the appropriate thread – eyllanesc Apr 02 '19 at 18:59
  • By running thread.start() the method run() is called. Yes it works. I know that because the debug message from the while() loop is printed out. So the method run is called. Also, I'l upload the .h file you asked me for. – Bozhidar Ivaylov Peychev Apr 02 '19 at 20:37

3 Answers3

4

You have 2 errors:

  • You should not update the GUI from another thread, the run method is executed in another thread so Qt does not guarantee that it works correctly.
  • Do not export an element from QML to C++ because it brings several problems since it is many times impossible to obtain the object through the objectname, another inconvenient is that the life cycle of the Item is determined by QML so at a given moment it could be eliminated so that label could point to an unreserved memory making its use, etc. Instead it exports the C++ object to QML.

Considering the above the solution is:

thread.h

#ifndef THREAD_H
#define THREAD_H

#include <QThread>

class Thread : public QThread
{
    Q_OBJECT
public:
    Thread(QObject *parent=nullptr);
   ~Thread() override;
    Q_SLOT void stop();
    Q_SIGNAL void textChanged(const QString & text);
protected:
    void run() override;
};

#endif // THREAD_H

thread.cpp

#include "thread.h"
#include <QDebug>

Thread::Thread(QObject *parent):
    QThread(parent)
{   
}

Thread::~Thread()
{
}

void Thread::stop()
{
    requestInterruption();
    wait();
}

void Thread::run()
{
    int speed = 100;
    QString text;
    while(!isInterruptionRequested()) {
        speed++;
        text = QString("%1 km/h").arg(speed);
        Q_EMIT textChanged(text);
        QThread::msleep(1000);
        qDebug()<<"tic: "<< text;
    }
}

main.cpp

#include "thread.h"

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>

int main(int argc, char *argv[])
{
    QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
    QGuiApplication app(argc, argv);
    Thread thread;
    QObject::connect(&app, &QGuiApplication::aboutToQuit, &thread, &Thread::stop);
    thread.start();
    QQmlApplicationEngine engine;
    engine.rootContext()->setContextProperty("thread", &thread);
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    if (engine.rootObjects().isEmpty())
        return -1;
    return app.exec();
}

main.qml

// ...

Text {
    id: txt
    width: 38
    height: 19
    text: qsTr(" 0 ")
    anchors.verticalCenterOffset: 1
    anchors.horizontalCenterOffset: 46
    anchors.centerIn: parent
    objectName: "myLabel"
}
Connections{
    target: thread
    onTextChanged: txt.text = text
}

// ...

For more information read:

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
1

You shouldn't modify the UI from another thread. Use signal/slot instead. You should not create a child class from QThread, also (create a worker and move it in another thread).

Item {
    id: rooItem
    visible: true
    anchors.fill: parent

    signal change(string s);
    onChange: foobar.text = s
    Text {
        id: foobar
        text: "Empty"
    }
}

class Worker: public QObject
{
    Q_OBJECT
public slots:
    void run()
    {
        while(1) {
            QThread::msleep(1000);
            emit changed(QDateTime::currentDateTime().toString());
        }
    }
signals:
    void changed(QString);
};

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

    QThread* th = new QThread();
    Worker* worker = new Worker();
    worker->moveToThread(th);
    QObject::connect(th, &QThread::started, worker, &Worker::run);
    th->start();

    QQuickView view(QStringLiteral("qrc:/Main.qml"));

    QObject* o = view.rootObject();
    QObject::connect(worker, SIGNAL(changed(QString)), o, SIGNAL(change(QString)));
    view.showMaximized();

    return app.exec();
}
Dimitry Ernot
  • 6,256
  • 2
  • 25
  • 37
0

You are using the wrong mechanism to update a qml property, take a look at QQmlProperty for the right way. You could also export a QObject instance into the qml engine and bind the labels text property to some property of that object. Always keep in mind that qml/qt quick are essentially hacks. There is a way to update the gui safely from a non-gui thread without using signals. instead you can use events to do the work.

user1095108
  • 14,119
  • 9
  • 58
  • 116