简体   繁体   中英

Update QML text from C++

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!

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:

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();
}

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM