Strange behavior of onParentChanged when dynamic QML-Object gets destroyed



  • Hi,

    I'm working on an an Project where it is necessary to create dynamic QML components as they are requested by the user. Also these dynamic components can be moved within the GUI in a way that the parent item is reset.

    To react to this without any QML warning I tried to use
    @property: parent != null ? parent.xy : defaultValue@
    To set default values as long as there is no parent and read new values if the parent changes.

    To make the code better readable and also to do additional stuff if the parent changes I tried to set a flag within the onParentChanged method, but this leads to a strange behavior as soon as the QML object tree is destroyed.

    A minimum working example looks like this
    (Qt5.4 VS2013)
    @
    //main.cpp
    #include <memory>

    #include <QGuiApplication>
    #include <QQuickView>
    #include <QQmlEngine>
    #include <QQuickItem>
    #include <QQmlComponent>
    #include <QObject>

    int main(int argc, char **argv)
    {
    QGuiApplication app(argc, argv);

    QQmlEngine* pQmlEngine = new QQmlEngine(&app);

    std::shared_ptr<QQuickView> pQuickView;
    pQuickView = std::make_shared<QQuickView>(pQmlEngine, nullptr);

    pQuickView->setSource(QUrl("qml/wrapper.qml"));
    pQuickView->setResizeMode(QQuickView::SizeRootObjectToView);
    pQuickView->show();

    QQmlComponent Component(pQmlEngine);
    Component.loadUrl(QUrl("qml/dynamic.qml"));

    if (Component.isError()) {
    qDebug() << Component.errorString();
    return -1;
    }
    QObject* pNewComponent = Component.create(pQmlEngine->rootContext());

    if (Component.isError()) {
    qDebug() << Component.errorString();
    return -1;
    }

    QQuickItem* pDynamicObject = qobject_cast<QQuickItem*>(pNewComponent);

    QQuickItem* pRootObject = pQuickView->rootObject();
    pDynamicObject->setParent(pRootObject);
    pDynamicObject->setParentItem(pRootObject);

    QObject::connect(pQuickView.get(), &QQuickView::destroyed, {qDebug() << "QQuickView destroyed" ; });
    QObject::connect(pRootObject, &QQuickView::destroyed, {qDebug() << "RootObject destroyed"; });
    QObject::connect(pDynamicObject, &QQuickView::destroyed, {qDebug() << "DynamicObject destroyed"; });

    return app.exec();
    }
    @

    @
    // qml/wrapper.qml

    import QtQuick 2.4
    Rectangle {
    width: 400
    height: 400

    color: "grey"
    }
    @

    @
    // qml/dynamic.qml

    import QtQuick 2.4
    Rectangle {
    objectName: "ProblemRect"

    color: "red"
    width: 50
    height: validParent ? parent.height*0.5 : 50

    property bool validParent: false

    onParentChanged: {
    console.log("this:" + this);

    if(parent != null) {
      validParent = true;
    } else {
      validParent = false;
    }
    
    console.log("parent: " + parent); 
    

    } //onParentChanged

    onValidParentChanged: {
    console.log("validParent: " + validParent)
    } // onValidParentChanged

    MouseArea {
    anchors.fill: parent
    drag.target: parent
    drag.axis: Drag.YAxis

    drag.minimumY: 0
    drag.maximumY: validParent ? (parent.height - height) : 25
    

    } //MouseArea

    } //Rectangle
    @

    If I run this code an close the QuickView the following error is reported:
    "qml/dynamic.qml:16: TypeError: Cannot read property of null"

    As you can see from the debug output there seams to be a problem that in the "onParentChanged" method the "this" object is already kind of dead as it looks like

    Can some one point me in the direction what I'm doing wrong?

    Thanks a lot,
    Cheers
    Kieren

    Additional things that happened:
    height and drag.maximumY properties are not bind as expected

    drag.maximumY: seams not to be set at all, at least the 25 are not used
    The ternary operators in bindings (e.g. height) are sometimes only evaluated once, and so the parent is excessed even if it is set to null


  • Moderators

    Hi Kieren!

    This works for me:

    In main.cpp:

    @
    pDynamicObject->setParentItem( pQuickView.contentItem() );
    pDynamicObject->setParent( pQuickView.contentItem() );
    @

    And dynamic.qml (lines 9, 36):

    @
    import QtQuick 2.4
    Rectangle {
    objectName: "ProblemRect"

    color: "red"
    width: 50
    height: validParent ? parent.height*0.5 : 50

    property real parentHeight: parent.height

    property bool validParent: false

    onParentChanged: {
    console.log("this:" + this);

    if(parent != null) {
      validParent = true;
    } else {
      validParent = false;
    }
    
    console.log("parent: " + parent);
    

    } //onParentChanged

    onValidParentChanged: {
    console.log("validParent: " + validParent)
    } // onValidParentChanged

    MouseArea {
    anchors.fill: parent
    drag.target: parent
    drag.axis: Drag.YAxis

    drag.minimumY: 0
    drag.maximumY: validParent ? (parentHeight - height) : 25
    

    } //MouseArea

    } //Rectangle
    @


  • Moderators

    Oops, should have been:

    @
    property real parentHeight: validParent ? parent.height : 0
    @

    :-)



  • Hi,
    thank you for the answer.
    At first this looks like this might reduce the problem
    but the thing is my problem looks more like this:

    @
    // qml/wrapper.qml
    import QtQuick 2.4

    Rectangle {
    width: 400
    height: 400
    color: "grey"

    Rectangle {
    objectName: "wrapper"
    width: 200
    height: 200
    anchors.centerIn: parent
    color: "blue"
    }
    }
    @

    The actual parent is not the root object but something nested even several layers into the QML hierarchy. So it looks more like:
    @
    QQuickItem* pParent = pQuickView->rootObject()->findChild<QQuickItem*>("wrapper");
    @
    Using contentItem() does not work here for me.
    For me it looks like there is an issue with the destruction order of the QObjects using the "parent" property.

    I also can't see why an extra property(parentHeight) is needed to have the intended behavior on QML side. The bindings should be reevaluated if any of the accessed properties changes. At least this is promised in some documentations.

    To me it is very important to real understand what the issue is, not just having a work around.

    Thank you again anyway.
    Cheers
    Kieren


  • Moderators

    Hi!

    bq. For me it looks like there is an issue with the destruction order.

    Yes, to me, too, but honestly I don't believe that QQuick is broken in this place because if it really was, then this would cause problems all over the place. Could be, but don't believe it yet. This needs more investigation.

    bq. I also can’t see why an extra property(parentHeight) is needed

    If I understand correctly what you're doing, then your QML code is wrong:
    @
    drag.maximumY: validParent ? (parent.height - height) : 25
    @
    parent.height is the height of your rectangle in dynamic.qml and height is the height of your MouseArea which is...
    @
    anchors.fill: parent
    @
    the same as the height of your rectangle. Thus drag,maximumY evaluates to zero when validParent is true.

    I used parentHeight as property of your rectangle so I could access the rectangle's parent.

    Gruß!



  • Hi again,

    [quote author="Wieland" date="1424695189"]
    parent.height is the height of your rectangle in dynamic.qml and height is the height of your MouseArea which is...
    [/quote]
    Thanks for pointing this out.
    I updated the code:
    @
    // qml/dynamic.qml
    import QtQuick 2.4
    Rectangle {
    id: problemRect
    objectName: "ProblemRect"

    color: "red"
    width: 50
    height: validParent ? parent.height*0.5 : 50

    property bool validParent: false

    onParentChanged: {
    console.log("this:" + this);

    if(parent != null) {
      validParent = true;
    } else {
      validParent = false;
    }
    
    console.log("parent: " + parent); 
    

    } //onParentChanged

    onValidParentChanged: {
    console.log("validParent: " + validParent)
    } // onValidParentChanged

    MouseArea {
    anchors.fill: parent
    drag.target: parent
    drag.axis: Drag.YAxis

    drag.minimumY: 0
    drag.maximumY: validParent ? (problemRect.parent.height - problemRect.height) : 25
    

    } //MouseArea
    } //Rectangle
    @

    Now the problem with the binding gets even more obvious as line 36 now gives the error:
    "dynamic.qml:36: TypeError: Cannot read property of null"
    on destruction.

    To me it looks like the ternary operator:
    @
    drag.maximumY: validParent ? parent.Y : 123
    @

    Is only evaluated once and than the binding looks like
    @
    drag.maximumY: parent.Y
    @

    or
    @
    drag.maximumY: 123
    @

    And the flag that should prevent the access of a non excising parent is simply ignored.

    To me this is all a bit fishy. Also I think my problem with having a changing parent(even no parent) shouldn't be this special that no solutions to this problem are around, but I can't still find any :/

    Thanks again (Danke mal wieder :D)
    Cheers
    Kieren


  • Moderators

    Think I've found it. It's the garbage collector. Look at this:

    @
    pDynamicObject->setParentItem(pRootObject);

    QQmlEngine::setObjectOwnership( pNewComponent, QQmlEngine::CppOwnership );

    QObject::connect(pQuickView.get(), &QQuickView::destroyed, {qDebug() << "QQuickView destroyed" ; });
    QObject::connect(pRootObject, &QQuickView::destroyed, {qDebug() << "RootObject destroyed"; });
    QObject::connect(pDynamicObject, &QQuickView::destroyed, {qDebug() << "DynamicObject destroyed"; });

    const int res = app.exec();

    delete pDynamicObject;

    return res;
    @



  • Hi,
    thanks again for looking into this.

    This does work but is not what was intended.
    @pDynamicObject->setParent(pRootObject);@
    I thought by setting the QObject parent of the dynamic object, Qt would clean it up properly.
    Doing the cleaning all by my self is not very useful, as this is only part of a big software project.
    The plan was that the QML objects take care of them selfs as they are connected via the parent-property of Qt. And if I got this right. A parent should take care of the destruction of all his children?

    It seams like I got something wrong with how Qt works or there must be just a bug in the few lines of code :/

    Cheers
    Kieren


  • Moderators

    bq. I thought by setting the QObject parent of the dynamic object, Qt would clean it up properly.

    Yes, should. I don't understand why this is messed up. Maybe it is a bug in Qt. I give up on this for now.

    My final quick fix (Quick, haha...) :

    @
    onParentChanged: {
    console.log("this:" + this);

     if (!hasOwnProperty("parent")) {
         return
     } 
    
    if(parent != null) {
      validParent = true;
    } else {
      validParent = false;
    }
    
    console.log("parent: " + parent);
    

    } //onParentChanged
    @

    Cheers


Log in to reply
 

Looks like your connection to Qt Forum was lost, please wait while we try to reconnect.