Q_PROPERTY System not working as expected - c++

I have this class I intended to use in the context of a qml engine so in order to use property binding I setted up these Q_PROPERY macros. I want to use the MEMBER keyword and have the notify signal emitted automatically.
class InterfaceBackend : public QObject
{
Q_OBJECT
Q_PROPERTY(quint8 current_view MEMBER m_current_view NOTIFY sCurrentViewChanged)
Q_PROPERTY(quint8 future_view MEMBER m_future_view NOTIFY sFutureViewChanged)
public:
explicit InterfaceBackend(QObject *parent = 0);
~InterfaceBackend();
quint8 getCurrentView() { return this->m_current_view; }
quint8 getFutureView() { return this->m_future_view; }
private:
quint8 m_current_view;
quint8 m_future_view;
QByteArray m_selected_language;
public slots:
void onLanguageSelected(QByteArray language);
private slots:
signals:
void sCurrentViewChanged(quint8 current_view);
void sFutureViewChanged(quint8 future_view);
};
InterfaceBackend::InterfaceBackend(QObject *parent) : QObject(parent)
{
this->setObjectName("backend");
QObject::connect(this, &InterfaceBackend::sFutureViewChanged, []() {qDebug() << "sFutureViewChanged";});
this->m_current_view=1;
this->m_future_view=1;
}
InterfaceBackend::~InterfaceBackend()
{
}
void InterfaceBackend::onLanguageSelected(QByteArray language)
{
this->m_selected_language=language;
this->m_future_view=2;
}
qt docs say:
A NOTIFY signal is optional. If defined, it should specify one existing signal in that class that is emitted whenever the value of the property changes. NOTIFY signals for MEMBER variables must take zero or one parameter, which must be of the same type as the property. The parameter will take the new value of the property. The NOTIFY signal should only be emitted when the property has really been changed, to avoid bindings being unnecessarily re-evaluated in QML, for example. Qt emits automatically that signal when needed for MEMBER properties that do not have an explicit setter
But whenever I call the slot the signals never gets called nor the property is updated in the qml model, what's wrong!?

To give an answer that is technically more accurate:
The MEMBER in Q_PROPERTY will tell the moc (Meta object compiler) that when accessing the property via the meta object it should use the member directly instead of a getter or setter method. So the moc will the generate a setter method internally that sets the member and emits the signal - it basically just does the work of writing getters/setters for you. Since changing a member needs to emit the change signal, this is automatically done when the property is written from the meta object system. So, calling:
backend->setProperty("future_view", future_view);
will correctly emit the changed signal. This is the only guarantee that is given when using MEMBER. Changes, that are done via the meta property will trigger the change signal. This means if you would set future_view from QML directly, without the onLanguageSelected method, it would actually work.
In your example however you directly write a value to the member inside a special method - This will not trigger the signal automatically! (I mean, how should Qt even know you did that). So what you need to do is whenever you change the value of your member you need to emit the change signal yourself:
void onLanguageSelected(QByteArray language)
{
this->m_selected_language=language;
this->m_future_view=2;
emit sFutureViewChanged();
}
Edit: If you were trying prevent the properties from beeing written directly from QML, using MEMBER will not work! Use a getter instead and only register the getter with the property. Use the same code as above to write and change the properties:
Q_PROPERTY(quint8 future_view READ futureView NOTIFY sFutureViewChanged)

