Dynamically created item destroyed with original parent despite reparenting
-
The title is a bit of a mouth full, but the basic problem is simple. In my application, I have items that can be dragged and dropped. Whenever an item is dragged for the first time, it creates a copy of itself. Thus you have an infinite supply of draggable items. After some head scratching, I thought I had come up with a nice solution:
import QtQuick 2.5 import QtQuick.Window 2.1 Window { id: window width: 1280; height: 960 visible: true Component { id: component Item { id: replicant width: 64; height: 64 property var prototype: true signal replicate(var parent); signal drag(var item); signal drop(var item); Rectangle { anchors.fill: parent color: "green" } MouseArea { anchors.fill: parent drag.target: parent onPressed: { if (prototype) { prototype = false; replicate(replicant); } replicant.drag(replicant); } onReleased: { replicant.drop(replicant); } } } } Item { id: root anchors.fill: parent Item { id: replicator anchors.fill: parent function replicated(parent) { var replicant = component.createObject(replicator, {}); replicant.replicate.connect(replicated); replicant.drag.connect(dragged); replicant.drop.connect(dropped); } function dragged(item) { console.log("Drag"); item.parent = container; } function dropped(item) { console.log("Drop"); } Component.onCompleted: { replicated(null); } } Item { id: container anchors.fill: parent } Rectangle { width: 64; height: 64 anchors.right: parent.right color: "red" MouseArea { anchors.fill: parent onClicked: { replicator.destroy(); } } } } }
The only problem is: The replicator may be destroyed at an arbitrary point (in this case triggered by the red rectangle, but in the real application this is out of my control). Whenever that happens, all the items that have been created in this manner get destroyed, despite already having been reparented to a different item. Is there any way to salvage this solution? It works perfectly otherwise...
-
Here is an even more simplified version. The program does not make much sense at this point, but it serves to demonstrate the (un-)desired behavior.
import QtQuick 2.5 import QtQuick.Window 2.1 Window { id: window width: 1280; height: 960 visible: true Component { id: component Rectangle { width: 64; height:64 color: "blue" } } Item { id: root anchors.fill: parent Rectangle { anchors.right: parent.right width: 64; height:64 color: "red" MouseArea { anchors.fill: parent property var target: null onPressed: { if (oldParent) { // The blue rectangle is initially created as a child of oldParent. target = component.createObject(oldParent, {"x": 0, "y": 0}); } } onReleased: { if (oldParent) { // How can I transfer ownership to newParent? // The blue rectangle should stay alive as a child of newParent. // (It appears the following line is not sufficient!) target.parent = newParent; oldParent.destroy(); } } } } Item { id: oldParent; anchors.fill: parent } Item { id: newParent; anchors.fill: parent } } }
-
As the documentation says: http://doc.qt.io/qt-5/qml-qtqml-component.html
Dynamically created instances can be deleted with the destroy() method. See Dynamic QML Object Creation from JavaScript for more information.
So I tested you code by commenting oldParent.destroy(); and it worked just fine.
You can set the opacity of oldParent to 0.5 or change the color of the rectangle to be able to see a change while mouse is pressed and released. -
@siamak.rahimi.motem Thank you for your response, but my concern is less the dynamic creation/destruction of the parent, since like I said in my original post, that is out of my control. I merely want the dynamically created children to stay alive, once they have been reparented.
A message by Elvis Stansvik from the mailing list shed some light on this:
What I found in http://doc.qt.io/qt-5/qml-qtquick-item.html#parent-prop is that
"Note: The concept of the visual parent differs from that of the
QObject parent. An item's visual parent may not necessarily be the
same as its object parent. See Concepts - Visual Parent in Qt Quick
for more details."which is why Peter's assignment to parent doesn't cause it to be
reparented in the QObject sense.Looking at http://doc.qt.io/qt-5/qtquick-visualcanvas-visualparent.html
one can read:"Any object assigned to an item's data property becomes a child of the
item within its QObject hierarchy, for memory management purposes.
Additionally, if an object added to the data property is of the Item
type, it is also assigned to the Item::children property and becomes a
child of the item within the visual scene hierarchy."So Peter, if you just change your:
target.parent = newParent;
to
newParent.data.push(target);
Will that work? I think that should re-parent it in both the QObject
and visual scene hierarchy.
Unfortunately, the proposed solution does not seem to work for me, as I get a TypeError.
TypeError: Property 'push' of object [object Object] is not a function
-
@kloffy said: I merely want the dynamically created children to stay alive, once they have been reparented.
Welcome @kloffy , I have done this (changing parent of an item)several times and you are now doing it right, but because of the destroy function calling on a non-component Item ( I mean the oldparrent) it was not working. if you comment it . It works.
What do you want that cannot be achieved with this solution ? ( by solution, I mean simply commenting the destroy() line ) -
@siamak.rahimi.motem The problem is that in my real application "destroy" is called on the old parent and there is nothing I can do about it. For example, you can imagine the old parent being an item inside a ListView. Initially, the child should behave like a normal ListView item, but once it is dragged, it should become an independent item in the scene.
-
Here is my current workaround for reparenting a QQuickItem using PyQt5:
class Utilities(Singleton): @pyqtSlot(QtQuick.QQuickItem, QtQuick.QQuickItem) def reparent(self, item, parent): # Sets the visual parent item.setParentItem(parent) # Sets the QObject parent item.setParent(parent) QtQml.qmlRegisterSingletonType(Utilities, "Utilities", 1, 0, "Utilities", Utilities.Instance)
Here's the usage in QML:
Utilities.reparent(target, newParent); oldParent.destroy();
It should be trivial to translate into C++. How you chose to manage your singletons is an implementation detail, here is my quick and dirty solution:
def lazy_setdefaultattr(obj, key, default): try: result = getattr(obj, key) except AttributeError: result = default() setattr(obj, key, result) finally: return result class Singleton(QtCore.QObject): @classmethod def Instance(cls, *args, **kwargs): return lazy_setdefaultattr(cls, '_instance', cls)