qtqmlqtquick2qt-signalsqqmllistproperty

How to detect dataChanged() and resourcesChanged() from QML


The codes below:

Item{
    onDataChanged: console.log("Data changed")
}

Item{
    onResourcesChanged: console.log("Resources changed")
}

throw Cannot assign to non-existent property "onDataChanged" and Cannot assign to non-existent property "onResourcesChanged" respectively.

This is not the case with the childrenChanged() signal. The reason for this is that in qtdeclarative/src/quick/items/qquickitem.h, children property is declared with:

Q_PRIVATE_PROPERTY(QQuickItem::d_func(), QQmlListProperty<QQuickItem> children READ children NOTIFY childrenChanged DESIGNABLE false)

but this is not the case for data or resources. They are declared with:

Q_PRIVATE_PROPERTY(QQuickItem::d_func(), QQmlListProperty<QObject> data READ data DESIGNABLE false)
Q_PRIVATE_PROPERTY(QQuickItem::d_func(), QQmlListProperty<QObject> resources READ resources DESIGNABLE false)

with no changed() signal. Why is this design choice to particularly hide the change on non-visible children made? Moreover, how can the change on data be detected from QML?


Solution

  • Why do you need this ?

    One possible workaround is to listen for child events. I wrote a quick attached type PoC :

    ChildListener.h :

    #ifndef CHILDLISTENER_H
    #define CHILDLISTENER_H
    
    #include <QObject>
    #include <QtQml>
    
    class ChildListener : public QObject {
        Q_OBJECT
    
    public:
        ChildListener(QObject *object) : QObject(object) {
            if (object)
                object->installEventFilter(this);
        }
    
        static ChildListener *qmlAttachedProperties(QObject *object) {
            return new ChildListener(object);
        }
    
    signals:
        void childAdded(QObject* child);
        void childRemoved(QObject* child);
    
    protected:
          bool eventFilter(QObject *obj, QEvent *event) override {
              Q_UNUSED(obj)
              if (QChildEvent *childEvent = dynamic_cast<QChildEvent*>(event)) {
                  if (childEvent->added())
                      emit childAdded(childEvent->child());
                  if (childEvent->removed())
                      emit childRemoved(childEvent->child());
              }
              return false;
          }
    };
    
    QML_DECLARE_TYPEINFO(ChildListener, QML_HAS_ATTACHED_PROPERTIES)
    
    #endif // CHILDLISTENER_H
    

    Register it with qmlRegisterUncreatableType<ChildListener>("fr.grecko.ChildListener", 1, 0, "ChildListener", "ChildListener can only be accessed as an attached type."); and you can now use it like so :

    import fr.grecko.ChildListener 1.0
    /* ... */
    Timer {
        id: foo
        objectName: "My name is foo"
    }
    Item {
        ChildListener.onChildAdded: print("child added : ", child)
        data: [foo];
    }
    

    This outputs : qml: child added : QQmlTimer(0x7ffe22f538e0, "My name is foo") in the console