As you can see in this article:
New keyword in Q_PROPERTY: MEMBER let you bind a property to a class member without requiring to have a getter or a setter.
So you should remove your getters, and your result code will look like this
class InterfaceBackend : public QObject
{
Q_OBJECT
Q_PROPERTY(quint8 current_view MEMBER m_current_view NOTIFY sCurrentViewChanged)
Q_PROPERTY(quint8 future_view MEMBER m_future_view NOTIFY sFutureViewChanged)
public:
explicit InterfaceBackend(QObject *parent = 0)
: QObject(parent)
{
this->setObjectName("backend");
QObject::connect(this, &InterfaceBackend::sFutureViewChanged, []() { qDebug() << "sFutureViewChanged";});
this->m_current_view=1;
emit sCurrentViewChanged();
this->m_future_view=1;
emit sFutureViewChanged();
}
~InterfaceBackend() = default;
private:
quint8 m_current_view;
quint8 m_future_view;
QByteArray m_selected_language;
public slots:
void onLanguageSelected(QByteArray language) {
this->m_selected_language=language;
this->m_future_view=2;
emit sFutureViewChanged();
}
signals:
void sCurrentViewChanged();
void sFutureViewChanged();
};

Every Q_PROPERTY must have READ public method and WRITE public slot, also the signal will never automatically emited, you should emit it whenever MEMBER change.
class InterfaceBackend : public QObject
{
Q_OBJECT
Q_PROPERTY(quint8 current_view MEMBER m_current_view READ currentView WRITE setCurrentView NOTIFY sCurrentViewChanged)
Q_PROPERTY(quint8 future_view MEMBER m_future_view READ futureView WRITE setFutureView NOTIFY sFutureViewChanged)
public:
explicit InterfaceBackend(QObject *parent = 0);
~InterfaceBackend();
quint8 currentView() const
{
return m_current_view;
}
quint8 futureView() const
{
return m_future_view;
}
private:
quint8 m_current_view;
quint8 m_future_view;
QByteArray m_selected_language;
public slots:
void onLanguageSelected(QByteArray language);
void setCurrentView(quint8 current_view)
{
if (m_current_view == current_view)
return;
m_current_view = current_view;
emit sCurrentViewChanged(m_current_view);
}
void setFutureView(quint8 future_view)
{
if (m_future_view == future_view)
return;
m_future_view = future_view;
emit sFutureViewChanged(m_future_view);
}
private slots:
signals:
void sCurrentViewChanged(quint8 current_view);
void sFutureViewChanged(quint8 future_view);
};
InterfaceBackend::InterfaceBackend(QObject *parent) : QObject(parent)
{
this->setObjectName("backend");
QObject::connect(this, &InterfaceBackend::sFutureViewChanged, []() {qDebug() << "sFutureViewChanged";});
this->m_current_view=1;
this->m_future_view=1;
}
InterfaceBackend::~InterfaceBackend()
{
}
void InterfaceBackend::onLanguageSelected(QByteArray language)
{
this->m_selected_language=language;
this->m_future_view=2;
}

Spending some time, I got it working as I need, althought this is EXTREMELY UGLY and BY NO MEANS DECLARATIVE I think it is the only way to achive double binding, and maybe it will be helpful for other people approaching the problem. Note this is not an accepted answer, I still hope someone somewhere, perhaps in the future if and when Qt will be updated, could come up with a far more elegant solution.
Main disadvantages: longer and verbose code, one has to use setters everywhere and (try not forget)
The property system doesn't do anything automatically in the end, just states wich properties are exposed and wich are their getters/setters and change signals
c++
class InterfaceController : public QObject
{
Q_OBJECT
Q_PROPERTY(quint8 current_view READ getCurrentView WRITE setCurrentView NOTIFY sCurrentViewChanged)
Q_PROPERTY(quint8 future_view READ getFutureView WRITE setFutureView NOTIFY sFutureViewChanged)
public:
explicit InterfaceController(QObject *parent = 0);
//getters
quint8 getCurrentView() { return this->m_current_view; }
quint8 getFutureView() { return this->m_future_view; }
//setters
Q_INVOKABLE void setCurrentView(quint8 current_view) { if(this->m_current_view!=current_view) {this->m_current_view=current_view; emit sCurrentViewChanged(this->m_current_view);} }
Q_INVOKABLE void setFutureView(quint8 future_view) { if(this->m_future_view!=future_view) {this->m_future_view=future_view; emit sFutureViewChanged(this->m_future_view);} }
private:
quint8 m_current_view;
quint8 m_future_view;
QByteArray m_selected_language;
public slots:
void onLanguageSelected(QByteArray language);
private slots:
signals:
void sCurrentViewChanged(quint8 current_view);
void sFutureViewChanged(quint8 future_view);
};
InterfaceController::InterfaceController(QObject *parent) : QObject(parent)
{
this->m_current_view=1;
this->m_future_view=1;
}
void InterfaceController::onLanguageSelected(QByteArray language)
{
this->m_selected_language=language;
this->setFutureView(2);
}
QML
id: root
property int current_view: 1
property int future_view: 1
Connections {
target: root
onCurrent_viewChanged: { backend.setCurrentView(current_view); }
onFuture_viewChanged: { backend.setFutureView(future_view); }
}
Connections {
target: backend
onSCurrentViewChanged: { if(root.current_view!=current_view) {root.current_view=current_view;} }
onSFutureViewChanged: { if(root.future_view!=future_view) {root.future_view=future_view;} }
}

