Qt 5.1 QML property through Threads
-
For the purpose of the resolution, I've created a TestApp that repeat the same problem that I have.
I'm porting my software from Qt 4.8 to Qt 5.1.
My first program was multithreaded, and was working smoothly with QML, provided that the classes were thread safe. But now I get this message :
@QObject::connect: No such slot TestApp::run() in ..\ThreadingTest\main.cpp:21
QQmlEngine: Illegal attempt to connect to TestApp(0x29cfb8) that is in a different thread than the QML engine QQmlEngine(0x2f3e0f8).@This is the code that reproduce the error :
@#include <QtGui/QGuiApplication>
#include <QQmlContext>
#include <QThread>
#include "qtquick2applicationviewer.h"
#include "testapp.h"int main(int argc, char *argv[])
{
int out;QGuiApplication app(argc, argv); QtQuick2ApplicationViewer viewer; TestApp * testapp = new TestApp(); QThread * testappThread; testappThread = new QThread(); QObject::connect(testappThread, SIGNAL(started()), testapp, SLOT(run())); testapp->moveToThread(testappThread); testappThread->start(); viewer.rootContext()->setContextProperty("TestApp", testapp); viewer.setMainQmlFile(QStringLiteral("qml/ThreadingTest/main.qml")); viewer.showExpanded(); out = app.exec(); testappThread->quit(); testappThread->wait(); delete testapp; delete testappThread; return out;
}@
@#ifndef TESTAPP_H
#define TESTAPP_H#include <QObject>
#include <QString>
#include <QTimer>
#include <QReadWriteLock>#define HELLOWORLD "Hello World !"
extern QReadWriteLock HelloWorldLock;
class TestApp : public QObject
{
Q_OBJECTQ_PROPERTY(QString HelloWorld READ getHelloWorld WRITE setHelloWorld NOTIFY HelloWorldChanged)
public:
explicit TestApp(QObject *parent = 0);virtual ~TestApp(); QString getHelloWorld(); void setHelloWorld(QString);
public slots:
void run(); void toggleHelloWorld();
signals:
void HelloWorldChanged();
private:
QString m_HelloWorld; QTimer * m_Timer;
};
#endif // TESTAPP_H
@@#include "testapp.h"
QReadWriteLock HelloWorldLock(QReadWriteLock::Recursive);
TestApp::TestApp(QObject *parent) :
QObject(parent)
{
HelloWorldLock.lockForWrite();
m_HelloWorld = HELLOWORLD;
HelloWorldLock.unlock();m_Timer = new QTimer(this); connect(m_Timer, SIGNAL(timeout()), this, SLOT(toggleHelloWorld()));
}
TestApp::~TestApp() {
m_Timer->stop();delete m_Timer;
}
QString TestApp::getHelloWorld() {
HelloWorldLock.lockForRead();
QString out = m_HelloWorld;
HelloWorldLock.unlock();return out;
}
void TestApp::setHelloWorld(QString text) {
HelloWorldLock.lockForWrite();
m_HelloWorld = text;
HelloWorldLock.unlock();emit HelloWorldChanged();
}
void TestApp::run() {
m_Timer->start(1000);
}void TestApp::toggleHelloWorld() {
HelloWorldLock.lockForWrite();
if(m_HelloWorld == "") {
m_HelloWorld = HELLOWORLD;
}
else {
m_HelloWorld = "";
}
HelloWorldLock.unlock();emit HelloWorldChanged();
}
@@import QtQuick 2.0
Rectangle {
width: 360
height: 360
Text {
text: TestApp.HelloWorld
anchors.centerIn: parent
}
MouseArea {
anchors.fill: parent
onClicked: {
Qt.quit();
}
}
}
@My program is quite complex (a lot of properties and classes to share with the interface) and I wouldn't like to have to create an interface class just to connect my properties... Do you have any suggestions to cope with this issue ?
-
Really, no answers ? This should be a trending question, it's a common problem for QML users who wants to multi-thread their apps...
-
I am having the same problem! I have been trying to figure out a way around this for days. Did you ever end up solving your problem?
-
I know this doesn't answer your question just as it didn't answer mine but it convinced me to rewrite all my code without using threads. See the bottom of the article: http://qt-project.org/wiki/Threads_Events_QObjects
By the way if you did end up coming up with a solution I would be interested to see what you did
-
I came up with a solution that worked really well for me. For my first thread I was pulling GPIO for a custom mini keyboard and I changed it to use a timer to check the GPIO every 40ms and it runs on the "main thread" (It actually looks like Qt automatically creates a separate thread for me so I don't have to do it, that is why I put main thread in quotes).
The second thread I had was for CAN communication and it uses SocketCAN, which Qt does not have a built in class I can use. For this I required an actual thread, a timer would not do. The way I solved the problem was to separate my class into two classes. The first class with a for(;;) loop that reads and writes from the SocketCAN and runs in it's own thread. The second class is my data class and it runs on the main thread and is connected through a signal/slot with a Qt::QueuedConnection connection type. The magic is in this line of code right here...
@
QObject::connect(canComm, SIGNAL(canDataRecieved(struct can_frame)), &canData, SLOT(canDataRecieved(struct can_frame)), Qt::BlockingQueuedConnection);
@So for your app you could create a second class called HelloWorldData and put your Q_PROPERTY(QString HelloWorld READ getHelloWorld WRITE setHelloWorld NOTIFY HelloWorldChanged) in there. Then to update it create a slot in HelloWorldData and a signal in HelloWorld that could send the data via the connection you create in main.cpp. You can then update your qml file to bind to the property in the data class and as long as you created the class in main.cpp everything should work. Hope this helps