Search code examples
pythonpyqt5qgraphicsviewqgraphicssceneqpixmap

Why QGraphicsGridLayout doesn't handle row and column placement of items placed on a scene?


So far, I have the following code:

 p1 = QGraphicsWidget(self.scene.addItem(QGraphicsPixmapItem(self.pixmap)))
 p2 = QGraphicsWidget(self.scene.addItem(QGraphicsPixmapItem(self.pixmap)))
self.grid.addItem(pixmap, 0, 0)
    self.grid.addItem(pixmap2, 0,1 )
    f = QGraphicsWidget()
    f.setLayout(self.grid)
    self.scene.addItem(f)

For some reason those to equal pixmaps are put onto each other. While I want then next to each other.

w1 = self.scene.addItem(QGraphicsPixmapItem(self.pixmap))
w1.setPos(0,0)
pixmap = QGraphicsWidget(w1)
w2 = self.scene.addItem(QGraphicsPixmapItem(self.pixmap))
w2.setPos(200,0)
pixmap2 = QGraphicsWidget(w2)
self.grid.addItem(pixmap, 0, 0)
self.grid.addItem(pixmap2, 0,1 )
f = QGraphicsWidget()
f.setLayout(self.grid)
self.scene.addItem(f)

This code over worked, but shouldn't QGraphicsGridLayout handle placement?


Solution

  • QGraphicsScene.addItem() returns None.

    You're not adding the pixmap items to the QGraphicsWidget, you're directly adding them to the scene. Also, the first parameter in the constructor of QGraphicsWidget is a parent, so it wouldn't be valid anyway; the result is that you are actually adding empty QGraphicsWidgets to the layout.

    Let's analyze those first two lines, which is where the problem resides, by following the order of execution:

    QGraphicsWidget(self.scene.addItem(QGraphicsPixmapItem(self.pixmap)))
                                       ^^^^^^^^^^^^^^^^^^^(^^^^^^^^^^^)
                    ^^^^^^^^^^^^^^^^^^(               1.               )
    ^^^^^^^^^^^^^^^(         2.                                         )
          3.
    

    Remember that using "nested" function calls means that the outer calls always use the returned values of the inner calls, exactly like math expressions with brackets, with inner calls being executed first for obvious reasons: you cannot solve the expressions within the outer parentheses until you evaluate the result the inner ones.
    In the case above, 3 will use the return value of 2, which will be executed with the return value of 1.

    command current argument result returned object
    1. QGraphicsPixmapItem(self.pixmap) QPixmap create an item that will display the given pixmap an instance of QGraphicsPixmapItem
    2. self.scene.addItem(...) QGraphicsPixmapItem instance adds the given item to the scene None
    3. QGraphicsWidget(...) None create an empty QGraphicsWidget without parents QGraphicsWidget instance

    The result of the above is that both p1 and p2 are empty QGraphicsWidget items that are added to the layout, while the pixmap items were directly added to the scene, which is why they appear overlapped. Those two lines could have been written like this, giving the same result:

    self.scene.addPixmap(self.pixmap)
    self.scene.addPixmap(self.pixmap)
    p1 = QGraphicsWidget()
    p2 = QGraphicsWidget()
    

    Now, the problem is that only QGraphicsLayoutItem subclasses can be added to a QGraphicsLayout, and you cannot just add a standard QGraphicsItem, so the possible solution is to create a basic QGraphicsLayoutItem subclass that will act as a "container" for the pixmap item, and implement the required functions, as explained in the documentation.

    class PixmapLayoutItem(QGraphicsLayoutItem):
        def __init__(self, pixmap, parent=None):
            super().__init__(parent)
            self.pixmapItem = QGraphicsPixmapItem(pixmap)
            self.setGraphicsItem(self.pixmapItem)
    
        def sizeHint(self, which, constraint=None):
            return self.pixmapItem.boundingRect().size()
    
        def setGeometry(self, geometry):
            self.pixmapItem.setPos(geometry.topLeft())
    
    
    # ...
    
    p1 = PixmapLayoutItem(self.pixmap)
    p2 = PixmapLayoutItem(self.pixmap)
    self.grid.addItem(p1, 0, 0)
    self.grid.addItem(p2, 0, 1)
    f = QGraphicsWidget()
    f.setLayout(self.grid)
    self.scene.addItem(f)
    

    Note that the above subclass is extremely basic and does not consider any transformation of the embedded item: if you want to support those (like scaling or rotation), you must implement both sizeHint() and setGeometry() accordingly.