Search code examples
pythonpyqt5pyside2qgraphicsitem

How to identify a QGraphicsItem?


I want to know if there's a way of getting a QGraphicsItem's id. Nothing complicated. Lets say I have a QGraphicsScene and I created a lot of rectangles of the same size and color in it. Even if not with ids, than how can I identify them?

UPDATE Here's some of my code

class Test(QtWidgets.QDialog):

    def __init__(self):
        super(Test, self).__init__()
        
        loader = QtUiTools.QUiLoader()
        self.ui = loader.load(ui_path, self)
        self.variables()
        
        self.ui.create_button.clicked.connect(self.creator)
        self.scene.focusItemChanged.connect(self.nameDisplay)
        
    def variables(self):        
        self.scene = QtWidgets.QGraphicsScene()
        self.ui.canvas_area.setScene(self.scene)
        
         
    def creator(self):
        rect = self.scene.addRect(-20,-20,40,40, QPen(Qt.red), QBrush(Qt.gray))
        rect.setFlag(QGraphicsItem.ItemIsMovable)
        rect.setFlag(QGraphicsItem.ItemIsFocusable)
        rect.setFlag(QGraphicsItem.ItemIsSelectable)
        
    def nameDisplay(self, newFocusItem, oldFocusItem, reason):
        self.ui.name_line.setText(str(self.scene.focusItem()))       
                
if __name__ == '__main__':
    test_window = Test()
    test_window.ui.show()

Thanks in advance!


Solution

  • QGraphicsItem is not a QObject, so it doesn't support the property system, but it has limited support for storing properties through setData().

    class Test(QtWidgets.QDialog):
        count = 0
        # ...
    
        def creator(self):
            rect = self.scene.addRect(-20,-20,40,40, QPen(Qt.red), QBrush(Qt.gray))
            rect.setFlag(QGraphicsItem.ItemIsMovable)
            rect.setFlag(QGraphicsItem.ItemIsFocusable)
            rect.setFlag(QGraphicsItem.ItemIsSelectable)
            rect.setData(0, self.count)
            self.count += 1
    
        def nameDisplay(self, newFocusItem, oldFocusItem, reason):
            if newFocusItem and newFocusItem.data(0) is not None:
                self.ui.name_line.setText('Item {}'.format(newFocusItem.data(0)))
    

    Note: the key argument of setData() and data() must be an integer, and there's no way to know if (and which) keys are set: you can only check if the key value is not None. If you need more complex data mapping, you can use a dictionary as value, or just subclass (see below).

    It's important to consider that while one might try to set an attribute for the item returned by addRect(), it wouldn't work: we should remember that PyQt is a binding to Qt, and all python objects used for Qt objects are wrappers.

    Consider the following:

        def creator(self):
            # ...
            rect.id = self.count
    
        def nameDisplay(self, newFocusItem, oldFocusItem, reason):
            print(hasattr(newFocusItem, 'id')
    

    This will always print False, and that's because we only created an attribute for the wrapper reference in creator, but since that reference is just local and the attribute is set on that reference (not the wrapped object), it will be garbage collected and the attribute will be lost.

    A possible option would be to add a persistent reference for each item:

    class Test(QtWidgets.QDialog):
        def __init__(self):
            # ...
            self.items = []
    
        def creator(self):
            # ...
            rect.id = self.count
            self.items.append(rect)
    

    But I'd suggest to stick with the setData() option, as it is more compliant with how Qt works.

    Alternatively, we can create a custom QGraphicsRectItem subclass and add it with self.scene.addItem(). In that case, the python wrapper will be persistent and the attribute will be preserved:

    class MyRect(QtWidgets.QGraphicsRectItem):
        def __init__(self, id, x, y, w, h, pen, brush):
            super().__init__(x, y, w, h)
            self.id = id
            self.setPen(pen)
            self.setBrush(brush)
    
    class Test(QtWidgets.QDialog):
        # ...
        def creator(self):
            rect = MyRect(self.count, -20, -20, 40, 40, QPen(Qt.red), QBrush(Qt.gray))
            self.scene.addItem(rect)
            # ...
    
        def nameDisplay(self, newFocusItem, oldFocusItem, reason):
            if hasattr(newFocusItem, 'id'):
                self.name_line.setText('Item {}'.format(newFocusItem.id))