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. Dynamically created item destroyed with original parent despite reparenting
Forum Updated to NodeBB v4.3 + New Features

Dynamically created item destroyed with original parent despite reparenting

Scheduled Pinned Locked Moved Unsolved QML and Qt Quick
7 Posts 2 Posters 3.6k Views
  • 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
    kloffy
    wrote on last edited by kloffy
    #1

    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...

    1 Reply Last reply
    0
    • K Offline
      K Offline
      kloffy
      wrote on last edited by kloffy
      #2

      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 }
      	}
      }
      
      1 Reply Last reply
      0
      • S Offline
        S Offline
        siamak.rahimi.motem
        wrote on last edited by siamak.rahimi.motem
        #3

        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.

        K 1 Reply Last reply
        0
        • S siamak.rahimi.motem

          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.

          K Offline
          K Offline
          kloffy
          wrote on last edited by
          #4

          @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
          
          S 1 Reply Last reply
          0
          • K kloffy

            @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
            
            S Offline
            S Offline
            siamak.rahimi.motem
            wrote on last edited by siamak.rahimi.motem
            #5
            @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 )

            K 1 Reply Last reply
            0
            • S siamak.rahimi.motem
              @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 )

              K Offline
              K Offline
              kloffy
              wrote on last edited by
              #6

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

              K 1 Reply Last reply
              0
              • K kloffy

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

                K Offline
                K Offline
                kloffy
                wrote on last edited by
                #7

                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)
                
                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