Calling C++ from QML
-
I am working on a test project to learn QML and C++ integration, here is the QML:
import QtQuick 2.0 import QtQuick.Controls 1.4 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import QtQuick.Extras 1.4 import QtQuick.Window 2.12 import SimonsCPP 1.0 Window { id: root visible: true width: 640 height: 480 title: qsTr("Playground / Staging Arena") property bool staleHdg: false SimonP { id: simonP } Text { id:hdgValue anchors.top: parent.top color: "green" font.pointSize: parent.width < 150 ? 30 : 16 text: simonP.value horizontalAlignment: Text.AlignRight verticalAlignment: Text.AlignTop width: 64 } Text { id:hdgLabel anchors.top: hdgValue.bottom color: "black" text: "HDG" horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignTop font.pointSize: parent.width < 150 ? 30 : 16 width: 40 } Image { id: staleIndicator visible: staleHdg anchors.left: hdgValue.right source: "/stale.svg" } Button { id: btnData text: "Click Me..." anchors.top: hdgLabel.bottom onClicked: function() { var currValue = hdgValue.text ,newValue = String(parseFloat(currValue) + 1); var tmp = typeof SimonP; var tmp2 = typeof simonP; var tmp3 = typeof tmp.setValue; var tmp4 = typeof tmp2.setValue; SimonP.setValue(newValue); console.log( "currentValue: " + currValue + ", newValue: " + newValue ); } } }
Here is the C++, prototype:
#ifndef SIMONP_H #define SIMONP_H #include <QObject> #include <QString> #include <qqml.h> class SimonP : public QObject { Q_OBJECT Q_PROPERTY(QString value READ value WRITE setValue) QML_ELEMENT private: QString mstrValue; public: explicit SimonP(QObject *parent_ = nullptr); Q_INVOKABLE void setValue(const QString& crstrValue); QString value() { return mstrValue; } signals: }; #endif // SIMONP_H
And the implementation:
#include "simonp.h" SimonP::SimonP(QObject* parent_) : QObject(parent_) { mstrValue = "123"; } void SimonP::setValue(const QString& crstrValue) { mstrValue = crstrValue; }
In the QML when I click the button, I have a function onClicked with a breakpoint:
var currValue = hdgValue.text ,newValue = String(parseFloat(currValue) + 1); var tmp = typeof SimonP; var tmp2 = typeof simonP; var tmp3 = typeof tmp.setValue; var tmp4 = typeof tmp2.setValue; SimonP.setValue(newValue); console.log( "currentValue: " + currValue + ", newValue: " + newValue );
I can see that tmp and tmp2 are "object", however both tmp3 and tmp4 are "undefined". How do I fix this so I can call setValue from the QML?
-
Fixed:
var currValue = simonP.value ,newValue = String(parseFloat(currValue) + 1); var tmp = typeof SimonP; var tmp2 = typeof simonP; var tmp3 = typeof SimonP.setValue; var tmp4 = typeof simonP.setValue; simonP.setValue(newValue); hdgValue.text = newValue; console.log( "currentValue: " + currValue + ", newValue: " + newValue );
This works!, tmp4 now shows "function" and the value is updated.
-
@J-Hilk , thank you, where? I think you may be misreading the code. When I call setValue it does have parentheses, in the tests using typeof I am not intending to call the function but test the type and I expect to see tmp3 or tmp4 or both to have a value of "function".
-
Fixed:
var currValue = simonP.value ,newValue = String(parseFloat(currValue) + 1); var tmp = typeof SimonP; var tmp2 = typeof simonP; var tmp3 = typeof SimonP.setValue; var tmp4 = typeof simonP.setValue; simonP.setValue(newValue); hdgValue.text = newValue; console.log( "currentValue: " + currValue + ", newValue: " + newValue );
This works!, tmp4 now shows "function" and the value is updated.
-
Use your Q_PROPERTY instead. You don't need to mark the setter as Q_INVOKABLE.
Add a NOTIFY signal to it to use property bindings ( https://doc.qt.io/qt-6/qtqml-cppintegration-exposecppattributes.html#exposing-properties ). With this the QML enfine is made aware of the property changes and will update thetext
ofhdgValue
automatically.onClicked: function() { var currValue = simonP.value var newValue = String(parseFloat(currValue) + 1); simonP.value = newValue; // hdgValue.text = newValue; // no need to do that if you have a NOTIFY signal, it is actually counter productive and break the binding. }
If it makes sense in your case, you could just define a float/double property in SimonP and do the following:
onClicked: ++simonP.value
You are overcomplicating your code.
-
Finished prototype and re-worked so it doesn't use a timer, QML:
import QtQuick 2.0 import QtQuick.Controls 2.15 import QtQuick.Controls.Styles 1.4 import QtQuick.Layouts 1.3 import QtQuick.Extras 1.4 import QtQuick.Window 2.12 import SimonsCPP 1.0 Window { id: root visible: true width: 640 height: 480 title: qsTr("Playground / Staging Arena") function checkStaleStatus(strToolTip) { if ( typeof strToolTip != "string" ) { simonP.stale() return } staleIndicator.visible = (strToolTip.length > 0) hdgValue.ToolTip.text = strToolTip } SimonP { id: simonP onValueChanged: { checkStaleStatus(strToolTip) } } Label { id:hdgValue anchors.top: parent.top color: "green" font.pointSize: parent.width < 150 ? 30 : 16 text: String(simonP.value) horizontalAlignment: Text.AlignRight verticalAlignment: Text.AlignTop ToolTip.visible: ToolTip.text.length > 0 && ma.containsMouse MouseArea { id: ma anchors.fill: parent hoverEnabled: true onContainsMouseChanged: checkStaleStatus() } width: 256 } Label { id:hdgLabel anchors.horizontalCenter: hdgValue.horizontalCenter anchors.top: hdgValue.bottom color: "black" text: "HDG" horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignTop font.pointSize: parent.width < 150 ? 30 : 16 width: 32 } Image { id: staleIndicator visible: false anchors.left: hdgValue.right source: "/stale.svg" } Button { id: btnData text: "Click Me..." anchors.top: hdgLabel.bottom onClicked: { var currValue = simonP.value ,newValue = parseFloat(currValue) + 1; simonP.setValue(newValue); hdgValue.text = String(newValue); } } Component.onCompleted: { simonP.stale() } }
SimonP.H:
#ifndef SIMONP_H #define SIMONP_H #include <QObject> #include <qqml.h> class SimonP : public QObject { Q_OBJECT Q_PROPERTY(double value READ dataValue WRITE setValue NOTIFY toolTip) QML_ELEMENT private: bool mblnStale; double mdblEpoch, mdblValue; static double msdblValid; public: explicit SimonP(QObject *parent_ = nullptr); double dataValue() { return mdblValue; } Q_INVOKABLE void setValue(double dblValue); Q_INVOKABLE bool stale(); double timestamp(); signals: void toolTip(QString strToolTip); }; #endif // SIMONP_H
SimonP.CPP:
#include <QDateTime> #include "simonp.h" static const long sclngSecsInMin(60); static const long sclngMinsInHour(60); static const long sclngHoursInDay(24); static const long sclngMsInSec(1000); static const long sclngMsInMin(sclngSecsInMin * sclngMsInSec); static const long sclngMsInHour(sclngMinsInHour * sclngMsInMin); static const long sclngMsInDay(sclngHoursInDay * sclngMsInHour); static const char scszDelim[](", "); double SimonP::msdblValid = 5000.0; //5 seconds in milliseconds SimonP::SimonP(QObject* parent_) : QObject(parent_) { mdblEpoch = mdblValue = 0.0; } double SimonP::timestamp() { return mdblEpoch; } void SimonP::setValue(double dblValue) { mdblValue = dblValue; mdblEpoch = QDateTime::currentMSecsSinceEpoch(); //Update stale age stale(); } bool SimonP::stale() { double dblAge(QDateTime::currentMSecsSinceEpoch()); QString strToolTip; bool blnStale; if ( mdblEpoch > 0.0 ) { dblAge -= mdblEpoch; } blnStale = dblAge >= msdblValid; if ( blnStale == true ) { long lngRemaining(static_cast<long>(dblAge)); long lngDays(lngRemaining / sclngMsInDay); lngRemaining -= (lngDays * sclngMsInDay); long lngHours(lngRemaining / sclngMsInHour); lngRemaining -= (lngHours * sclngMsInHour); long lngMins(lngRemaining / sclngMsInMin); lngRemaining -= (lngMins * sclngMsInMin); long lngSecs(lngRemaining / sclngMsInSec); lngRemaining -= (lngSecs * sclngMsInSec); long lngMS(lngRemaining); if ( lngDays > 0 ) { strToolTip.append(QString("%1 days").arg(lngDays)); } if ( lngHours > 0 ) { if ( strToolTip.isEmpty() != true ) { strToolTip.append(scszDelim); } strToolTip.append(QString("%1 hours").arg(lngHours)); } if ( lngMins > 0 ) { if ( strToolTip.isEmpty() != true ) { strToolTip.append(scszDelim); } strToolTip.append(QString("%1 minutes").arg(lngMins)); } if ( strToolTip.isEmpty() != true ) { strToolTip.append(scszDelim); } strToolTip.append(QString("%1").arg(lngSecs)); if ( lngMS > 0 ) { strToolTip.append(QString(".%1").arg(lngMS)); } strToolTip.append(" seconds"); strToolTip.prepend("Stale: "); } emit toolTip(strToolTip); return blnStale; }
All works great now, tool tip is shown when data is stale and mouse moves over value.