Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. Strange behavior of onParentChanged when dynamic QML-Object gets destroyed
Forum Updated to NodeBB v4.3 + New Features

Strange behavior of onParentChanged when dynamic QML-Object gets destroyed

Scheduled Pinned Locked Moved QML and Qt Quick
9 Posts 2 Posters 2.6k Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • K Offline
    K Offline
    Kieren
    wrote on last edited by JKSH
    #1

    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

    1 Reply Last reply
    0
    • ? Offline
      ? Offline
      A Former User
      wrote on last edited by
      #2

      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
      @

      1 Reply Last reply
      0
      • ? Offline
        ? Offline
        A Former User
        wrote on last edited by
        #3

        Oops, should have been:

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

        :-)

        1 Reply Last reply
        0
        • K Offline
          K Offline
          Kieren
          wrote on last edited by
          #4

          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

          1 Reply Last reply
          0
          • ? Offline
            ? Offline
            A Former User
            wrote on last edited by
            #5

            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ß!

            1 Reply Last reply
            0
            • K Offline
              K Offline
              Kieren
              wrote on last edited by
              #6

              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

              1 Reply Last reply
              0
              • ? Offline
                ? Offline
                A Former User
                wrote on last edited by
                #7

                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;
                @

                1 Reply Last reply
                0
                • K Offline
                  K Offline
                  Kieren
                  wrote on last edited by
                  #8

                  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

                  1 Reply Last reply
                  0
                  • ? Offline
                    ? Offline
                    A Former User
                    wrote on last edited by
                    #9

                    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

                    1 Reply Last reply
                    0

                    • Login

                    • Login or register to search.
                    • First post
                      Last post
                    0
                    • Categories
                    • Recent
                    • Tags
                    • Popular
                    • Users
                    • Groups
                    • Search
                    • Get Qt Extensions
                    • Unsolved