0

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.

Update 2023:

This issue is still here at Qt 5.15.8. Anyway, I found a workaround that I stated it as an answer.

S.M.Mousavi
  • 5,013
  • 7
  • 44
  • 59
  • How do you expect that should work? As I know `on*Changed` properties for objects work on assigning/reassigning but not on inner state changing. You should bind a handler to a specified property to be notifies when it changed, not to the whole object. – folibis Jul 26 '20 at 07:47
  • @folibis No! `on*Changed` works just when related signal is emitted not when you assign value (as documented in Qt Doc). `Fruit` is also a property so emit its change signal must notify qml about change. This works as expected. But the problem is that, change signal not propagated to the second qml property (which bound to first property). [C++: emit change]=====>[QML: ok, changed]=====>[QML second property: nothing happens! :| ] – S.M.Mousavi Jul 26 '20 at 08:51

2 Answers2

1

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!");
}
Ihor Drachuk
  • 1,265
  • 7
  • 17
  • oh! excuse me, this is just a sample code and I corrected my question. Also I checked naming in the real project and no problem has been found. actually if I check value of the `selectedFruit` in `onFeaturedFruitChanged` handler, data is updated but no signal is propagated to the qml property named `selectedFruit`. (as it is pointer) – S.M.Mousavi Jul 26 '20 at 20:41
  • Hmm, strange... actually, I've tried and if fix typo - it really worked as expected, all signals and handlers were called. But I don't see full source, maybe I reproduced the problem incorrectly. 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` – Ihor Drachuk Jul 26 '20 at 21:18
  • I updated question and typo fixed. In general, changing value of a property with emitting change signal will works. But in this case bound property not notified about change. – S.M.Mousavi Jul 27 '20 at 05:54
  • Probably, because it didn't actually changed. You're emitting the same value – Ihor Drachuk Jul 27 '20 at 08:42
  • Here is example with your source, it works fine: https://file.io/Jw7O2XASCzl3, does it also work fine on your PC? – Ihor Drachuk Jul 27 '20 at 08:47
  • link is corrupted (404 error). I think Qt internal behaviour is so that prevents emitting changes for same object. even if internal of that object is entirely changed :/ So I tried to overloading `operator==()` to control that behaviour but no success :/ – S.M.Mousavi Jul 27 '20 at 13:35
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/218685/discussion-between-ihor-drachuk-and-s-m-mousavi). – Ihor Drachuk Jul 27 '20 at 13:56
  • I've edited an answer. Also I've uploaded sources to another resource, hope they will be downloadable https://fex.net/s/sske7xz – Ihor Drachuk Jul 27 '20 at 14:14
0

I found a workaround. In the QML codes, change type of the property named selectedFruit to the var.
This causes the emitted signal to be propagated through proxied property.

MyClass {
    id: classObj
    onFeaturedFruitChanged: console.log("original property shows an change");  //<--- called as expected
}

Item {

    //        ╭── USE `var` INSTEAD. THIS IS A WORKAROUND.  
    property var selectedFruit: classObj.featuredFruit
    
    onSelectedFruitChanged: {
        console.log("binded property recieved change signal");  //<--- will be called as expected :)
        alertAnimation.restart();
    }
}
S.M.Mousavi
  • 5,013
  • 7
  • 44
  • 59