【问题标题】:How to move line with endpoints in PyQt5 [closed]如何在 PyQt5 中使用端点移动线[关闭]
【发布时间】:2021-10-09 21:02:53
【问题描述】:

我有一个图形场景和 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)

【问题讨论】:

标签: python pyqt5 qgraphicsscene


【解决方案1】:

这是预期的行为:子项与其父项一起移动,而不是相反。如果你想根据端点调整线,你需要编写一个函数来做到这一点。

class LineItem(QGraphicsLineItem):
    # ...
    def updateLine(self):
        p1, p2 = self.endpoints
        self.setLine(QLineF(p1.pos(), p2.pos()))

现在有几个不同的选项用于调用此函数的方式/位置(它们都实现了相同的结果,但您可能更喜欢一个或发现一个更适合您的项目)。

在子类中

选项 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)

在父子类中

选项 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 将过滤来自所有子项的事件,您可能需要检查if obj in self.endpoints。或者在每个孩子上调用installSceneEventFilter(parent),这只能在将父母添加到场景后使用。所以会在添加线到场景的范围内完成,比如:

line = LineItem()
scene.addItem(line)
for x in line.endpoints:
    x.installSceneEventFilter(line)

此外,了解这些方法响应鼠标移动的调用顺序可能很有用:

sceneEventFilter -> mouseMoveEvent -> itemChange

  1. LineItem.sceneEventFilter 在事件被分派到事件处理程序之前拦截事件。如果它返回 False,它将通过事件系统进行。

  2. Endpoint.mouseMoveEvent 现在在 mouseMoveEvent 处理程序中接收到该事件。默认实现或调用super 将移动项目并发送启用通知:

  3. Endpoint.itemChange这里通知item ItemScenePositionHasChanged,(只读通知)

【讨论】:

  • 请注意,使用显式坐标调用 QGraphicsLineItem 的超 __init__ 并且仍然添加 args 不是一个好主意:允许位置参数意味着您提供该功能,如果您在构造函数中添加任何标准参数,您将获得 TypeError。
  • 还值得注意的是,虽然使用鼠标事件作为触发器是个好主意,但如果必须以编程方式定位端点,则可能不够。
  • @musicamante 好点。我已经多次使用 args 和 kwargs,我经常不假思索地把它扔进去
【解决方案2】:

端点是线的子级这一事实并不意味着什么:父级并不神奇地知道您希望它根据其子级的位置进行更改。
在您的情况下,您只添加了两个端点,但没有什么能阻止您添加其他子项:无论何时移动任何子项,父项如何知道该怎么做?

如果您想根据需要自行实现的端点更新线路,通过:

  • 拦截位置变化,通过设置ItemSendsGeometryChanges标志并捕获ItemPositionHasChanged覆盖itemChange()
  • 通知家长位置变化;

考虑到像 QGraphicsLineItem 和 QGraphicsEllipseItem 这样的基本 QGraphicsItems 继承自 QObject,您不能使用信号,但如果您确定这些项目将始终 成为您的 Lineitem 类的子项,然后您可以安全地调用将重建新行的父项函数:

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) 为中心。
这样可以确保无论何时移动它们,它们始终位于正确的坐标上,而无需尝试将它们“修复”一半大小。

【讨论】:

  • 哇,我们基本上同时发布了相同的答案
  • @alec 是的,我注意到了...而且,还使用了相同的函数名 ;-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-15
  • 1970-01-01
相关资源
最近更新 更多