Unsolved QML <- c++ sending variables
-
I've been interested in, and working on, analyzing data from log files that come from data written out of a CANBus network. I've been able to write a couple programs that load, parse and graph the data from the log files. Now I want to keep extending my learning by now trying to look at the live data flowing over the bus. I've been working with the CANBus example that comes with a recent version of QT Creator, and I was able to get that working without too much trouble. I've been trying to extend it to allow me to see specific data that I'm interested in, and have had some success. I can look for a specific PGN on the bus and write out to the qDebug console the decoded messages. Here is the code I added to the examples' "processReceivedFrames" function:
void MainWindow::processReceivedFrames() { if (!m_canDevice) return; while (m_canDevice->framesAvailable()) { const QCanBusFrame frame = m_canDevice->readFrame(); QString view; if (frame.frameType() == QCanBusFrame::ErrorFrame) view = m_canDevice->interpretErrorFrame(frame); else view = frame.toString(); //qDebug() << view; QTextStream canStream(&view); QString line; while (canStream.readLineInto(&line)) { const QStringList canParts = line.split(' ',QString::KeepEmptyParts); CanId = canParts.at(0); DB0 = canParts.at(5); DB1 = canParts.at(6); } const unsigned int bitmask = 0x01FFFF; int canArray = CanId.toInt(&ok,16); //qDebug()<<canArray; canArray = canArray>>8; //qDebug()<<canArray; PgnNum = canArray & bitmask; //qDebug() << PgnNum << " " << D0; PgnDoSomething(PgnNum, DB0, DB1); const QString time = QString::fromLatin1("%1.%2 ") .arg(frame.timeStamp().seconds(), 10, 10, QLatin1Char(' ')) .arg(frame.timeStamp().microSeconds() / 100, 4, 10, QLatin1Char('0')); const QString flags = frameFlags(frame); m_ui->receivedMessagesEdit->append(time + flags + view); } }
and here is my function 'pgnDoSomething' (it's a mess, I know):
int MainWindow::PgnDoSomething(int mPgnNum, QString mD0, QString mD1) { QVariant hMessage; //qDebug() << mPgnNum << " " << mD0; if (mPgnNum = heading) { QString m_vHeading = mD0 + mD1; double computedVHeading = m_vHeading.toInt(&ok, 16); hMessage = computedVHeading; qDebug() << "computed vesselHeading " << computedVHeading; return computedVHeading; } }
I'm trying to load a QML window with a dial and set the value of the dial with the computed value from the pgnDoSomething function, and I'm making no progress on getting it to work. My latest error is "QQmlComponent: Component is not ready"
At this point I'm just trying to send a value over to the QML window and have it show up. Here is my 'compass.qml':import QtQuick 2.2 import QtQuick.Window 2.3 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Extras 1.4 Window { Item { Gauge { id: hGauge objectName: "hdGauge" minimumValue: 0 value: vesselHeading maximumValue: 360 anchors.centerIn: parent function myHeadingFunction(hMessage) { console.log("got message", hMessage) } } } }
and my main.cpp:
#include "mainwindow.h" #include <QApplication> #include <QtQml/QQmlApplicationEngine> #include <QtQml/QQmlComponent> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); QQmlEngine engine; QQmlComponent component(&engine, QUrl::fromLocalFile("qrc:/compass.qml")); QObject *object = component.create(); object->setProperty("value", "100"); return a.exec(); }
If things look pretty messy, it's because I've spent hours (over days) trying to modify things to get it to work. I've read over numerous QT documentation pages here, for example:
Interacting with QML Objects from C++
and here:
Using QML Bindings in C++ Applications
and many other pages and examples, but I just can't seem to get anything to work - I just don't quite understand it.
I would appreciate any advice, and/or (working) examples, or tips to get me on the right track
Thanks!
-
Hi there.
When
QUrl::fromLocalFile
tries to resolve the url, this fuction return the wrong path to file.>> Debug log
Documents/build-testProject-Desktop_Qt_5_6_2_MinGW_32bit-Debug/qrc:/main.qml:-1 File not found\n
To fix this problem:
main.cpp
... int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); QQmlEngine engine; // QQmlComponent component(&engine, QUrl::fromLocalFile("qrc:/compass.qml")); QQmlComponent component(&engine, QUrl(QStringLiteral("qrc:/main.qml"))); QObject *object = component.create(); object->setProperty("value", "100"); return a.exec(); } ...
But when the componet is created, the function
component.create()
returns the main father of components (Window), but i noticed you are trying to set value property of a child of Window, this lineobject->setProperty("value", "100")
will try to find the value property in Window element.But if you want to get the child with objectName hdGauge and set the property value, you can use this code:
... //main function QObject *object = component.create(); // create the object QObject *child = object->findChild<QObject*>("hdGauge"); // find child with objectName hdGauge child->setProperty("value", 100); // set integer value ...
Or, you can use an alias property to expose the value to father.
main.qml
... Window { property alias gaugeValue: hGauge.value Item { Gauge { id: hGauge objectName: "hdGauge" minimumValue: 0 value: vesselHeading maximumValue: 360 anchors.centerIn: parent function myHeadingFunction(hMessage) { console.log("got message", hMessage) } } } }
main.cpp
... QObject *object = component.create(); // create the object child->setProperty("gaugeValue", 100); // set integer value using alias to hGauge.value ...
I hope I have helped you, good coding.
>> References
-
Hello @KillerSmath ,
Thank you for taking the time to answer! Your answers were clear and you helped me get back on track.
I'm able to get the program to display a circular gauge now, but I'm struggling with where to call certain functions so that only the value that needs to be updated gets passed to the gauge. I put the lines
QQmlEngine engine; QQmlComponent component(&engine, QUrl("qrc:/compass.qml")); object = component.create(); // create the object
in the MainWindow setup section and defined 'object' in the mainwindow.h file in the Public section.
I then added the line:
object->setProperty("gaugeValue", hMessage); // set integer value using alias to hGauge.value
to the function like so:
int MainWindow::PgnDoSomething(int mPgnNum, QString mD0, QString mD1) { if (mPgnNum = heading) { QString m_vHeading = mD0 + mD1; hMessage.setValue((m_vHeading.toInt(&ok, 16)*.0001)*57.296); object->setProperty("gaugeValue", hMessage); // set integer value using alias to hGauge.value //qDebug() << "computed vesselHeading " << hMessage; return hMessage.toInt(); } }
Now the window with the gauge appears, but the whole program crashes as soon as it starts receiving CAN frames (or at least when I start the CAN messages on the bus).
I could get it to barely work (needle moving) when I instantiated the object inside the PgnDoSomething function but it was creating a new one every time the function was called...not good!
Thanks again,
-Scott
-
okay, i have some questions:
- Why do you need to send a object to qml ?
- If you need to send a object to qml, did you try to set this object in context of qml engine?
I suggest you to read about C++ Object and Qml interation
- http://doc.qt.io/qt-5/qtqml-cppintegration-overview.html#choosing-the-correct-integration-method-between-c-and-qml
- http://doc.qt.io/qt-5/qtqml-cppintegration-contextproperties.html
- http://doc.qt.io/qt-5/qtqml-cppintegration-exposecppattributes.html
>> Below is an example of how you can create an object to directly interaction with qml
message.h
#ifndef MESSAGE_H #define MESSAGE_H #include <QObject> class Message : public QObject { Q_OBJECT Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged) public: Message(); int value(); void setValue(int value); Q_INVOKABLE void cppFunction(); signals: void valueChanged(int value); private: int m_value; }; #endif // MESSAGE_H
message.cpp
#include "message.h" #include <qDebug> Message::Message() { m_value = 0; } int Message::value() { return m_value; } void Message::setValue(int value) { if(m_value != value){ m_value = value; emit valueChanged(m_value); } } void Message::cppFunction() { qDebug() << "I has been called from Cpp"; }
main.cpp
#include <QQmlContext> #include <QTimer> ... Message hMessage; QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("hMessage", &hMessage); QTimer::singleShot(5000, [&hMessage](){hMessage.setValue(50);}); // set the Value after 5 seconds ...
compass.qml
Window { Gauge { id: hGauge objectName: "hdGauge" minimumValue: 0 maximumValue: 360 value: hMessage.value anchors.centerIn: parent } Connections { target: hMessage onValueChanged: console.log("The value has changed to: " + value) } Component.onCompleted: hMessage.cppFunction() }
Observation: the c++ object need to be setted on context before any qml file is loaded or the qml file will not find the c++ object.
-
@KillerSmath - thank you again for taking time to respond and help noobies like me! To answer your question "Why do you need to send a object to qml ?" - answer, so I can understand how. I like the apparent ease of creating instrument layouts in QML, but with Qt, have only ever coded in c++, so I'm trying to learn a new skill!
Okay, I've spent the last week trying to adapt the example above to my code and I finally got it to compile, but my QML gauge still isn't updating with the incoming CAN messages. If I uncomment the QTimer though, it WILL update the gauge. Any thoughts? Here is my relevant code so far:
main.cpp
#include "mainwindow.h" #include "message.h" #include <QApplication> #include <QtQml/QQmlApplicationEngine> #include <QtQml/QQmlComponent> //#include <QQuickView> //#include <QQuickItem> #include <QObject> #include <QQmlContext> #include <QTimer> int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); Message hMessage; QQmlApplicationEngine engine; QQmlComponent component(&engine, QUrl("qrc:/compass.qml")); //added 6-14 engine.rootContext()->setContextProperty("hMessage", &hMessage); QObject *object = component.create(); //added 6-14 //QTimer::singleShot(5000, [&hMessage](){hMessage.setValue(50);}); // set the Value after 5 seconds return a.exec(); }
my function pgnDoSomething() in MainWindow.cpp, which takes in the messages and in the future I want it to send parsed data to different gauges/text displays:
void MainWindow::PgnDoSomething(int mPgnNum, QString mD0, QString mD1, QString mD2) { Message hMessage; if ((mPgnNum == heading)) { QString m_vHeading = mD1 + mD0; int msg_vHeading = (m_vHeading.toInt(&ok, 16)*.0001)*57.296; //hMessage.setValue(msg_vHeading); hMessage.setValue(msg_vHeading); qDebug() << "sent value: " << msg_vHeading; } }
message.h:
#ifndef MESSAGE_H #define MESSAGE_H #include <QObject> class Message : public QObject { Q_OBJECT Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged) public: Message(); int value(); Q_INVOKABLE void setValue(int); signals: void valueChanged(int value); private: int m_value; }; #endif // MESSAGE_H
message.cpp is unchanged from above.
Compass.qml:import QtQuick 2.2 import QtQuick.Window 2.3 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Extras 1.4 Window { //flags: Qt.WindowStaysOnTopHint visible: true width: 320 height: 320 // property alias gaugeValue: hGauge.value Item { anchors.centerIn: parent CircularGauge { id: hGauge objectName: "hdGauge" minimumValue: 0 value: hMessage.value maximumValue: 360 anchors.centerIn: parent function myHeadingFunction(gaugeValue) { console.log("got message", hMessage) } } } Connections { target: hMessage onValueChanged: console.log("The value has changed to: " + value) } //Component.onCompleted: hMessage.setValue(value) }
My guess is that my function (pgnDoSomething) isn't interacting with the Message class correctly??
-
@MScottM
Hi Again.I noticed you are efforting so much to know new features about qt and i apreciate that :)
Signals and Slots are good mechanims when you need to create a communication among differente parts of your code.
Your code emite a signal and this signal call a function (slot)
Signal Slot Documentation <- Read More
You will see many examples about this feature in Qt examples.
Below is an github repository where i uploaded a good example about your implementation.
Good study and good coding :)
-
I will download and study it this weekend. Thanks!
-
Okay, so I mostly get how this example is working. My problem is how to connect MY function, that is updating a number whenever a message passes the if() statement, to 'sendMessage', instead of having to use a button? I've been reading up on signals and slots, but I just don't quite get it yet!
-
@MScottM You could create a signal at MainWindow and connect this signal to slot of Message Class.
PseudoCode
sendData(*/ Your Data */) -> receiveData(/* Your Data */);
So, you implement the receiveData slot to set the data that you need.