Related

QML pointer property change signal not propagated to binding properties

I exposed a pointer variable to qml like this:
Fruit:
class Fruit : public QObject
{
Q_OBJECT
Q_PROPERTY(int qualityGrade READ qualityGrade WRITE setQualityGrade NOTIFY qualityGradeChanged)
Q_PROPERTY(bool organic READ organic WRITE setOrganic NOTIFY organicChanged)
public:
int qualityGrade() const
{
return m_qualityGrade;
}
bool organic() const
{
return m_organic;
}
public slots:
void setQualityGrade(int qualityGrade)
{
if (m_qualityGrade == qualityGrade)
return;
m_qualityGrade = qualityGrade;
emit qualityGradeChanged(m_qualityGrade);
}
void setOrganic(bool organic)
{
if (m_organic == organic)
return;
m_organic = organic;
emit organicChanged(m_organic);
}
signals:
void qualityGradeChanged(int qualityGrade);
void organicChanged(bool organic);
private:
int m_qualityGrade = -1;
bool m_organic = false;
};
MyClass.h:
class MyClass : public QObject
{
Q_OBJECT
Q_PROPERTY(Fruit* featuredFruit READ featuredFruit WRITE setFeaturedFruit NOTIFY featuredFruitChanged)
public:
explicit MyClass(QObject *parent = nullptr);
~MyClass();
Fruit* featuredFruit() const
{
return m_featuredFruit;
}
public slots:
void setFeaturedFruit(Fruit* featuredFruit)
{
if (m_featuredFruit == featuredFruit)
return;
m_featuredFruit = featuredFruit;
emit featuredFruitChanged(m_featuredFruit);
}
signals:
void featuredFruitChanged(Fruit* featuredFruit);
private:
Fruit* m_featuredFruit = nullptr;
};
MyClass.cpp:
MyClass::MyClass(QObject *parent) : QObject(parent)
{
m_featuredFruit = new Fruit();
m_featuredFruit->setQualityGrade(2);
QTimer *timer = new QTimer();
connect(timer, &QTimer::timeout, this, [=]() {
//m_featuredFruit->deleteLater(); //<--- activating these two lines causes to force working featuredFruitChanged signal
//m_featuredFruit = new Fruit();
m_featuredFruit->setQualityGrade(5);
emit featuredFruitChanged(m_featuredFruit);
delete timer;
});
timer->start(5000);
}
MyClass::~MyClass()
{
m_featuredFruit->deleteLater();
m_featuredFruit = nullptr;
}
and I used it in QML as follow:
MyClass {
id: classObj
onFeaturedFruitChanged: console.log("original property shows an change");//<--- called as expected
}
Item {
property Fruit selectedFruit: classObj.featuredFruit //<--- binding qml defined property to C++ property
onSelectedFruitChanged: {
console.log("binded property recieved change signal");//<--- not called after changes!!!
alertAnimation.restart(); //<--- an example of usage
}
}
The problem is whenever I emit featuredFruitChanged, the binding qml property does not received change signal.
What is wrong?! Is this a Qt Framework bug? Any suggestion?
Also I tried overloading equality operator in C++ without success
Update:
OK, I add some more precisions to my sample code in order to reproduce problem easier.
A typo in my sample code fixed (thanks #ihor-drachuk). The problem exist yet.
Because you misspelled featuredFruit and wrote featuedFruit. Fix it and it will work.
Update: It should work if call setFeaturedFruit or if change it from QML. It will not work as you expect if change some property of featuredFruit even if it is selected
Update 2: QML call onChanged if changed value, value is pointer to object. So if pointer changed - then onChanged will be called. If something behind pointer changed - no.
But you can handle it with help of Connections:
Connections {
target: classObj.featuredFruit
onTargetChanged: console.warn("Target changed!");
onQualityGradeChanged: console.warn("onQualityGradeChanged!");
onOrganicChanged: console.warn("onOrganicChanged!");
}
Also you can add to Fruit some special signal like somethingChangedInMe and emit it in each setter. So, you can write in Connections just this signal handler:
Connections {
target: classObj.featuredFruit
onTargetChanged: console.warn("Target changed!");
onSomethingChangedInMe: console.warn("onSomethingChangedInMe!");
}

Q_PROPERTY with private setter

I have an QObject with properties accessible from QML.
Something like:
Class C : public QObject {
Q_OBJECT
public:
explicit C(QObject * parent = nullptr);
Q_PROPERTY(QString ro_text READ ro_text WRITE setRo_text NOTIFY ro_textChanged)
};
Is it possible to make the setter(setRo_text) "private", so the property cannot by modified from QML, but can still be set from C++ code(inside the class)?
if you don't want it to be modified from QML then don't declare the WRITE, and create a method that every time the property changes it emits the signal, the setter method can be public or private but it can't be accessed in QML
class C: public QObject{
Q_OBJECT
Q_PROPERTY(QString ro_text READ ro_text NOTIFY ro_textChanged)
public:
C(QObject *parent=nullptr): QObject(parent){
}
QString ro_text() const {
return m_ro_text;
}
Q_SIGNALS:
void ro_textChanged();
private:
void setRo_text(const QString & text){
if(m_ro_text == text)
return;
m_ro_text = text;
Q_EMIT ro_textChanged();
}
QString m_ro_text;
};

connect signal (Qstring) whit slot (String) of 2 different class

i have 2 class : Class MaFentre and Code
code.h :
class Code : public QObject {
public :
explicit Code(Q3DScatter *scatter);
public slots:
std::vector<point> readingData(std::string inputFileName);
}
MaFenetre.h :
class MaFenetre : public QWidget
{ Q_OBJECT
public:
MaFenetre();
private:
QLineEdit *entry1;
}
Code.cpp :
std::vector<point> Code::readingData(std::string inputFileName){
// i read a file here
}
i created the Code class object in the constructor of the class MaFenetre
Code *modifier = new Code(graph);
for making connection between slot and signal
QObject::connect(entry1, SIGNAL(textChanged(QString)),modifier, SLOT(readingDara(std::string inputFileName)))
i know the parameters must be of the same type , for that i try to code :
QObject::connect(entry, SIGNAL(textChanged(QString.toStdString)),modifier, SLOT(readingDara(std::string inputFileName)))
but it doesnt work
Your signal and slot arguments are not compatible.
You can do this workaround with the lambda function
Code *modifier = new Code();
MaFenetre * poMaFenetre = new MaFenetre();
connect(poMaFenetre->Entry(), &QLineEdit::textChanged,
[modifier](const QString & oText)
{
std::vector<int> data = modifier->readingData(oText.toStdString());
// Handle data here...
});
In the MaFenetre
class MaFenetre : public QWidget
{
Q_OBJECT
public:
MaFenetre() {entry1.reset(new QLineEdit());}
QLineEdit *Entry() {return entry1.data();}
private:
QScopedPointer<QLineEdit> entry1;
};
Using signals and slots it's not the same as calling function and pass parameters.
At first signal and slot must have same parameters type, means they must be defined with same parameters. In your case you have to change your slot to fit possible signals. Also note that returned value is useless in case of slot invoking, so better way is to keep you reading function as is, move it to private area, and create wrapper slot:
void Code::readingDataSlot(QString inputFileName)
{
std::vector<point> result = readingData( inputFileName.toStdString() );
// Do what ever you need with result vector
}
and connect it to signal.
connect(entry1, SIGNAL(textChanged(QString)),modifier, SLOT(readingDataSlot(QString)));

Qt QML C++ Plugin Singleton

Is it possible to make MyObject be always equal (one same instance) in all it's qml definitions?
C++:
class MyObject : public QObject {
Q_OBJECT
Q_DISABLE_COPY(MyObject)
Q_PROPERTY(QString test READ test NOTIFY testChanged)
public:
explicit MyObject(QObject *parent = 0);
signals:
void testChanged();
private:
QString test() const {
return _test;
}
QString _test;
};
QML:
Item {
MyObject { id: myObject1 }
MyObject { id: myObject2 }
}
I want myObject1 to be equal myObject2. Some kind of singleton (but no qmlRegisterSingletonType)
I can interpret your question as if you want more than one entry of MyObject in QML code referring to the same C++ object. You also know what singleton is. How about the wrapper over the singleton that you can use with QML like:
class MyObject : public QObject {
Q_OBJECT
Q_DISABLE_COPY(MyObject)
Q_PROPERTY(QString test READ test NOTIFY testChanged)
public:
explicit MyObject(QObject *parent = 0);
signals:
void testChanged();
private:
QString test() const {
return MySingleton::instance().test();
}
// QString _test; // this supposed to be implemented in MySingleton
};
Or I in my application for many different types of communication between C++ and QML use some kind of MessageBoard from the article Exposing Attributes of C++ Types to QML. That one is even more convenient considering many uses.

How would I connect this signal to the slot

I would like to create a signal in my main class foo so that a static method in a different class could emit it.I just started of with QT so I am a bit confused. I currently have the following code
class Foo : public QMainWindow
{
Q_OBJECT
public:
Foo(QWidget *parent = 0, Qt::WFlags flags = 0);
~Foo();
signals:
void UpdateSignal(int val);
private slots:
void MySlot(int val);
};
Foo::Foo(QWidget *parent, Qt::WFlags flags): QMainWindow(parent, flags)
{
//How do I connect Bfoo::somemethod() here. I know its suppose to be like
connect(xx,SIGNAL(UpdateSignal(int)),this, SLOT(MySlot(int)));
ui.setupUi(this);
}
void Foo::MySlot(int val)
{
//Do something..
}
Now I have this class
Class Bfoo
{
static void somemethod()
{
emit UpdateSignal(12);
}
}
Any suggestions on how the static somemethod() could emit the UpdateSignal
When you emit signal it is necessary to know which object is emitting it. This is because signals are not implemented to be messages between different classes but messages between instances of (possibly different) classes.
Secondly, signals are protected methods. They are not accessible for external users. What you can do is define public method in Foo which will do the emission:
void Foo:EmitUpdateSignal(int x) {
emit UpdateSignal(x);
}
And then in your Bfoo::somemethod() you need to pass object which will emit signal:
void BFoo::somemethod(Foo &f) {
f.EmitUpdateSignal(12);
}
However, notice what you are doing. You emit signal which is connected to the slot in the same instance. This suggests design flaw but I cannot give any hints without more details about what are you going to achieve.