Search code examples
pythonqtpyqtparentqgraphicsitem

Removing a QGraphicsItem's parent


I have a QGraphicsItem called child_item that is a child of another QGraphicsItem called parent_item. I need to unparent child_item from parent_item so that child_item has no parent.

But when I try childItem.setItemParent(None) my script crashes.

Apparently this is because when you remove a QGraphicsItem's parent in this way the item is returned to Qt...

For now I've just created a global_parent QGraphicsItem so that if any item needs to be unparented I will simply parent it under the global_parent and if a QGraphicsItem has the parent global_parent my code will act like it doesn't have a parent but I would like a better solution.

Any ideas please?

Some of my code:

POINT_SIZE = 7
class Test_Box(QGraphicsItem):
    # Constants
    WIDTH           = 50 * POINT_SIZE
    HEIGHT          = 13 * POINT_SIZE
    RECT            = QRectF(0, 0, WIDTH, HEIGHT)
    CORNER_RADIUS   = 1.5 * POINT_SIZE


    def __init__(self, position, parent=None):
        super(Test_Box, self).__init__(parent)

        # Settings
        self.setFlags(  self.flags()                            |
                        QGraphicsItem.ItemIsSelectable          |
                        QGraphicsItem.ItemIsMovable             |
                        QGraphicsItem.ItemIsFocusable           |
                        QGraphicsItem.ItemSendsScenePositionChanges )
        self.setPos(position)


    def boundingRect(self):
        return Test_Box.RECT


    def paint(self, painter, option, widget):
        # Draw Box
        brush   = QBrush()
        painter.setBrush(brush) 
        painter.drawRoundedRect(Test_Box.RECT, Test_Box.CORNER_RADIUS, Test_Box.CORNER_RADIUS)



    def itemChange(self, change, variant):
        super(Test_Box, self).itemChange(change, variant)
        if change == QGraphicsItem.ItemScenePositionHasChanged:
            self.setParentItem(None)
        return QGraphicsItem.itemChange(self, change, variant)

Calling setItemParent and setting the item's parent to another item does work though so I'm using a generic parent in the meantime.


Solution

  • If that setParent is not a typo, then that is your issue. A QGraphicsItem has no setParent method and that should give you an error. You should rather use setParentItem:

    children.setParentItem(None)
    

    Edit

    I created a test case based on your item:

    import sys
    from PyQt4.QtGui import *
    from PyQt4.QtCore import *
    
    POINT_SIZE = 7
    class Test_Box(QGraphicsItem):
        # Constants
        WIDTH           = 50 * POINT_SIZE
        HEIGHT          = 13 * POINT_SIZE
        RECT            = QRectF(0, 0, WIDTH, HEIGHT)
        CORNER_RADIUS   = 1.5 * POINT_SIZE
    
    
        def __init__(self, position, parent=None):
            super(Test_Box, self).__init__(parent)
    
            # Settings
            self.setFlags(  self.flags()                            |
                            QGraphicsItem.ItemIsSelectable          |
                            QGraphicsItem.ItemIsMovable             |
                            QGraphicsItem.ItemIsFocusable           |
                            QGraphicsItem.ItemSendsScenePositionChanges )
            self.setPos(position)
    
    
        def boundingRect(self):
            return Test_Box.RECT
    
    
        def paint(self, painter, option, widget):
            # Draw Box
            brush   = QBrush()
            painter.setBrush(brush) 
            painter.drawRoundedRect(Test_Box.RECT, Test_Box.CORNER_RADIUS, Test_Box.CORNER_RADIUS)
    
    
    
        def itemChange(self, change, variant):
            super(Test_Box, self).itemChange(change, variant)
            if change == QGraphicsItem.ItemScenePositionHasChanged:
                self.setParentItem(None)
            return QGraphicsItem.itemChange(self, change, variant)
    
    
    class Window(QWidget):
        def __init__(self, parent=None):
            super(Window, self).__init__(parent)
    
            self.scene = QGraphicsScene()
    
            self.item1 = Test_Box(QPointF(0, 0))
            self.item2 = Test_Box(QPointF(20, 20))
            self.item11 = Test_Box(QPointF(10, 5), self.item1)
    
            self.scene.addItem(self.item1)
            self.scene.addItem(self.item2)
    
            self.view = QGraphicsView(self.scene)
    
            self.listItems = QPushButton('list')
            self.listItems.clicked.connect(self.printItems)
    
            layout = QHBoxLayout()
            layout.addWidget(self.view)
            layout.addWidget(self.listItems)
            self.setLayout(layout)
    
        def printItems(self):
            for item in self.scene.items():
                print item, item.parentItem()
    
    
    app = QApplication(sys.argv)
    
    w = Window()
    w.show()
    
    sys.exit(app.exec_())
    

    And it works as expected, but I did find some odd behavior. If I don't keep references for items in the Window class, namely if I do the following then the application crashes when I move items.

    class Window(QWidget):
        def __init__(self, parent=None):
            super(Window, self).__init__(parent)
    
            self.scene = QGraphicsScene()
    
            item1 = Test_Box(QPointF(0, 0))
            item2 = Test_Box(QPointF(20, 20))
            item11 = Test_Box(QPointF(10, 5), item1)
    
            self.scene.addItem(item1)
            self.scene.addItem(item2)
    
            self.view = QGraphicsView(self.scene)
    
            self.listItems = QPushButton('list')
            self.listItems.clicked.connect(self.printItems)
    
            layout = QHBoxLayout()
            layout.addWidget(self.view)
            layout.addWidget(self.listItems)
            self.setLayout(layout)
    
        def printItems(self):
            for item in self.scene.items():
                print item, item.parentItem()
    

    I don't actually know what happens but looks like the items are garbage collected, which shouldn't happen since addItem would give the ownership to the scene and that should keep the items 'alive'. This may be a bug in PyQt, but I'm not sure.