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)

Log in to reply
 

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