c++qtqmlqtquick2qproperty

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.

Update 2023:

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


Solution

  • 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();
        }
    }