Search code examples
pythonqtpyqtqgraphicsview

QGraphicsItemGroup.removeFromGroup -- child items not correctly reparented to Scene


I'm trying to remove a QGraphicsItem from a QGraphicsItemGroup. When calling removeFromGroup, the item is removed (of course). However, it's then no longer visible in the Scene. I have to call Scene.addItem(item) in order for it to appear again. This is apparently something you shouldn't do (I'm given a warning for doing that). But I can't seem to find another workaround.

Here's a minimal example:

import sys 

from PyQt4.QtGui import *
from PyQt4.QtCore import *

class MainWindow(QMainWindow):

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

        self.view = QGraphicsView()
        self.scene = QGraphicsScene()
        self.view.setScene(self.scene)

        self.setCentralWidget(self.view)


def add_group(scene):
    group = QGraphicsItemGroup()
    text = QGraphicsTextItem()
    text.setPlainText("I'm visible")
    group.addToGroup(text)
    scene.addItem(group)

    # After this, text is no longer in group. However, it is no longer visible.
    group.removeFromGroup(text)
    assert not text in group.childItems()

    # But text is still in scene. 
    assert text.scene() == scene

    # this works (i.e. text becomes visible again). However, it also produces a 
    # warning: QGraphicsScene::addItem: item has already been added to this scene. 
    # The docs also advice against it.
    scene.addItem(text)

    # According to the docs, I thought this might work, but it gives me a TypeError.
    # text.setParentItem(0)


if __name__ == '__main__':
    app = QApplication(sys.argv)
    main = MainWindow()
    add_group(main.scene)
    main.show()
    sys.exit(app.exec_())

Tips and hints are very welcome.


Solution

  • The QGraphicsTextItem can never be parented to a scene, because it's parent must be a QGraphicsItem (which QGraphicsScene does not inherit).

    When the QGraphicsTextItem is created, it's parent is None. Its parent is set to group when it is added to it (QGraphicsItemGroup is a subclass of QGraphicsItem), then set back to None when it's removed from group.

    Calling scene.addItem() is actually a NO-OP. Qt checks whether scene is the same as text.scene(), and if it is, it prints the warning and returns without doing anything.

    The fact that it seems to "work" in some circumstances, is just an artifact of python's garbage-collecting mechanism.

    If your test is re-cast in a more realistic way, the QGraphicsTextItem remains visible after removal from the group:

    import sys
    
    from PyQt4.QtGui import *
    from PyQt4.QtCore import *
    
    class MainWindow(QMainWindow):
        def __init__(self, parent=None):
            super(MainWindow, self).__init__(parent)
            self.view = QGraphicsView(self)
            self.scene = QGraphicsScene(self.view)
            self.view.setScene(self.scene)
            self.setCentralWidget(self.view)
            self.group = QGraphicsItemGroup()
            self.text = QGraphicsTextItem()
            self.text.setPlainText("I'm visible")
            self.group.addToGroup(self.text)
            self.scene.addItem(self.group)
            self.group.removeFromGroup(self.text)
    
    if __name__ == '__main__':
        app = QApplication(sys.argv)
        main = MainWindow()
        main.show()
        sys.exit(app.exec_())