如何在 PyQt5 中移动带端点的线
How to move line with endpoints in PyQt5
我有一个图形场景和 2 类 条线和端点,但是当我移动线的端点时出现问题。当我移动线时,端点会移动,但是当移动端点时,它们就会离开线,即使我将其设为父线。我希望它双向工作,所以当移动端点时,线会移动到它,因此它可以改变长度和斜率。
class Lineitem(QGraphicsLineItem):
def __init__(self):
super().__init__()
self.setLine(0, 0, 500, 0)
self.setPen(QPen(QColor(0, 0, 0), 5 ))
self.setFlag(self.ItemIsMovable)
self.setFlag(self.ItemIsSelectable)
self.endpoints = []
for i in range(2):
endpoint = Endpoint(self)
endpoint.setParentItem(self)
self.endpoints.append(endpoint)
self.endpoints[0].setPos(-5, -5)
self.endpoints[1].setPos(495, -5)
class Endpoint(QGraphicsEllipseItem):
def __init__(self, parent):
super().__init__(parent)
self.setRect(0, 0, 10, 10 )
self.setBrush(QColor(0, 0, 255))
self.setFlag(self.ItemIsMovable)
self.setFlag(self.ItemIsSelectable)
这是主窗口:
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.gscene = QGraphicsScene(0, 0, 1000, 1000)
self.line = Lineitem()
self.line.setPos(300, 300)
self.gview = QGraphicsView(self.gscene)
self.gscene.addItem(self.line)
self.setCentralWidget(self.gview)
这是预期的行为:child 项目与其 parent 项目一起移动,而不是相反。如果你想根据端点调整线,你需要编写一个函数来做到这一点。
class LineItem(QGraphicsLineItem):
# ...
def updateLine(self):
p1, p2 = self.endpoints
self.setLine(QLineF(p1.pos(), p2.pos()))
现在 how/where 有几个不同的选项来调用此函数(它们都实现相同的结果,但您可能更喜欢一个或发现一个更适合您的项目)。
在 CHILD 子类中
选项 1) 重新实现 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()
另请注意,我偏移了 rect
,因此 pos()
实际上位于项目的中心。
选项 2) 设置标志 ItemSendsScenePositionChanges
,重新实现 itemChange
以捕获 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)
在 PARENT 子类中
选项 3) 调用 setFiltersChildEvents(True)
并重新实现 sceneEventFilter
以捕获 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()))
请注意 setFiltersChildEvents
将从所有 child 项中过滤事件,您可能需要检查 if obj in self.endpoints
。或者在每个 child 上调用 installSceneEventFilter(parent)
,它只能在 parent 添加到场景后使用。所以会在场景中添加线条的范围内完成,如:
line = LineItem()
scene.addItem(line)
for x in line.endpoints:
x.installSceneEventFilter(line)
了解响应鼠标移动时调用这些方法的顺序可能也很有用:
sceneEventFilter
-> mouseMoveEvent
-> itemChange
LineItem.sceneEventFilter
在事件被分派到事件处理程序之前拦截事件。如果它 returns False 它将通过事件系统进行。
Endpoint.mouseMoveEvent
mouseMoveEvent 处理程序现在接收到事件。默认实现或调用 super
将移动项目并发送启用的通知:
Endpoint.itemChange
此处通知项目 ItemScenePositionHasChanged,(一个 read-only 通知)
线的 children 端点这一事实并不意味着什么:parent 并不 神奇地 知道你想要它根据其 children.
的位置而改变
在您的情况下,您只添加了两个端点,但没有什么可以阻止您添加其他 child 项:parent 怎么知道只要 children 中的任何一个被移动就该做什么?
如果您想根据端点更新线路,您需要自行实施,方法是:
- 拦截位置变化,通过设置
ItemSendsGeometryChanges
标志并捕获 ItemPositionHasChanged
覆盖 itemChange()
;
- 通知 parent 职位变动;
考虑到像 QGraphicsLineItem 和 QGraphicsEllipseItem 这样的基本 QGraphicsItem 不是 从 QObject 继承,你不能使用信号,但如果你确定这些项目将 always 是你的 Lineitem children class,然后你可以安全地调用一个 parent item 函数来重建新行:
class Lineitem(QGraphicsLineItem):
def __init__(self):
super().__init__()
self.setLine(0, 0, 500, 0)
self.setPen(QPen(QColor(0, 0, 0), 5 ))
self.setFlag(self.ItemIsMovable)
self.setFlag(self.ItemIsSelectable)
self.endpoints = []
for i in range(2):
endpoint = Endpoint(self)
endpoint.setParentItem(self)
self.endpoints.append(endpoint)
self.endpoints[1].setPos(500, 0)
def updateLine(self):
self.setLine(QLineF(self.endpoints[0].pos(), self.endpoints[1].pos()))
class Endpoint(QGraphicsEllipseItem):
def __init__(self, parent):
super().__init__(parent)
self.setRect(-5, -5, 10, 10)
self.setBrush(QColor(0, 0, 255))
self.setFlag(self.ItemIsMovable)
self.setFlag(self.ItemIsSelectable)
self.setFlag(self.ItemSendsGeometryChanges)
def itemChange(self, change, value):
if change == self.ItemPositionHasChanged:
self.parentItem().updateLine()
return super().itemChange(change, value)
请注意,我更改了椭圆的矩形,以使它们始终以项目坐标系的原点 (0, 0)
为中心。
这可确保您无论何时移动它们,它们始终位于正确的坐标处,而不会试图将它们“固定”到一半大小。
我有一个图形场景和 2 类 条线和端点,但是当我移动线的端点时出现问题。当我移动线时,端点会移动,但是当移动端点时,它们就会离开线,即使我将其设为父线。我希望它双向工作,所以当移动端点时,线会移动到它,因此它可以改变长度和斜率。
class Lineitem(QGraphicsLineItem):
def __init__(self):
super().__init__()
self.setLine(0, 0, 500, 0)
self.setPen(QPen(QColor(0, 0, 0), 5 ))
self.setFlag(self.ItemIsMovable)
self.setFlag(self.ItemIsSelectable)
self.endpoints = []
for i in range(2):
endpoint = Endpoint(self)
endpoint.setParentItem(self)
self.endpoints.append(endpoint)
self.endpoints[0].setPos(-5, -5)
self.endpoints[1].setPos(495, -5)
class Endpoint(QGraphicsEllipseItem):
def __init__(self, parent):
super().__init__(parent)
self.setRect(0, 0, 10, 10 )
self.setBrush(QColor(0, 0, 255))
self.setFlag(self.ItemIsMovable)
self.setFlag(self.ItemIsSelectable)
这是主窗口:
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.gscene = QGraphicsScene(0, 0, 1000, 1000)
self.line = Lineitem()
self.line.setPos(300, 300)
self.gview = QGraphicsView(self.gscene)
self.gscene.addItem(self.line)
self.setCentralWidget(self.gview)
这是预期的行为:child 项目与其 parent 项目一起移动,而不是相反。如果你想根据端点调整线,你需要编写一个函数来做到这一点。
class LineItem(QGraphicsLineItem):
# ...
def updateLine(self):
p1, p2 = self.endpoints
self.setLine(QLineF(p1.pos(), p2.pos()))
现在 how/where 有几个不同的选项来调用此函数(它们都实现相同的结果,但您可能更喜欢一个或发现一个更适合您的项目)。
在 CHILD 子类中
选项 1) 重新实现 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()
另请注意,我偏移了 rect
,因此 pos()
实际上位于项目的中心。
选项 2) 设置标志 ItemSendsScenePositionChanges
,重新实现 itemChange
以捕获 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)
在 PARENT 子类中
选项 3) 调用 setFiltersChildEvents(True)
并重新实现 sceneEventFilter
以捕获 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()))
请注意 setFiltersChildEvents
将从所有 child 项中过滤事件,您可能需要检查 if obj in self.endpoints
。或者在每个 child 上调用 installSceneEventFilter(parent)
,它只能在 parent 添加到场景后使用。所以会在场景中添加线条的范围内完成,如:
line = LineItem()
scene.addItem(line)
for x in line.endpoints:
x.installSceneEventFilter(line)
了解响应鼠标移动时调用这些方法的顺序可能也很有用:
sceneEventFilter
-> mouseMoveEvent
-> itemChange
LineItem.sceneEventFilter
在事件被分派到事件处理程序之前拦截事件。如果它 returns False 它将通过事件系统进行。Endpoint.mouseMoveEvent
mouseMoveEvent 处理程序现在接收到事件。默认实现或调用super
将移动项目并发送启用的通知:Endpoint.itemChange
此处通知项目 ItemScenePositionHasChanged,(一个 read-only 通知)
线的 children 端点这一事实并不意味着什么:parent 并不 神奇地 知道你想要它根据其 children.
的位置而改变
在您的情况下,您只添加了两个端点,但没有什么可以阻止您添加其他 child 项:parent 怎么知道只要 children 中的任何一个被移动就该做什么?
如果您想根据端点更新线路,您需要自行实施,方法是:
- 拦截位置变化,通过设置
ItemSendsGeometryChanges
标志并捕获ItemPositionHasChanged
覆盖itemChange()
; - 通知 parent 职位变动;
考虑到像 QGraphicsLineItem 和 QGraphicsEllipseItem 这样的基本 QGraphicsItem 不是 从 QObject 继承,你不能使用信号,但如果你确定这些项目将 always 是你的 Lineitem children class,然后你可以安全地调用一个 parent item 函数来重建新行:
class Lineitem(QGraphicsLineItem):
def __init__(self):
super().__init__()
self.setLine(0, 0, 500, 0)
self.setPen(QPen(QColor(0, 0, 0), 5 ))
self.setFlag(self.ItemIsMovable)
self.setFlag(self.ItemIsSelectable)
self.endpoints = []
for i in range(2):
endpoint = Endpoint(self)
endpoint.setParentItem(self)
self.endpoints.append(endpoint)
self.endpoints[1].setPos(500, 0)
def updateLine(self):
self.setLine(QLineF(self.endpoints[0].pos(), self.endpoints[1].pos()))
class Endpoint(QGraphicsEllipseItem):
def __init__(self, parent):
super().__init__(parent)
self.setRect(-5, -5, 10, 10)
self.setBrush(QColor(0, 0, 255))
self.setFlag(self.ItemIsMovable)
self.setFlag(self.ItemIsSelectable)
self.setFlag(self.ItemSendsGeometryChanges)
def itemChange(self, change, value):
if change == self.ItemPositionHasChanged:
self.parentItem().updateLine()
return super().itemChange(change, value)
请注意,我更改了椭圆的矩形,以使它们始终以项目坐标系的原点 (0, 0)
为中心。
这可确保您无论何时移动它们,它们始终位于正确的坐标处,而不会试图将它们“固定”到一半大小。