pythonpython-3.xgraphicspyqt5paintevent

How to draw both object on the same window?


Prologue: Both of the object represented below are on the same window.

I am having problem with the not updating I am having a problem related to self.setGeometry(x,y,w,h) function. So what I want to achieve is multiple rect, rendered in parallel, which each has a line protruding out of their rectangle when clicked. (Creating a connector though that is not the point of this topic).

In this case, I have rect A and rect B rendering together. Rect A has a line protruding out to the position of the mouse.

(obj A)           (obj B)
 ____              ____
|    |            |    |
|  \ |            |    |
 ---\              ----
     \
     (Mouse)

An example code of what am I trying to achieve.

# File: connectors.py
from PyQt5.QtCore import Qt, QPoint
from PyQt5.QtGui import QPainter
from PyQt5.QtWidgets import QWidget

class Connector(QWidget):

    def __init__(self, rect: List[int]):
        super().__init__()
        self.setGeometry(0, 0, 1920, 1080)
        self.rect = rect
        self.clicked = False

        self.begin = QPoint(rect[0], rect[1])
        self.end = QPoint(0,0)

    def paintEvent(self, event):
        qp = QPainter()
        qp.begin(self)
        qp.setPen(Qt.red)
        qp.drawRect(*self.rect)

        if self.clicked:
            qp = QPainter()
            qp.begin(self)
            qp.setPen(Qt.red)
            qp.drawLine(self.begin, self.end)
        self.update()

    def mousePressEvent(self, event):
        if event.button() == 1:
            self.clicked = True
            self.end = event.pos()

    def mouseMoveEvent(self, event):
        if self.clicked:
            self.end = event.pos()

    def mouseReleaseEvent(self, event):
        if event.button() == 1:
            self.clicked = False
            self.end = event.pos()

# File: main.py
scene = QGraphicsScene()
scene.addWidget( Connector((400, 400, 100, 100)) )
scene.addWidget( Connector((400, 600, 100, 100)) )

But what I am ending up is PyQt showing the top-most object onto the screen thus only showing one object, BUT I also tried to minimize the geometry leading the protruding line, when clicked, cutting off on the border.


Solution

  • Explanation:

    In your case it has 2 widgets where you draw the rectangles where one is on top of another so you will only see one of them: the one above.

    Solution:

    Qt GraphicsView Framework (QGraphicsView, QGraphicsScene, QGraphicsXItem, etc.) works differently and painting is not used directly since they offer basic items that implement all the functionalities so in this case you must use QGraphicsRectItem with QGraphicsLineItem and modify it with the information of the QGraphicsView.

    Considering the above, the solution is:

    import sys
    
    from PyQt5.QtCore import QRectF, Qt
    from PyQt5.QtWidgets import (
        QApplication,
        QGraphicsLineItem,
        QGraphicsRectItem,
        QGraphicsScene,
        QGraphicsView,
    )
    
    
    class RectItem(QGraphicsRectItem):
        def __init__(self, rect, parent=None):
            super().__init__(parent)
            self.setRect(QRectF(*rect))
            self.setPen(Qt.red)
            self._line_item = QGraphicsLineItem(self)
            self.line_item.setPen(Qt.red)
            l = self.line_item.line()
            l.setP1(self.rect().topLeft())
            l.setP2(self.rect().topLeft())
            self.line_item.setLine(l)
            self.line_item.hide()
    
        @property
        def line_item(self):
            return self._line_item
    
        def move_line_to(self, sp):
            lp = self.mapFromScene(sp)
            l = self.line_item.line()
            l.setP2(lp)
            self.line_item.setLine(l)
    
    
    class GraphicsView(QGraphicsView):
        def __init__(self, parent=None):
            super().__init__(parent)
    
            self.setScene(QGraphicsScene())
    
            self.scene().addItem(RectItem((400, 400, 100, 100)))
            self.scene().addItem(RectItem((400, 600, 100, 100)))
    
        def mousePressEvent(self, event):
            vp = event.pos()
            sp = self.mapToScene(vp)
            self.move_lines(sp)
            super().mousePressEvent(event)
    
        def mouseMoveEvent(self, event):
            vp = event.pos()
            sp = self.mapToScene(vp)
            self.move_lines(sp)
            super().mouseMoveEvent(event)
    
        def mouseReleaseEvent(self, event):
            for item in self.items():
                if isinstance(item, RectItem):
                    item.line_item.hide()
            super().mouseReleaseEvent(event)
    
        def move_lines(self, sp):
            for item in self.items():
                if isinstance(item, RectItem):
                    item.line_item.show()
                    item.move_line_to(sp)
    
    
    if __name__ == "__main__":
        app = QApplication(sys.argv)
        w = GraphicsView()
        w.resize(640, 480)
        w.show()
        sys.exit(app.exec_())