Search code examples
pythonpyqtpyqt5qgraphicsviewqgraphicsscene

Join QGraphicsItem and QPainter in same GraphicsScene


I am currently building an interactive canvas using PyQt5 and Graphicscene, so far modifying the codes found on those posts:

I have made two separates examples of what I would like , but so far I have not been able to merge the two into a single code.

The first code I inserts a node and corresponding edge in the position clicked on the screen. Begins and ends with a double click of the left button of the mouse.

import sys from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import *

class WindowClass(QMainWindow):
    def __init__(self, parent=None):
        QMainWindow.__init__(self, parent)
        self.view = ViewClass()
        self.setCentralWidget(self.view)

class ViewClass(QGraphicsView):
    def __init__(self, parent=None):
        QGraphicsView.__init__(self, parent)
        self.s = SceneClass()
        self.setScene(self.s)
        self.setRenderHint(QPainter.Antialiasing)

class SceneClass(QGraphicsScene):
    def __init__(self, parent=None):
        QGraphicsScene.__init__(self, QRectF(-1000, -1000, 2000, 2000), parent)

        self.node_start = None
        self.node_end = None
        self.pos = None
        self.pos_end = None

    def mouseDoubleClickEvent(self, event):
        if event.button() == Qt.LeftButton and self.node_start is None:
            node = Node()
            self.addItem(node)
            node.setPos(event.scenePos() + QPointF(10, 10))
            self.node_start = node
        else:
            self.node_start = None

    def mouseMoveEvent(self, event):
        super(SceneClass, self).mouseMoveEvent(event)

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton and self.node_start:
            "nodo final"
            node = Node()
            self.addItem(node)
            node.setPos(event.scenePos() + QPointF(10, 10))
            self.node_end = node
            edge = Edge(self.node_start, self.node_end)
            self.addItem(edge)
            "nodo final se convierte en nodo inicial"
            self.node_start = self.node_end
        super(SceneClass, self).mousePressEvent(event)

class Node(QGraphicsEllipseItem):
    def __init__(self, rect=QRectF(-20, -20, 20, 20), parent=None):
        QGraphicsEllipseItem.__init__(self, rect, parent)
        self.edges = []
        self.setZValue(1)
        self.setBrush(Qt.darkGray)
        self.setFlag(QGraphicsItem.ItemIsMovable, True)
        self.setFlag(QGraphicsItem.ItemIsSelectable, True)
        self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)

    def addEdge(self, edge):
        self.edges.append(edge)

    def itemChange(self, change, value):
        if change == QGraphicsItem.ItemSelectedChange:
            self.setBrush(Qt.green if value else Qt.darkGray)

        if change == QGraphicsItem.ItemPositionHasChanged:
            for edge in self.edges:
                edge.adjust()

        return QGraphicsItem.itemChange(self, change, value)

class Edge(QGraphicsLineItem):
    def __init__(self, source, dest, parent=None):
        QGraphicsLineItem.__init__(self, parent)
        self.source = source
        self.dest = dest
        self.source.addEdge(self)
        self.dest.addEdge(self)
        self.setPen(QPen(Qt.red, 3))
        self.adjust()

    def adjust(self):
        self.prepareGeometryChange()
        self.setLine(QLineF(self.dest.pos() + QPointF(-10, -10), self.source.pos() + QPointF(-10, -10)))


if __name__ == '__main__':
    app = QApplication(sys.argv)
    wd = WindowClass()
    wd.show()
    sys.exit(app.exec_())

The second code paints a line from the last position where the left mouse button was clicked to the current mouse position on the screen.

import sys
from PyQt5.QtWidgets import (QApplication, QLabel, QWidget)
from PyQt5.QtGui import QPainter, QColor, QPen
from PyQt5.QtCore import Qt

