Solved Need QML advice
-
@sierdzio
First - thank you for your advice so far.I'm having a hard time getting it working right now, but with what you've given me above, I know I'll figure it out before too long.
I wanted to ask about best practice. Sending a signal up and down a chain of parented items seems...inelegant, but in the documentation about interacting between C++ and QML is this statement:
"Warning: Although it is possible to access QML objects from C++ and manipulate them, it is not the recommended approach, except for testing and prototyping purposes. One of the strengths of QML and C++ integration is the ability to implement UIs in QML separate from the C++ logic and dataset backend, and this fails if the C++ side starts manipulating QML directly. Such an approach also makes changing the QML UI difficult without affecting its C++ counterpart."
So maybe the first method is considered the best way?
-
The second way I described is not about accessing QML objects from C++, it is about sending a signal from QML to C++, and then connecting to the re-emitted signal in another place. There is no tight integration here and no mixing of logic with UI. At least in my view.
-
Okay - still having a hard time (trying to learn!). All of the examples show declaring a QQmlEngine and creating a component
QQmlEngine engine; QQmlComponent component(&engine, "MyItem.qml"); QObject *object = component.create();
which I've already done in my main.cpp - or they create a QGuiApplication
int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); QQuickView view(QUrl::fromLocalFile("MyItem.qml")); QObject *item = view.rootObject(); MyClass myClass; QObject::connect(item, SIGNAL(qmlSignal(QString)), &myClass, SLOT(cppSlot(QString)));
Is it possible to create a class that is aware of the signals without having to create views or add to the main.cpp file?
-
@MScottM said in Need QML advice:
Is it possible to create a class that is aware of the signals without having to create views or add to the main.cpp file?
Yes. Consider this:
// someclass.h class SomeClass : public QObject { Q_OBJECT // blah blah blah... public signals: void someCppSignal() const; public slots: void someCppSlot(); }; // main.cpp SomeClass someclass; QQmlApplicationEngine engine; engine.rootContext()->setContextProperty("SomeClass", &someclass); // QML code: Item { id: sender signal valueOutOfRange() onValueOutOfRange: { // If you want to call a slot in C++ // Let's assume that someCppSlot() emitts someCppSignal() SomeClass.someCppSlot() } } // Other QML file ("receiver") Item { id: receiver Connections { target: SomeClass onSomeCppSignal: console.log("Oh look! A signal from C++!") } }
Does that help? I can explain more if necessary.
-
Thank you! I'm pretty sure I understand how your example is working. I'll spend this weekend seeing if I can implement it in my code.
-
@sierdzio
Okay...my goal is to pass two pieces of information if a value goes out of range - the name of the gauge and its value. Here the code that I've tried so far.
In the QML 'sender' (snipped to keep it short):CircularGauge { id: Temp objectName: Temp value: valueSource.coolantTemp property real normalRangeHi: 200 property real normalRangeLo: 20 signal valueOutOfRange(var messageObject) onValueChanged: { if (value > normalRangeHi) { valueOutOfRange("Coolant Temp", Temp.value) } else if (value < normalRangeLo) { valueOutOfRange("Coolant Temp", Temp.value) } onValueOutOfRange: MessageRelay.setValueMsg(valueOutOfRange(messageObject)) } }
and my MessageRelay.h file:
#ifndef MESSAGERELAY_H #define MESSAGERELAY_H #include <QObject> #include <QQmlProperty> #include <QVariant> #include <QDebug> class MessageRelay : public QObject { Q_OBJECT Q_PROPERTY(QVariant valueMsg READ valueMsg WRITE setValueMsg NOTIFY valueMsgChanged) private: QVariant m_messageToRelay; public: QVariant valueMsg() const { return m_messageToRelay; } signals: void valueMsgChanged(const QVariant relayMessage); public slots: void setValueMsg(QVariant relayMessage){ m_messageToRelay = relayMessage; emit valueMsgChanged(m_messageToRelay); } }; #endif // MESSAGERELAY_H
I can't get past this error from moc:
error: no match for call to '(QVariant) ()'
case 0: reinterpret_cast< QVariant>(_v) = _t->valueMsg(); break;I haven't put any code in the 'receiver' yet.
-
Okay - got a little further (edited above code to what I have now). My QML file with the gauge is saying that "Reference error: MessageRelay is not defined" and then "Reference error: messageObject is not defined".
Is that the purpose of the 'setContextProperty' statement in main.cpp? Or, I am probably not bundling the variables correctly into the messageObject - looking at that now.
-
@MScottM said in Need QML advice:
void setValueMsg(QVariant relayMessage){
Qt classes, especially implicitly shared ones, are best passed by const reference
const QVariant &relayMessage
.id: Temp
objectName: TempNot sure if using a capital letter at the start of an id is a good idea. If it works - fine. But the convention is that capital letters are for QML components only.
Object name should be a string.
Now, the actual reply:
MessageRelay.setValueMsg(valueOutOfRange(messageObject))
Here you are calling a method on
MessageRelay
so there is no need to usevalueOutOfRange
here.Also, this code does not belong inside
onValueChanged
because it is inside another slot. Here is something that should work:onValueChanged: { if (value > normalRangeHi) { valueOutOfRange("Coolant Temp", Temp.value) } else if (value < normalRangeLo) { valueOutOfRange("Coolant Temp", Temp.value) } } onValueOutOfRange: MessageRelay.setValueMsg(messageObject)
Note: since you declared
valueMsg
as a Q_PROPERTY, the last line can also be:onValueOutOfRange: MessageRelay.valueMsg = messageObject
There might be one more problem ahead: you declare
valueOutOfRange
signal with one argument (messageObject
) but then you pass two inonValueChanged
. That is likely going to fail, but I may be wrong. -
@sierdzio
Okay - here is updated code from my gauge:CircularGauge { //portCoolantTemp id: temp property real normalRangeHi: 200 property real normalRangeLo: 20 signal valueOutOfRange(var msgGauge, var msgValue) onValueChanged: { if (value > normalRangeHi) { valueOutOfRange("Coolant Temp", temp.value) } else if (value < normalRangeLo) { valueOutOfRange("Coolant Temp", temp.value) } } onValueOutOfRange: MessageRelay.setValueMsg(msgGauge, msgValue)
and the change to the MessageRelay.h code:
void setValueMsg(const QVariant &relayMessage){ m_messageToRelay = relayMessage; emit valueMsgChanged(m_messageToRelay); qDebug()<<m_messageToRelay; } }; #endif // MESSAGERELAY_H
my qDebug is in the slot is printing 'QVariant(invalid)', so you are probably right that I need to separate the values to pass them - trying to figure that out now. This is progress to me! Thanks!
-
OK, good progress. Now you need to decide on the nature of your MessageRelay class:
- if you want to keep valueMsg property, you need it to have only one argument (a property can only have one value). Here what you can do is create a JavaScript array and use it in your valueOutOfRange signal - on C++ side that will be translated to QVariantList
- if you don't need the valueMsg property, you can remove it and use the setValueMsg slot (you'll need to declare it as slot or Q_INVOKABLE). Or, if you really want to only relay the messages without remembering them, you should be able to emit the valueMsgChanged signal directly from QML, like this:
onValueOutOfRange: MessageRelay.valueMsgChanged(messageObject)
-
@sierdzio
Okay!! I have messages passing from QML to C++ now!Here is a snip from MessageRelay.h:
public: qint16 m_valueToRelay = 0; QString m_nameToRelay = ""; signals: void valueMsgChanged(const qint16 relayValue); void valueNameChanged(const QString relayName); public slots: Q_INVOKABLE void setValueMsg(const qint16 &relayValue){ if (m_valueToRelay != relayValue) { m_valueToRelay = relayValue; emit valueMsgChanged(relayValue); qDebug() << "relayMessage: " << relayValue; } } Q_INVOKABLE void setNameMessage(const QString &relayName){ if (m_nameToRelay != relayName){ m_nameToRelay = relayName; emit valueNameChanged(relayName); qDebug() << "relayName: " << relayName; } } };
And here is the QML code from the CircularGauge:
CircularGauge { //CoolantTemp id: temp // SIGNALING property real normalRangeHi: 200 property real normalRangeLo: 20 signal valueOutOfRange(var messageValue) signal nameOutOfRange(var messageName) onValueChanged: { if (temp.value > normalRangeHi) { valueOutOfRange(temp.value) nameOutOfRange("CoolantTemp") } else if (temp.value < normalRangeLo) { valueOutOfRange(temp.value) nameOutOfRange("CoolantTemp") } } onValueOutOfRange: MessageRelay.setValueMsg(messageValue) onNameOutOfRange: MessageRelay.setNameMessage(messageName)
and this code gets this output in the console when the gauge goes low:
relayMessage: 20
relayName: "CoolantTemp"
relayMessage: 19
relayMessage: 18
relayMessage: 17
relayMessage: 16
relayMessage: 15But now I think I have a problem. If more than one gauge goes out of range at the same time, which is very possible when Bad Stuff happens, I won't be able to associate the value being sent with the name.
I've tried several ways to bundle the two variables into one message, but nothing seems to work. The closest I was able to get was some code that compiled and ran, but the QML side never seemed to trigger the signal.
If there is a way to bundle the variables into one message so that I can tell where the messages are coming from on the receiver side, I would really appreciate the advice - and by the way, I really appreciate the help so far.
-
Here is the code I finally got working:
QML Gauge:
CircularGauge { //temp id: temp // SIGNALING property real normalRangeHi: 180 property real normalRangeLo: 50 signal messageObject(var messageName, var messageValue) onValueChanged: { if (temp.value > normalRangeHi) { messageObject("Coolant Temp HI", temp.value) } else if (portCoolantTemp.value < normalRangeLo) { messageObject("Coolant Temp LO", temp.value) } } onMessageObject: MessageRelay.setValueMsg(messageName, messageValue)
MessageRelay.h:
class MessageRelay : public QObject { Q_OBJECT private: public: qint16 m_valueToRelay = 0; QString m_nameToRelay = ""; bool relayAlarm = false; signals: void valueMsgChanged(const QString relayName, const qint16 relayValue, const bool relayAlarm); public slots: Q_INVOKABLE void setValueMsg(const QString &relayName, const qint16 &relayValue){ if(relayValue != 0){ //qDebug()<< m_nameToRelay; m_nameToRelay = relayName; m_valueToRelay = relayValue; relayAlarm = true; emit valueMsgChanged(m_nameToRelay, m_valueToRelay, relayAlarm); } else {emit valueMsgChanged("", 0, false); } } };
And the receiver QML code:
Connections { target: MessageRelay onValueMsgChanged: {cnsl(relayName, relayValue, relayAlarm)} function cnsl(relayName, relayValue, relayAlarm) { if (relayValue > 0){ alarmState = relayAlarm txt.text = relayName + ": " + relayValue } else { alarmState = false } //console.log(alarmState) } }
EDIT
And the main.cpp code registering the class:int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); MessageRelay messageRelay; QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); engine.rootContext()->setContextProperty("MessageRelay", &messageRelay); if (engine.rootObjects().isEmpty()) return -1; return app.exec(); }