【问题标题】:How can I delete this QGraphicsLineItem when context menu is open in a QGraphicsPixmapItem?在 QGraphicsPixmapItem 中打开上下文菜单时,如何删除此 QGraphicsLineItem?
【发布时间】:2021-11-12 18:42:35
【问题描述】:

我正在开发一个 GUI,您可以在其中连接节点之间的节点,但在少数特殊情况下除外。这个实现在大多数情况下工作得很好,但是经过一些测试后我发现,当我通过 QGraphicsLineItem 将一个 QGraphicsPixmapItem 与另一个连接时,并且用户在完成链接之前打开上下文菜单时,线路卡住了,它不能已删除。

链接两个元素的过程是先按下元素,然后在移动线的同时按住,当指针在另一个元素上时释放。这是分别使用 mousePressEvent、mouseMoveEvent 和 mouseReleaseEvent 实现的。

此代码是一个示例:

#!/usr/bin/env python3

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

import sys


class Ellipse(QGraphicsEllipseItem):
    def __init__(self, x, y):
        super(Ellipse, self).__init__(x, y, 30, 30)

        self.setBrush(QBrush(Qt.darkBlue))
        self.setFlag(QGraphicsItem.ItemIsMovable)
        self.setZValue(100)

    def contextMenuEvent(self, event):
        menu = QMenu()

        first_action = QAction("First action")
        second_action = QAction("Second action")

        menu.addAction(first_action)
        menu.addAction(second_action)

        action = menu.exec(event.screenPos())


class Link(QGraphicsLineItem):
    def __init__(self, x, y):
        super(Link, self).__init__(x, y, x, y)

        self.pen_ = QPen()
        self.pen_.setWidth(2)
        self.pen_.setColor(Qt.red)

        self.setPen(self.pen_)

    def updateEndPoint(self, x2, y2):
        line = self.line()
        self.setLine(line.x1(), line.y1(), x2, y2)


class Scene(QGraphicsScene):
    def __init__(self):
        super(Scene, self).__init__()

        self.link = None
        self.link_original_node = None

        self.addItem(Ellipse(200, 400))
        self.addItem(Ellipse(400, 400))

    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            item = self.itemAt(event.scenePos(), QTransform())
            if item is not None:
                self.link_original_node = item
                offset = item.boundingRect().center()
                self.link = Link(item.scenePos().x() + offset.x(), item.scenePos().y() + offset.y())
                self.addItem(self.link)

    def mouseMoveEvent(self, event):
        super().mouseMoveEvent(event)
        if self.link is not None:
            self.link.updateEndPoint(event.scenePos().x(), event.scenePos().y())

    def mouseReleaseEvent(self, event):
        super().mouseReleaseEvent(event)
        if self.link is not None:
            item = self.itemAt(event.scenePos(), QTransform())
            if isinstance(item, (Ellipse, Link)):
                self.removeItem(self.link)
                self.link_original_node = None
                self.link = None


class MainWindow(QMainWindow):
    def __init__(self):
        super(QMainWindow, self).__init__()

        self.scene = Scene()
        self.canvas = QGraphicsView()

        self.canvas.setScene(self.scene)
        self.setCentralWidget(self.canvas)

        self.setGeometry(500, 200, 1000, 600)
        self.setContextMenuPolicy(Qt.NoContextMenu)


app = QApplication(sys.argv)
win = MainWindow()
win.show()
sys.exit(app.exec())

如何在上下文菜单事件之前/之后摆脱掉线?我试图阻止他们,但我不知道怎么做。

【问题讨论】:

  • 似乎只有在椭圆项目上释放链接项目时才会删除链接项目。在任何情况下都不应该将其移除,尤其是当鼠标没有在任何项目上释放时?
  • @musicamante 感谢您的观看!我在代码中犯了一个错误,现在它是正确的。目标是在任何情况下都不将它们之间的元素链接起来,因此如果该行脱离任何元素,则必须将其删除,就像更新的代码一样。该错误仅在上下文菜单打开时发生。
  • 由于该菜单只能用鼠标打开,我想当用户右键单击保持左键时会出现问题按下,对吗?在任何情况下,考虑到在某些情况下订单项可能恰好在光标位置(这是由于位置映射的复杂工作方式),因此,您应该始终在鼠标释放中检查if self.link is not None,并最终基于此将其删除,并且仅依靠itemAt 才能成功链接。
  • @musicamante 没错,在按住左键的同时按右键会出现问题。您提出的解决方案的问题是当用户访问上下文菜单时 mouseReleaseEvent 不会激活。就像链接进入“空白”一样。
  • 这是另一个点:当上下文菜单事件打开菜单时,鼠标会被隐式释放。事实仍然存在,如果该项目存在,则应始终在鼠标释放时将其删除,不仅是在检查鼠标 over 之后:因为它假定它是在鼠标按下时创建的,所以链接项目的位置是无关的。看我的回答。另请注意,虽然您的方法“有效”,但它存在一些问题:例如,根本不调用基本实现mousePressEvent 可能会导致意外行为(尝试双击)。

标签: python-3.x pyqt5 qgraphicslineitem


【解决方案1】:

假设菜单仅通过鼠标按键触发,解决方案是删除mouseButtonPress 中的任何现有链接项。

    def mousePressEvent(self, event):
        if self.link is not None:
            self.removeItem(self.link)
            self.link_original_node = None
            self.link = None
        # ...

请注意,对于非常小的项目,itemAt 并不总是可靠的,因为项目的 shape() 可能会稍微偏离映射的鼠标位置。由于在任何情况下都会删除链接,因此只需在 mouseReleaseEvent() 中执行相同操作即可:

    def mouseReleaseEvent(self, event):
        super().mouseReleaseEvent(event)
        if self.link is not None:
            item = self.itemAt(event.scenePos(), QTransform())
            if isinstance(item, Ellipse):
                # do what you need with the linked ellipses

            # note the indentation level
            self.removeItem(self.link)
            self.link_original_node = None
            self.link = None

【讨论】:

  • 就是这样!只需使用第一段代码,它就起作用了!谢谢!
  • @dpoloa 第二部分也很重要。我已经能够多次重现该问题:在某些角度和距离上,itemAt 返回的item 为 None 即使链接处于活动状态。如果它在鼠标释放时存在,您必须在任何情况下都将其移除,而不仅仅是在鼠标下方。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-02-22
  • 1970-01-01
相关资源
最近更新 更多