当QGraphicsPixmapItem打开上下文菜单时,如何删除对应的QGraphicsLineItem?
修复连线拖拽时触发上下文菜单导致残留的问题
问题原因
拖拽连线过程中触发右键上下文菜单时,菜单的exec()方法会进入模态事件循环,此时鼠标释放事件会被菜单拦截,不会触发QGraphicsScene的mouseReleaseEvent,连线相关的状态变量不会被重置,最终导致连线残留无法删除。
修复方案
步骤1:给Scene类新增统一的连线清理方法
将未完成连线的删除、状态重置逻辑抽为公共方法,方便多场景调用:
class Scene(QGraphicsScene): # 原有__init__等方法不变 def clear_pending_link(self): if self.link is not None: self.removeItem(self.link) self.link = None self.link_original_node = None
步骤2:在上下文菜单触发前调用清理方法
修改Ellipse类的contextMenuEvent方法,弹出菜单前先清理未完成的连线:
def contextMenuEvent(self, event): # 弹出菜单前先清理未完成连线 scene = self.scene() scene.clear_pending_link() 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())
步骤3(可选兜底):覆盖Scene的右键事件做全局清理
如果你的场景支持空白处右键弹出菜单,可以重写Scene的contextMenuEvent做全局兜底:
class Scene(QGraphicsScene): # 原有方法不变 def contextMenuEvent(self, event): self.clear_pending_link() super().contextMenuEvent(event)
步骤4(可选优化):统一mouseReleaseEvent的逻辑
原来的mouseReleaseEvent只有命中节点/连线才会删除连线,你可以改成不管释放到哪里都调用清理方法,避免空白处释放也残留的问题:
def mouseReleaseEvent(self, event): super().mouseReleaseEvent(event) if self.link is not None: item = self.itemAt(event.scenePos(), QTransform()) if isinstance(item, Ellipse): # 这里可以加连线成功的逻辑,比如保存两个节点的关联关系 pass # 不管连线是否成功,都清理临时的拖拽连线 self.clear_pending_link()
完整可运行修改后代码
#!/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): # 弹出菜单前清理未完成连线 self.scene().clear_pending_link() menu = QMenu() first_action = QAction("First action") second_action = QAction("Second action") menu.addAction(first_action) menu.addAction(second_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 clear_pending_link(self): """统一清理未完成的拖拽连线""" if self.link is not None: self.removeItem(self.link) self.link = None self.link_original_node = None def contextMenuEvent(self, event): """空白处触发菜单的兜底清理""" self.clear_pending_link() super().contextMenuEvent(event) 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) and item != self.link_original_node: # 连线成功逻辑可在这里添加 print(f"成功连接节点{self.link_original_node}和节点{item}") # 不管结果都清理临时连线 self.clear_pending_link() 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())
内容的提问来源于stack exchange,提问作者dpoloa
相关产品推荐
相关产品推荐

