I have this simple example that represents a diagram editor I'm making.
In the diagram I have "parent" objecs that can have children, and the children should create and maintain a line that ties to the parent.
In the following example the black Square is the parent and the red triangle is the child.
The child creates a line with the parent, but when I move the child the line does not realign. Ideally I would extend the mouseMoveEvent function to do whatever it does nativelly plus the line redraw functioality.
What is the best way to make the child redraw the line?
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
class ParentNode(QGraphicsRectItem):
def __init__(self, diagramScene, parent=None, h=60, w=60):
QGraphicsItemGroup.__init__(self, parent)
self.scene = diagramScene
self.h = h
self.w = w
self.setPen(QPen(Qt.black, 2))
self.setBrush(QBrush(Qt.black))
self.setFlags(self.ItemIsSelectable | self.ItemIsMovable)
self.setCursor(QCursor(Qt.PointingHandCursor))
square = QGraphicsPolygonItem()
square.setPolygon(QPolygonF([QPointF(0, 0), QPointF(20, 0), QPointF(20, 20), QPointF(0, 20)]))
self.setRect(0.0, 0.0, self.w, self.h)
class ChildNode(QGraphicsItemGroup):
def __init__(self, parent):
QGraphicsItemGroup.__init__(self, parent)
self.parent = parent
self.setFlags(self.ItemIsSelectable | self.ItemIsMovable)
triangle = QGraphicsPolygonItem()
triangle.setPolygon(QPolygonF([QPointF(0, 0), QPointF(20, 0), QPointF(10, 20)]))
triangle.setPen(QPen(Qt.red, 2))
self.addToGroup(triangle)
self.setPos(180, 180)
# define line at first
self.line = self.parent.scene.addLine(self.x() + 10,
self.y() + 0,
self.parent.x() + self.parent.w / 2,
self.parent.y() + self.parent.h)
if __name__ == '__main__':
app = QApplication(sys.argv)
scene = QGraphicsScene()
n1 = ParentNode(scene)
scene.addItem(n1)
n2 = ChildNode(n1)
scene.addItem(n2)
view = QGraphicsView(scene)
view.show()
view.resize(600, 400)
sys.exit(app.exec_())
PS: This example is a representation of a much more complex program, so I need the classes inheritance to change as little as possible.
The simplest way to do this is to subclass the dependant item and reimplement its itemChange method so you can monitor position changes. Below is a re-write of your script that does that:
import sys
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
class LineUpdateMixin(object):
def __init__(self, parent):
super(LineUpdateMixin, self).__init__(parent)
self.setFlag(QGraphicsItem.ItemSendsScenePositionChanges)
def itemChange(self, change, value):
if change == QGraphicsItem.ItemScenePositionHasChanged:
self.parentItem().updateLine(value)
return super(LineUpdateMixin, self).itemChange(change, value)
class Triangle(LineUpdateMixin, QGraphicsPolygonItem): pass
class ParentNode(QGraphicsRectItem):
def __init__(self, diagramScene, parent=None, h=60, w=60):
super(ParentNode, self).__init__(parent)
self.setPen(QPen(Qt.black, 2))
self.setBrush(QBrush(Qt.black))
self.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable)
self.setCursor(QCursor(Qt.PointingHandCursor))
square = QGraphicsPolygonItem()
square.setPolygon(QPolygonF([QPointF(0, 0), QPointF(20, 0), QPointF(20, 20), QPointF(0, 20)]))
self.setRect(0.0, 0.0, w, h)
class ChildNode(QGraphicsItemGroup):
def __init__(self, parent):
super(ChildNode, self).__init__(parent)
self.setFlags(QGraphicsItem.ItemIsSelectable | QGraphicsItem.ItemIsMovable)
self.line = QGraphicsLineItem()
parent.scene().addItem(self.line)
self.updateLine(self.pos())
triangle = Triangle(self)
triangle.setPolygon(QPolygonF([QPointF(0, 0), QPointF(20, 0), QPointF(10, 20)]))
triangle.setPen(QPen(Qt.red, 2))
self.addToGroup(triangle)
self.setPos(180, 180)
def updateLine(self, pos):
parent = self.parentItem()
rect = parent.rect()
self.line.setLine(
pos.x() + 10, pos.y() + 0,
parent.x() + rect.width() / 2,
parent.y() + rect.height(),
)
if __name__ == '__main__':
app = QApplication(sys.argv)
scene = QGraphicsScene()
n1 = ParentNode(scene)
scene.addItem(n1)
n2 = ChildNode(n1)
view = QGraphicsView(scene)
view.setGeometry(600, 100, 600, 400)
view.show()
sys.exit(app.exec_())