QML pointer property change signal not propagated to binding properties

796 views Asked by At

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.

2

There are 2 answers

0
S.M.Mousavi On BEST ANSWER

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();
    }
}
8
Ihor Drachuk On

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!");
}