Accessing members of a class defined within another class
-
Hi Everyone,
I've been struggling to get the "proof of concept" code at the bottom to work as I want and hoping to get some help.The use case is that I'll have a c++ back end processing raw values from an input stream and I want to access these values from the QML UI like this
Text {text: qsTr("Value 1:" + backend.Value1.Value + " " + backend.Value1.Units)}
Text {text: qsTr("Value 2:" + backend.Value2.Value + " " + backend.Value2.Units)}The code below is just trying to get the QML / C++ interface working before developing the backend.
I also realise that each Value is being instantiated with the same values but I've been progressively working through the errors thrown during compile and execution and haven't got to sorting this bit.
The code below compiles but throws "TypeError: Cannot read property 'Value' of undefined" for the two text lines in main.qml.
Following this example https://doc.qt.io/qt-5/qtqml-cppintegration-topic.html I can get the functionality I want for a single value when it's not a member of another class. While I could create a separate class for each value I wouldn't be following good object orientated practice particularly as I have about 60 different values to access from QML.
As much as I've read the documents on Integrating QML and C++ and tried various versions of the code below, I've got no idea how to get QML to access the members of the Value class.
Appreciate any help and direction.
Peterbackend.h
#ifndef BACKEND_H #define BACKEND_H #include <QObject> #include <QString> class Value : public QObject { Q_OBJECT Q_PROPERTY(qreal Value READ getValue NOTIFY valueChanged) Q_PROPERTY(QString Units READ getUnits CONSTANT) public: explicit Value(QObject *parent = nullptr); // Value(QString _units, qreal _scale); Value(QString _units, qreal _scale); qreal getValue(); QString getUnits(); void newRawValue(qreal _rawValue); signals: void valueChanged(); private: QString units = ""; //prefer to be const qreal rawValue = 0; qreal scale = 0; //prefer to be const }; class BackEnd : public QObject { Q_OBJECT public: explicit BackEnd(QObject *parent = nullptr); Value Value1;// = Value("km",3); Value Value2;// = Value("hr",4); }; #endif // BACKEND_Hbackend.ccp
#include "backend.h" Value::Value(QObject *parent) : QObject(parent){ } //Value::Value(QString _units, qreal _scale){ // units = _units; // scale = _scale; //} qreal Value :: getValue(){return rawValue*scale;} QString Value :: getUnits(){return units;} void Value :: newRawValue(qreal _rawValue){ rawValue = _rawValue; emit valueChanged(); } BackEnd::BackEnd(QObject *parent) : QObject(parent){ }main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include "backend.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); qmlRegisterType<BackEnd>("io.qt.examples.backend", 1, 0, "BackEnd"); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); return app.exec(); }main.qml
import QtQuick 2.12 import QtQuick.Window 2.12 import io.qt.examples.backend 1.0 Window { visible: true width: 640 height: 480 BackEnd {id: backend} Text {text: qsTr("Value 1:" + backend.Value1.Value + " " + backend.Value1.Units)} Text {text: qsTr("Value 2:" + backend.Value2.Value + " " + backend.Value2.Units)} } -
Hi Everyone,
I've been struggling to get the "proof of concept" code at the bottom to work as I want and hoping to get some help.The use case is that I'll have a c++ back end processing raw values from an input stream and I want to access these values from the QML UI like this
Text {text: qsTr("Value 1:" + backend.Value1.Value + " " + backend.Value1.Units)}
Text {text: qsTr("Value 2:" + backend.Value2.Value + " " + backend.Value2.Units)}The code below is just trying to get the QML / C++ interface working before developing the backend.
I also realise that each Value is being instantiated with the same values but I've been progressively working through the errors thrown during compile and execution and haven't got to sorting this bit.
The code below compiles but throws "TypeError: Cannot read property 'Value' of undefined" for the two text lines in main.qml.
Following this example https://doc.qt.io/qt-5/qtqml-cppintegration-topic.html I can get the functionality I want for a single value when it's not a member of another class. While I could create a separate class for each value I wouldn't be following good object orientated practice particularly as I have about 60 different values to access from QML.
As much as I've read the documents on Integrating QML and C++ and tried various versions of the code below, I've got no idea how to get QML to access the members of the Value class.
Appreciate any help and direction.
Peterbackend.h
#ifndef BACKEND_H #define BACKEND_H #include <QObject> #include <QString> class Value : public QObject { Q_OBJECT Q_PROPERTY(qreal Value READ getValue NOTIFY valueChanged) Q_PROPERTY(QString Units READ getUnits CONSTANT) public: explicit Value(QObject *parent = nullptr); // Value(QString _units, qreal _scale); Value(QString _units, qreal _scale); qreal getValue(); QString getUnits(); void newRawValue(qreal _rawValue); signals: void valueChanged(); private: QString units = ""; //prefer to be const qreal rawValue = 0; qreal scale = 0; //prefer to be const }; class BackEnd : public QObject { Q_OBJECT public: explicit BackEnd(QObject *parent = nullptr); Value Value1;// = Value("km",3); Value Value2;// = Value("hr",4); }; #endif // BACKEND_Hbackend.ccp
#include "backend.h" Value::Value(QObject *parent) : QObject(parent){ } //Value::Value(QString _units, qreal _scale){ // units = _units; // scale = _scale; //} qreal Value :: getValue(){return rawValue*scale;} QString Value :: getUnits(){return units;} void Value :: newRawValue(qreal _rawValue){ rawValue = _rawValue; emit valueChanged(); } BackEnd::BackEnd(QObject *parent) : QObject(parent){ }main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include "backend.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); qmlRegisterType<BackEnd>("io.qt.examples.backend", 1, 0, "BackEnd"); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); return app.exec(); }main.qml
import QtQuick 2.12 import QtQuick.Window 2.12 import io.qt.examples.backend 1.0 Window { visible: true width: 640 height: 480 BackEnd {id: backend} Text {text: qsTr("Value 1:" + backend.Value1.Value + " " + backend.Value1.Units)} Text {text: qsTr("Value 2:" + backend.Value2.Value + " " + backend.Value2.Units)} }@_-Peter-_ thats so not possible, because your Value class is not a trivial type.
I think if you would fully register the class with the meta system you might have more luck accessing the direct member. But, the far easier option would be the following:public: explicit BackEnd(QObject *parent = nullptr); QINVOKABLE QVariant getValue1() {return QVariant::fromValue(Value1);} Value Value1;// = Value("km",3); Value Value2;// = Value("hr",4)Text {text: qsTr("Value 1:" + backend.getValue1().Value + " " + backend.getValue1().Units)} -
@_-Peter-_ thats so not possible, because your Value class is not a trivial type.
I think if you would fully register the class with the meta system you might have more luck accessing the direct member. But, the far easier option would be the following:public: explicit BackEnd(QObject *parent = nullptr); QINVOKABLE QVariant getValue1() {return QVariant::fromValue(Value1);} Value Value1;// = Value("km",3); Value Value2;// = Value("hr",4)Text {text: qsTr("Value 1:" + backend.getValue1().Value + " " + backend.getValue1().Units)}Your bakend object doesn't have any exposed properties named
Value1andValue2.
Use Q_PROPERTY to expose them like you did in yourValueclass. -
@_-Peter-_ thats so not possible, because your Value class is not a trivial type.
I think if you would fully register the class with the meta system you might have more luck accessing the direct member. But, the far easier option would be the following:public: explicit BackEnd(QObject *parent = nullptr); QINVOKABLE QVariant getValue1() {return QVariant::fromValue(Value1);} Value Value1;// = Value("km",3); Value Value2;// = Value("hr",4)Text {text: qsTr("Value 1:" + backend.getValue1().Value + " " + backend.getValue1().Units)}@J-Hilk Thanks for the response. When I add the line in I get the errors "incomplete result type 'QVariant' in function definition" and "incomplete type 'QVariant' named in nested name specifier"
class BackEnd : public QObject { Q_OBJECT public: explicit BackEnd(QObject *parent = nullptr); Q_INVOKABLE QVariant getValue1(){return QVariant::fromValue(Value1);} Value Value1();// = Value("km",3); Value Value2;// = Value("hr",4); }; -
Your bakend object doesn't have any exposed properties named
Value1andValue2.
Use Q_PROPERTY to expose them like you did in yourValueclass.@GrecKo I had given that a go but got the parse warning " Warning: Property declaration Value1 has no READ accessor function or associated MEMBER variable. The property will be invalid." and compiling ends.
I tried to a few different variations but got the same error.
class BackEnd : public QObject { Q_OBJECT Q_PROPERTY(qreal Value1.Value READ Value1.getValue NOTIFY Value1.valueChanged) Q_PROPERTY(QString Value1.Units READ Value1.getUnits CONSTANT) Q_PROPERTY(qreal Value2.Value READ Value2.getValue NOTIFY Value2.valueChanged) Q_PROPERTY(QString Value2.Units READ Value2.getUnits CONSTANT) public: explicit BackEnd(QObject *parent = nullptr); Value Value1;// = Value("km",3); Value Value2;// = Value("hr",4); }; -
@J-Hilk Thanks for the response. When I add the line in I get the errors "incomplete result type 'QVariant' in function definition" and "incomplete type 'QVariant' named in nested name specifier"
class BackEnd : public QObject { Q_OBJECT public: explicit BackEnd(QObject *parent = nullptr); Q_INVOKABLE QVariant getValue1(){return QVariant::fromValue(Value1);} Value Value1();// = Value("km",3); Value Value2;// = Value("hr",4); };@_-Peter-_ said in Accessing members of a class defined within another class:
"incomplete type 'QVariant'
you should know this!
#include <QVariant> -
@_-Peter-_ said in Accessing members of a class defined within another class:
"incomplete type 'QVariant'
you should know this!
#include <QVariant> -
Hi Everyone,
I've been struggling to get the "proof of concept" code at the bottom to work as I want and hoping to get some help.The use case is that I'll have a c++ back end processing raw values from an input stream and I want to access these values from the QML UI like this
Text {text: qsTr("Value 1:" + backend.Value1.Value + " " + backend.Value1.Units)}
Text {text: qsTr("Value 2:" + backend.Value2.Value + " " + backend.Value2.Units)}The code below is just trying to get the QML / C++ interface working before developing the backend.
I also realise that each Value is being instantiated with the same values but I've been progressively working through the errors thrown during compile and execution and haven't got to sorting this bit.
The code below compiles but throws "TypeError: Cannot read property 'Value' of undefined" for the two text lines in main.qml.
Following this example https://doc.qt.io/qt-5/qtqml-cppintegration-topic.html I can get the functionality I want for a single value when it's not a member of another class. While I could create a separate class for each value I wouldn't be following good object orientated practice particularly as I have about 60 different values to access from QML.
As much as I've read the documents on Integrating QML and C++ and tried various versions of the code below, I've got no idea how to get QML to access the members of the Value class.
Appreciate any help and direction.
Peterbackend.h
#ifndef BACKEND_H #define BACKEND_H #include <QObject> #include <QString> class Value : public QObject { Q_OBJECT Q_PROPERTY(qreal Value READ getValue NOTIFY valueChanged) Q_PROPERTY(QString Units READ getUnits CONSTANT) public: explicit Value(QObject *parent = nullptr); // Value(QString _units, qreal _scale); Value(QString _units, qreal _scale); qreal getValue(); QString getUnits(); void newRawValue(qreal _rawValue); signals: void valueChanged(); private: QString units = ""; //prefer to be const qreal rawValue = 0; qreal scale = 0; //prefer to be const }; class BackEnd : public QObject { Q_OBJECT public: explicit BackEnd(QObject *parent = nullptr); Value Value1;// = Value("km",3); Value Value2;// = Value("hr",4); }; #endif // BACKEND_Hbackend.ccp
#include "backend.h" Value::Value(QObject *parent) : QObject(parent){ } //Value::Value(QString _units, qreal _scale){ // units = _units; // scale = _scale; //} qreal Value :: getValue(){return rawValue*scale;} QString Value :: getUnits(){return units;} void Value :: newRawValue(qreal _rawValue){ rawValue = _rawValue; emit valueChanged(); } BackEnd::BackEnd(QObject *parent) : QObject(parent){ }main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include "backend.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); qmlRegisterType<BackEnd>("io.qt.examples.backend", 1, 0, "BackEnd"); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); return app.exec(); }main.qml
import QtQuick 2.12 import QtQuick.Window 2.12 import io.qt.examples.backend 1.0 Window { visible: true width: 640 height: 480 BackEnd {id: backend} Text {text: qsTr("Value 1:" + backend.Value1.Value + " " + backend.Value1.Units)} Text {text: qsTr("Value 2:" + backend.Value2.Value + " " + backend.Value2.Units)} }This is strictly practical advice. If this is an intellectual exercise, please ignore. All sorts of interesting things happen when the rules aren't followed.
@_-Peter-_ said in Accessing members of a class defined within another class:
Q_PROPERTY(qreal Value1.Value READ Value1.getValue NOTIFY Value1.valueChanged)In QML, not solely of custom,
.is a token separator, and a token starting with an upper-case letter is a type.https://doc.qt.io/qt-6/qtqml-documents-definetypes.html#naming-custom-qml-object-types:
It must begin with an uppercase letter.
Ignoring this leads to headaches. If the goal is working code rather than exploring the limits of the framework, do not use
.or strings starting with upper case letters in property names.@_-Peter-_ said in Accessing members of a class defined within another class:
backend.h
[...]backend.ccp
[...]Only tangentially connected, but defining separate .h, .cpp, and main.cpp files for an example makes the code harder to follow for a forum post. My not at all humble opinion is that everything should be inlined to focus on compilation units rather than files.
-
This is strictly practical advice. If this is an intellectual exercise, please ignore. All sorts of interesting things happen when the rules aren't followed.
@_-Peter-_ said in Accessing members of a class defined within another class:
Q_PROPERTY(qreal Value1.Value READ Value1.getValue NOTIFY Value1.valueChanged)In QML, not solely of custom,
.is a token separator, and a token starting with an upper-case letter is a type.https://doc.qt.io/qt-6/qtqml-documents-definetypes.html#naming-custom-qml-object-types:
It must begin with an uppercase letter.
Ignoring this leads to headaches. If the goal is working code rather than exploring the limits of the framework, do not use
.or strings starting with upper case letters in property names.@_-Peter-_ said in Accessing members of a class defined within another class:
backend.h
[...]backend.ccp
[...]Only tangentially connected, but defining separate .h, .cpp, and main.cpp files for an example makes the code harder to follow for a forum post. My not at all humble opinion is that everything should be inlined to focus on compilation units rather than files.
-
After almost turning software problems into a hardware problem a few times, I finally got it to work as I intended.
With the addition of Q_INVOKABLE as suggested by @J-Hilk and the removal of the Value(QObject *parent) constructor, it all works.
Final code for prosperity below:
backend.h#ifndef BACKEND_H #define BACKEND_H #include <QObject> #include <QString> #include <QVariant> class Value : public QObject { Q_OBJECT Q_PROPERTY(qreal value READ getValue NOTIFY valueChanged) Q_PROPERTY(QString units READ getUnits CONSTANT) public: Value(QString _units, qreal _scale); ~Value(); qreal getValue() const; QString getUnits() const; void newRawValue(qreal _rawValue); signals: void valueChanged(); private: const QString units; qreal rawValue = 1; const qreal scale; }; class BackEnd : public QObject { Q_OBJECT public: explicit BackEnd(QObject *parent = nullptr); Q_INVOKABLE QVariant getValue1(){return QVariant::fromValue(value1);} Q_INVOKABLE QVariant getValue2(){return QVariant::fromValue(value2);} private: Value *value1 = new Value("km",3); Value *value2 = new Value("hr",4); }; #endif // BACKEND_Hbackend.cpp
#include "backend.h" Value::Value(QString _units, qreal _scale): units(_units), scale(_scale){} Value::~Value(){} qreal Value :: getValue() const {return rawValue*scale;} QString Value :: getUnits()const {return units;} void Value :: newRawValue(qreal _rawValue){ if(rawValue == _rawValue) return; rawValue = _rawValue; emit valueChanged(); } BackEnd :: BackEnd(QObject *parent) : QObject(parent){ }main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include "backend.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); qmlRegisterType<BackEnd>("backend", 1, 0, "BackEnd"); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); return app.exec(); }main.qml
import QtQuick 2.12 import QtQuick.Window 2.12 import backend 1.0 Window { visible: true width: 640 height: 480 BackEnd {id: backend} Text {text: qsTr("Value 1: " + backend.getValue1().value + " " + backend.getValue1().units)} Text {y: 20;text: qsTr("Value 2: " + backend.getValue2().value + " " + backend.getValue2().units)} } -
_ _-Peter-_ has marked this topic as solved on
-
After almost turning software problems into a hardware problem a few times, I finally got it to work as I intended.
With the addition of Q_INVOKABLE as suggested by @J-Hilk and the removal of the Value(QObject *parent) constructor, it all works.
Final code for prosperity below:
backend.h#ifndef BACKEND_H #define BACKEND_H #include <QObject> #include <QString> #include <QVariant> class Value : public QObject { Q_OBJECT Q_PROPERTY(qreal value READ getValue NOTIFY valueChanged) Q_PROPERTY(QString units READ getUnits CONSTANT) public: Value(QString _units, qreal _scale); ~Value(); qreal getValue() const; QString getUnits() const; void newRawValue(qreal _rawValue); signals: void valueChanged(); private: const QString units; qreal rawValue = 1; const qreal scale; }; class BackEnd : public QObject { Q_OBJECT public: explicit BackEnd(QObject *parent = nullptr); Q_INVOKABLE QVariant getValue1(){return QVariant::fromValue(value1);} Q_INVOKABLE QVariant getValue2(){return QVariant::fromValue(value2);} private: Value *value1 = new Value("km",3); Value *value2 = new Value("hr",4); }; #endif // BACKEND_Hbackend.cpp
#include "backend.h" Value::Value(QString _units, qreal _scale): units(_units), scale(_scale){} Value::~Value(){} qreal Value :: getValue() const {return rawValue*scale;} QString Value :: getUnits()const {return units;} void Value :: newRawValue(qreal _rawValue){ if(rawValue == _rawValue) return; rawValue = _rawValue; emit valueChanged(); } BackEnd :: BackEnd(QObject *parent) : QObject(parent){ }main.cpp
#include <QGuiApplication> #include <QQmlApplicationEngine> #include "backend.h" int main(int argc, char *argv[]) { QGuiApplication app(argc, argv); qmlRegisterType<BackEnd>("backend", 1, 0, "BackEnd"); QQmlApplicationEngine engine; engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); return app.exec(); }main.qml
import QtQuick 2.12 import QtQuick.Window 2.12 import backend 1.0 Window { visible: true width: 640 height: 480 BackEnd {id: backend} Text {text: qsTr("Value 1: " + backend.getValue1().value + " " + backend.getValue1().units)} Text {y: 20;text: qsTr("Value 2: " + backend.getValue2().value + " " + backend.getValue2().units)} }Q_INVOKABLE leads to worse QML code here.
Semantically, Value1 and Value2 are properties of your Backend.
Invokable function are for functions, stuff that mutates your state or compute something, that's not the case here.If the Value1 and Value2 pointers never change, expose them as Q_PROPERTY in Backend.h:
Q_PROPERTY(Value* value1 READ getValue1 CONSTANT)
If for some reason you don't want to register theValuetype, you don't have to use QVariant, you can expose it as aQ_PROPERTY(QObject*, ...Keep your getters as is and just delete the
Q_INVOKABLEpart.QML code would then be:
Text {text: qsTr("Value 1: " + backend.value1.value + " " + backend.value1.units)} Text {y: 20;text: qsTr("Value 2: " + backend.value2.value + " " + backend.value2.units)}Which can be rewritten with a Column and a repeater:
Column { Repeater { model: [backend.value1, backend.value2] delegate: Text { text: qsTr("Value %1: %2 %3 ").arg(index).arg(modelData.value).arg(modelData.units) padding: 8 } }And then depending on the actual usecase you might consider exposing your values in a model instead of named properties for each Value1, Value2
-
Q_INVOKABLE leads to worse QML code here.
Semantically, Value1 and Value2 are properties of your Backend.
Invokable function are for functions, stuff that mutates your state or compute something, that's not the case here.If the Value1 and Value2 pointers never change, expose them as Q_PROPERTY in Backend.h:
Q_PROPERTY(Value* value1 READ getValue1 CONSTANT)
If for some reason you don't want to register theValuetype, you don't have to use QVariant, you can expose it as aQ_PROPERTY(QObject*, ...Keep your getters as is and just delete the
Q_INVOKABLEpart.QML code would then be:
Text {text: qsTr("Value 1: " + backend.value1.value + " " + backend.value1.units)} Text {y: 20;text: qsTr("Value 2: " + backend.value2.value + " " + backend.value2.units)}Which can be rewritten with a Column and a repeater:
Column { Repeater { model: [backend.value1, backend.value2] delegate: Text { text: qsTr("Value %1: %2 %3 ").arg(index).arg(modelData.value).arg(modelData.units) padding: 8 } }And then depending on the actual usecase you might consider exposing your values in a model instead of named properties for each Value1, Value2
@GrecKo Thanks for that it does make sense.
You are correct that value1 & 2 pointers never change. In the actual use case there will be other unexposed backend methods that update Value's rawValue which is then scaled to real units for the QML layer to read. All of Value's exposed methods and properties will be only be read by the QML layer. There will be exposed backend methods that are written to by the QML layer and other values that will be exposed in a model, but this is straight forward as there's plenty of information out there on doing that.
Can you please confirm your recommended changes to the code I previously posted. My understanding of the changes is below but that throws a "cannot convert 'QVariant' to 'Value*' in assignment." during the build.
class BackEnd : public QObject { Q_OBJECT Q_PROPERTY(Value* value1 READ getValue1 CONSTANT) Q_PROPERTY(Value* value2 READ getValue2 CONSTANT) public: explicit BackEnd(QObject *parent = nullptr); QVariant getValue1(){return QVariant::fromValue(value1);} QVariant getValue2(){return QVariant::fromValue(value2);}When I change the Q_PROPERTY lines to below everything works
Q_PROPERTY(QVariant value1 READ getValue1 CONSTANT)Thanks again for your assistance!