You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

当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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 00:57:04