C++ / QML property problem
-
I thought I had this beat, I have an object, C++ prototype **staleData.h":
class StaleData; typedef QString StaleDataMapKey; typedef QMap<StaleDataMapKey, StaleData*> StaleDataMap; class StaleData : public QObject { Q_OBJECT Q_PROPERTY(bool interface READ interfaceStatus WRITE setInterfaceStatus) Q_PROPERTY(double value READ dataValue WRITE setValue NOTIFY toolTip) Q_PROPERTY(double timeout READ timeoutValue WRITE setTimeout) Q_PROPERTY(double tolerance READ toleranceValue WRITE setTolerance) private: bool mblnDeviceSts, mblnInitd, mblnIsStale; double mdblEpoch, mdblTimeout, mdblTolerance, mdblValue; static const double mscdblDefaultTimeout; static StaleDataMap msStaleData; //All configured stale data types public: explicit StaleData(QObject *parent_ = nullptr); static StaleDataMap* pGetAllstaledata() { return &StaleData::msStaleData; } static StaleData* pFindData(QString strUDT) { StaleDataMap::const_iterator citFound(StaleData::msStaleData.find(strUDT)); if ( citFound != StaleData::msStaleData.constEnd() ) { return citFound.value(); } return nullptr; } Q_INVOKABLE bool canbeStale(QString strUDT) { StaleData* pobjStaleData(StaleData::pFindData(strUDT)); if ( pobjStaleData != nullptr ) { return true; } return false; } double& dataValue() { return mdblValue; } Q_INVOKABLE bool interfaceStatus() { return mblnDeviceSts; } Q_INVOKABLE void setInterfaceStatus(bool blnEN) { mblnDeviceSts = blnEN; } Q_INVOKABLE void setTimeout(double dblTimeout) { mdblTimeout = dblTimeout; } Q_INVOKABLE void setTimeout(QString strTimeout); Q_INVOKABLE void setTolerance(double dblTolerance) { mdblTolerance = dblTolerance; } Q_INVOKABLE void setUDT(QString strUDT); Q_INVOKABLE void setValue(double dblValue); Q_INVOKABLE bool stale(); Q_INVOKABLE QString strNormal(); Q_INVOKABLE QString strStale(); double& timeoutValue() { return mdblTimeout; } double& timestamp() { return mdblEpoch; } double& toleranceValue() { return mdblTolerance; } signals: void dataIsOk(const QString cstrColour); void dataIsStale(const QString cstrColour); void toolTip(QString strToolTip); public slots: };
Implementation:
#include <float.h> #include <math.h> #include <QDateTime> #include "staleData.H" //Static initialisation 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[](", "); static const char scszStale[]("#777777"); static const char scszNormal[]("#000000"); const double StaleData::mscdblDefaultTimeout(8000.0); StaleDataMap StaleData::msStaleData; /** * @brief staledata::StaleData * @param parent_ Optional pointer to parent */ StaleData::StaleData(QObject* parent_) : QObject(parent_) { mblnDeviceSts = mblnInitd = mblnIsStale = false; mdblTimeout = StaleData::mscdblDefaultTimeout; mdblEpoch = mdblTolerance = mdblValue = 0.0; } /** * @brief StaleData::setTimeout * @param strTimeout Timeout as time string HH:MM:SS.zzz */ void StaleData::setTimeout(QString strTimeout) { QTime tmTimeout(QTime::fromString(strTimeout, Qt::TextDate)); double dblTimeout(QTime(0,0).msecsTo(tmTimeout)); setTimeout(dblTimeout); } /** * @brief StaleData::setValue * @param dblValue Value to use */ void StaleData::setValue(double dblValue) { if ( mdblTolerance > 0.0 && mblnInitd == true ) { double dblDiff = mdblValue - dblValue; if ( fabs(dblDiff) < mdblTolerance ) { return; } } mblnInitd = true; mblnDeviceSts = true; mdblValue = dblValue; mdblEpoch = QDateTime::currentMSecsSinceEpoch(); //Update stale age stale(); } /** * @brief StaleData::setUDT * @param strUDT User Defined Tag to unqiuely identify data */ void StaleData::setUDT(QString strUDT) { StaleData* pobjNew(StaleData::pFindData(strUDT)); if ( pobjNew != nullptr ) { //Already defined, no action required return; } StaleData::msStaleData.insert(strUDT, new StaleData()); } /** * @brief StaleData::stale * @return true if data is stale else false */ bool StaleData::stale() { double dblAge(QDateTime::currentMSecsSinceEpoch()); QString strToolTip; bool blnStale; if ( mdblEpoch > 0.0 ) { dblAge -= mdblEpoch; } blnStale = dblAge >= mdblTimeout; 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 ) { const QString cstrZeros("000"); const int cintMSlength(cstrZeros.length()); QString strMS(QString::number(lngMS)); int intLength(strMS.length()); if ( intLength < cintMSlength ) { strMS = cstrZeros.mid(0, cintMSlength - intLength) + strMS; } strToolTip.append(QString(".%1").arg(strMS)); } strToolTip.append(" seconds"); if ( mblnDeviceSts != true ) { strToolTip.prepend("IF down: "); } else { strToolTip.prepend("Stale: "); } } emit toolTip(strToolTip); if ( mblnIsStale != blnStale ) { //Change in state if ( blnStale == true ) { emit dataIsStale(strStale()); } else { emit dataIsOk(strNormal()); } //Save state mblnIsStale = blnStale; } return blnStale; } /** * @brief StaleData::strNormal * @return RGB colour code for normal data representation */ QString StaleData::strNormal() { return QString(scszNormal); } /** * @brief StaleData::strStale * @return RGB colour code for stale data representation */ QString StaleData::strStale() { return QString(scszStale); }
In the C++ main:
#include <QGuiApplication> #include <QQmlApplicationEngine> #include <QQmlExtensionPlugin> #include <string> #include <qqml.h> #include "staleData.H" using namespace std; int main(int argc, char *argv[]) { QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QGuiApplication app(argc, argv); QQmlApplicationEngine engine; qmlRegisterType<StaleData> ("SimonsCPP", 1, 0, "StaleData"); const QUrl url(QStringLiteral("qrc:/stage.qml")); setenv ("TZ", "UTC", 1); 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(); }
DataModel.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 Item { id: root property var fontPointSize: 16 property string label property string labelColor: "#FF000000" property var percision: 4 property real textWidth: 80 property real timeout: 5000 property string units property string unitsColor: "#FF000000" property var value: simonP.value function checkStaleStatus(strToolTip) { if ( typeof strToolTip != "string" ) { simonP.stale() return } rImage.ToolTip.text = strToolTip rImage.visible = (strToolTip.length > 0) if ( rImage.visible === true ) { rText.color = simonP.strStale() } else { rText.color = simonP.strNormal() } } function rad2Deg(rad_) { return rad_ * 180.0 / Math.PI; } function setPrecision (val_, sigDigit_) { var precision = Math.abs (val_) >= 1000 ? sigDigit_ - 4 : Math.abs (val_) >= 100 ? sigDigit_ - 3 : Math.abs (val_) >= 10 ? sigDigit_ - 2 : 2; if (precision < 0) precision = 0; return val_.toFixed (precision); } function setValue(newValue) { simonP.setValue(newValue) rText.text = setPrecision (newValue, percision) } StaleData { id: simonP onValueChanged: { if ( simonP.canbeStale(rLabel.text) !== true ) { return; } checkStaleStatus(strToolTip) } } Label { id: rUnits anchors.top: root.top anchors.right: root.right text: { root.units ? String(root.units) : "" } verticalAlignment: Text.AlignTop horizontalAlignment: Text.AlignRight font.pointSize: root.fontPointSize color: root.unitsColor visible: root.units && root.units.length > 0 } Text { id: rText anchors { right: root.right rightMargin: rUnits.visible ? 24 : 0 top: root.top } verticalAlignment: Text.AlignTop horizontalAlignment: Text.AlignRight font.pointSize: 16//root.fontPointSize text: { root.value ? String(setPrecision (rad2Deg (root.value), percision)) : "N/A" } width: root.textWidth } Label { id: rLabel text: root.label anchors.top: rText.bottom anchors.right: rText.right verticalAlignment: Text.AlignTop horizontalAlignment: Text.AlignRight font.pointSize: root.fontPointSize color: root.labelColor } Image { id: rImage anchors { right: rLabel.left verticalCenter: rLabel.verticalCenter } visible: simonP.canbeStale(rLabel.text) source: "/stale.svg" ToolTip.visible: (rImage.visible && ToolTip.text.length) > 0 && ma.containsMouse MouseArea { id: ma anchors.fill: parent hoverEnabled: true onContainsMouseChanged: checkStaleStatus() } } Timer { interval: 250 running: true repeat: true onTriggered: simonP.stale() } Component.onCompleted: { simonP.setUDT("DPT"); } }
Stale.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") Button { id: btnDptData text: "DPT Click Me..." anchors { left: parent.left top: parent.top } onClicked: { dpt.setValue(parseFloat(dpt.value) + 0.01); } } Button { id: btnHdgData text: "HDG Click Me..." anchors { left: btnDptData.left top: btnDptData.bottom topMargin: 4 } onClicked: { hdg.setValue(parseFloat(hdg.value) + 0.01); } } Button { id: btnDeviceFailure text: "Set Device Failure" enabled: true anchors { left: btnDptData.left top: btnHdgData.bottom topMargin: 4 } onClicked: { dpt.interface = false; hdg.interface = false; btnDeviceFailure.enabled = false } } Button { id: btnClearDevFailure text: "Clr Device Failure" enabled: (btnDeviceFailure.enabled == true) ? false : true anchors { left: btnDptData.left top: btnDeviceFailure.bottom topMargin: 4 } onClicked: { dpt.interface = true; hdg.interface = true; btnDeviceFailure.enabled = true } } DataModel { id: dpt label: "DPT" units: "M" anchors { right: parent.right top: parent.top } fontPointSize: 18 textWidth: 256 } DataModel { id: hdg label: "HDG" units: "\xB0" anchors { right: parent.right top: parent.top topMargin: 80 } fontPointSize: dpt.fontPointSize textWidth: dpt.textWidth } }
When I click the button btnDeviceFailure, I see in QML Debugger Console:
qrc:/stage.qml:49: Error: Cannot assign to non-existent property "interface" qrc:/stage.qml: 49
What haven't I done or what have I done wrong?