This is the expected behavior: a child item moves with its parent item, not the other way around. If you want to adjust the line according to the endpoints you need to write a function to do that.
class LineItem(QGraphicsLineItem):
# ...
def updateLine(self):
p1, p2 = self.endpoints
self.setLine(QLineF(p1.pos(), p2.pos()))
Now there are a few different options for how/where to call this function (they all achieve the same result but you may prefer one or find that one better suits your project).
IN THE CHILD SUBCLASS
Option 1) Reimplement mouseMoveEvent
class Endpoint(QGraphicsEllipseItem):
def __init__(self, *args, **kwargs):
super().__init__(-5, -5, 10, 10, *args, **kwargs)
self.setBrush(QColor(0, 0, 255))
self.setFlags(self.ItemIsMovable | self.ItemIsSelectable)
def mouseMoveEvent(self, event):
super().mouseMoveEvent(event)
self.parentItem().updateLine()
Also notice that I offset the rect
so pos()
is actually at the center of the item.
Option 2) Set the flag ItemSendsScenePositionChanges
, reimplement itemChange
to catch ItemScenePositionHasChanged
class Endpoint(QGraphicsEllipseItem):
def __init__(self, *args, **kwargs):
super().__init__(-5, -5, 10, 10, *args, **kwargs)
self.setBrush(QColor(0, 0, 255))
self.setFlags(self.ItemIsMovable | self.ItemIsSelectable)
self.setFlag(self.ItemSendsScenePositionChanges)
def itemChange(self, change, value)
if change == self.ItemScenePositionHasChanged:
self.parentItem().updateLine()
return super().itemChange(change, value)
IN THE PARENT SUBCLASS
Option 3) Call setFiltersChildEvents(True)
and reimplement sceneEventFilter
to catch QEvent.GraphicsSceneMouseMove
class LineItem(QGraphicsLineItem):
def __init__(self):
super().__init__()
self.setLine(0, 0, 500, 0)
self.setPen(QPen(QColor(0, 0, 0), 5 ))
self.setFlags(self.ItemIsMovable | self.ItemIsSelectable)
self.endpoints = [Endpoint(self) for i in range(2)]
self.endpoints[1].setPos(500, 0)
self.setFiltersChildEvents(True)
def sceneEventFilter(self, obj, event):
if event.type() == QEvent.GraphicsSceneMouseMove:
obj.mouseMoveEvent(event)
self.updateLine()
return True # we handled the event, prevent further processing
return super().sceneEventFilter(obj, event)
def updateLine(self):
p1, p2 = self.endpoints
self.setLine(QLineF(p1.pos(), p2.pos()))
Note that setFiltersChildEvents
will filter events from ALL child items and you may want to check if obj in self.endpoints
. Alternatively call installSceneEventFilter(parent)
on each child, which can only be used AFTER the parent is added to the scene. So it would be done in the scope where the line is added to the scene, such as:
line = LineItem()
scene.addItem(line)
for x in line.endpoints:
x.installSceneEventFilter(line)
Also it may be useful to know in what order these methods are invoked in response to a mouse move:
sceneEventFilter
-> mouseMoveEvent
-> itemChange
LineItem.sceneEventFilter
intercepts the event before it gets dispatched to an event handler. If it returns False it will proceed through the event system.
Endpoint.mouseMoveEvent
the event is now received in the mouseMoveEvent handler. The default implementation or calling super
will move the item and send enabled notifications:
Endpoint.itemChange
the item is notified of ItemScenePositionHasChanged here, (a read-only notification)