QML/C++ signal slots help
-
Hi, relative coding newbie here.
I'm using an app that combines QML and C++. I'm able to access a C++ object's functions and trigger them from a QML GUI, but I can't seem to use the Connections properly and get the onSignal process working correctly. Any advice would be greatly appreciated! I'm using Qt 5.15 on an embedded device.
I am trying to get a signal (threadInitializeMainSuccess) to trigger a change in a qml GUI. However, when I trigger my mouseArea, the onSignal :{} functions trigger immediately rather than waiting until the signal actually fires.
Here is the relevant portion of my main.cpp file:
//int main stuff: System m_systemHandler; //instantiate the system class, named m_systemHandler to be passed to QML Engine and other classes. There will only be 1 "master" systemHandler that will be altered. threadController m_threadHandler(&m_systemHandler); QQmlApplicationEngine engine; const QUrl url(QStringLiteral("qrc:/main.qml")); QQmlContext *context(engine.rootContext()); QQmlContext *context2(engine.rootContext()); context->setContextProperty("systemHandler", &m_systemHandler); context2->setContextProperty("threadHandler", &m_threadHandler); QObject::connect(&engine, &QQmlApplicationEngine::objectCreated, &app, [url](QObject *obj, const QUrl &objUrl) { if (!obj && url == objUrl) QCoreApplication::exit(-1); }, Qt::QueuedConnection); engine.load(url); return app.exec(); }
Here is the portion of my threadController.cpp file that sends the signal. This signal is tied to a motor - once it finishes initializing it sends a signal connected to this slot. If need be, I can include that file, but I didn't think it necessary since it is firing normally:
void threadController::startInitializeMainThread() { qDebug("main initialize Thread called"); QThread *mainInitializeQThread = new QThread(); //Pointer To QThread mainInitializeQThread->setObjectName("mainInitializeThread"); motorController *mainInitializeMotor = new motorController(*m_systemHandler); //Create motorController instance and feed systemHandler into it. mainInitializeMotor->moveToThread(mainInitializeQThread); QObject::connect(mainInitializeMotor, &motorController::destroyMotorThreadInitMain, mainInitializeMotor, &motorController::deleteLater); QObject::connect(mainInitializeMotor, &motorController::destroyMotorThreadInitMain, mainInitializeQThread, &QThread::quit); QObject::connect(mainInitializeMotor, &motorController::motorInitializeMainSuccess, this, &threadController::mainSignalNotifier); QObject::connect(mainInitializeQThread, &QThread::finished, mainInitializeQThread, &QThread::deleteLater); QObject::connect(this, &threadController::userRequestStartInitializeMain, mainInitializeMotor, &motorController::initializeMain); mainInitializeQThread->start(); } void threadController::mainSignalNotifier() { emit threadInitializeMainSuccess(); }
And here is my .qml file with the GUI:
import QtQuick 2.15 Item { id: qsInitializeRoot property bool clientInitDone: false property bool serverInitDone: false property bool mainInitDone: false property bool telescopeInitDone: false property bool rotateInitDone: false property bool rollInitDone: false Connections { target: threadHandler //When I uncomment the following code, I get an "invalid property name" error. //onClientInitDone { change clientInitDone to true.} //onServerInitDone { change serverInitDone to true.} //if (clientInitDone && serverInitDone) {Change stacker.source = homeview.qml} } function initializeWait() { onThreadInitializeMainSuccess: { displayer.color = "red" mainInitDone = true if (mainInitDone && telescopeInitDone) { clientInitDone = true } } onThreadInitializeTelescopeSuccess: { displayer.color = "blue" telescopeInitDone = true if (mainInitDone && telescopeInitDone) { clientInitDone = true } } onRollInitSuccess: { displayer.color = "green" rollInitDone = true if (rollInitDone && rotateInitDone) { serverInitDone = true } } onRotateInitSuccess: { displayer.color = "black" rotateInitDone = true if (rollInitDone && rotateInitDone) { serverInitDone = true } } if (clientInitDone && serverInitDone) { displayer.color = "purple" //stacker.source = "/ui/homeView.qml" } } Image { id: backgroundImage anchors.fill: parent source: "/images/initializeScreen" MouseArea { id: cursorHide anchors.fill: parent cursorShape: Qt.BlankCursor onClicked: { cursorHide.enabled = false backgroundImage.source = "/images/initializeScreen2" initializeWait() threadHandler.startInitializeThread() threadHandler.startInitializeSocket() } } Rectangle { id: displayer anchors { left: parent.left right: parent.width/2 bottom:parent.height/2 top: parent.top } // anchors.fill: parent height: 150 width: 150 Text { anchors.fill: parent font.pixelSize: 50 text: "telescopeInit: " + telescopeInitDone + "\n mainInitDone: " + mainInitDone + "\n clientInitDone: " + clientInitDone + "\n serverInitDone: " + serverInitDone + "\n rollInitDone: " + rollInitDone + "\n rotateInitDone: " + rotateInitDone } } } }
Any advice would be super helpful! Thanks.
-
I should also mention that the threadHandler.startInitializeThread() function in the .qml file calls the startMainInitializeThread() in the threadcontroller.cpp file successfully.
-
@dij0nmustard I think it would be a much simpler solution to move your onSignal functions directly inside the Connection of threadHandler, and if need be to change the enabled property of the connection when initializeWait() is triggered.
Also some tips:
- New syntax for defining slots on qml is function on<SignalName>(params){}
- If working with threads, recommendation is to use Qt::QueuedConnection type.
Final thing, i didn't try it out, but dynamically assigning connections from in qml looks something like this
button.clicked.connect(function)
Never tried it with C++ and QML, but maybe it would work. (Do not recommend this way)
-
@Marko-Stanke Hi! Thanks for your reply. When I try moving the onSignal() {} into the Connection of threadHandler, I get an error saying "invalid property name". However, when I move the onSignal outside of the Connection, the program doesn't give me an error. Do you know why it's doing that?
With regards to threads, I believe the .qml file and threadcontroller instance are running on the same main thread.
-
@dij0nmustard Not sure. Based on the code it looks fine.
But maybe just in case, instead of making context and context2, try setting the contextProperty directly from the engine.
If that doesn't work maybe you could try using
qmlRegisterSingletonInstance instead of using Context, it is generally the preferred way anyway. -
-
@Marko-Stanke Thanks for the assistance. For some reason on my computer, it didn't sense the exposed object in Qt Creator - but on my raspberry pi it did. Not sure what I did differently.