class MouseTracker(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()
        self.setMouseTracking(True)

    def initUI(self):
        self.setGeometry(200, 200, 1000, 500)
        self.setWindowTitle('Mouse Tracker')
        self.label = QLabel(self)
        self.label.resize(500, 40)
        self.show()
        self.pos = None
        self.pos_end = None

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.pos_end = event.pos()

    def mouseMoveEvent(self, event):
        self.pos = event.pos()
        self.update()

    def paintEvent(self, event):
        if self.pos and self.pos_end:
            "Estilo de Linea"
            pen = QPen(Qt.red)
            pen.setWidth(3)
            pen.setStyle(Qt.CustomDashLine)
            pen.setDashPattern([10, 10])
            pen.setJoinStyle(Qt.RoundJoin)
            "Objeto de QPainter"
            q = QPainter(self)
            q.setRenderHint(QPainter.Antialiasing, True)
            "Aplicar estilo de Linea"
            q.setPen(pen)
            "Dibujar Linea"
            q.drawLine(self.pos.x(), self.pos.y(), self.pos_end.x(), self.pos_end.y())

app = QApplication(sys.argv)
ex = MouseTracker()
sys.exit(app.exec_())

The behavior I would like to see in mi GUI code is the union of the two meaning the following: being able to insert node with its corresponding edge by a left mouse click and then when the left mouse click is released start the QPaint line of the second code from the last node insert to the current position on the screen. I have tried to join the two code but the paintevent does not triggered when is written in any of the classes of the first code, even if ti is its own class.


Solution

  • QPainter is the low-level tool used for painting and the different high-level tools use it as the Qt Graphics Framework, but in this case they should not be used.

    In your case it is preferable to use the items since it simplifies the task:

    class WindowClass(QMainWindow):
        def __init__(self, parent=None):
            super(WindowClass, self).__init__(parent)
            view = QGraphicsView()
            view.setMouseTracking(True)
            view.setRenderHint(QPainter.Antialiasing)
            scene = SceneClass(self)
            view.setScene(scene)
            self.setCentralWidget(view)
            self.resize(640, 480)
    
    
    class SceneClass(QGraphicsScene):
        def __init__(self, parent=None):
            super(SceneClass, self).__init__(QRectF(-1000, -1000, 2000, 2000), parent)
            self._edge_item = None
    
        def mousePressEvent(self, event):
            if event.button() == Qt.LeftButton:
                node = Node()
                node.setPos(event.scenePos())
                self.addItem(node)
    
                if self._edge_item:
                    self._edge_item.dst = node
                    self._edge_item = None
                else:
                    self._edge_item = Edge()
                    self._edge_item.src = node
                    self.addItem(self._edge_item)
    
        def mouseMoveEvent(self, event):
            if self._edge_item:
                self._edge_item.p2 = event.scenePos()
            super(SceneClass, self).mouseMoveEvent(event)
    
    
    class Node(QGraphicsEllipseItem):
        def __init__(self, rect=QRectF(-10, -10, 20, 20), parent=None):
            super(Node, self).__init__(rect, parent)
            self.edges = []
            self.setZValue(1)
            self.setBrush(Qt.darkGray)
            self.setFlag(QGraphicsItem.ItemIsMovable, True)
            self.setFlag(QGraphicsItem.ItemIsSelectable, True)
            self.setFlag(QGraphicsItem.ItemSendsGeometryChanges, True)
    
        def addEdge(self, edge):
            self.edges.append(edge)
    
        def itemChange(self, change, value):
            if change == QGraphicsItem.ItemSelectedChange:
                self.setBrush(Qt.green if value else Qt.darkGray)
            if change == QGraphicsItem.ItemPositionHasChanged:
                for edge in self.edges:
                    edge.adjust()
            return super(Node, self).itemChange(change, value)
    
    
    class Edge(QGraphicsLineItem):
        def __init__(self, parent=None):
            super(Edge, self).__init__(parent)
            self.setPen(QPen(Qt.red, 3))
            self._src = None
            self._dst = None
    
        @property
        def src(self):
            return self._src
    
        @src.setter
        def src(self, node):
            self._src = node
            self._src.addEdge(self)
            self.adjust()
    
        @property
        def dst(self):
            return self._dst
    
        @dst.setter
        def dst(self, node):
            self._dst = node
            self._dst.addEdge(self)
            self.adjust()
    
        @property
        def p1(self):
            return self.line().p1()
    
        @p1.setter
        def p1(self, p):
            line = self.line()
            line.setP1(p)
            self.setLine(line)
    
        @property
        def p2(self):
            return self.line().p2()
    
        @p2.setter
        def p2(self, p):
            line = self.line()
            line.setP2(p)
            self.setLine(line)
    
        def adjust(self):
            self.prepareGeometryChange()
            if self.src:
                self.p1 = self.src.pos()
                self.p2 = self.src.pos()
            if self.dst:
                self.p2 = self.dst.pos()