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
KierenAdditional things that happened:
height and drag.maximumY properties are not bind as expecteddrag.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 -
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 : 50property 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)
} // onValidParentChangedMouseArea {
anchors.fill: parent
drag.target: parent
drag.axis: Drag.YAxisdrag.minimumY: 0 drag.maximumY: validParent ? (parentHeight - height) : 25
} //MouseArea
} //Rectangle
@ -
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.4Rectangle {
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 -
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 : 50property 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)
} // onValidParentChangedMouseArea {
anchors.fill: parent
drag.target: parent
drag.axis: Drag.YAxisdrag.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 -
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 -
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