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

QGraphicsScene调用blockSignals后selectionChanged仍触发的解决方案

PyQt5 QGraphicsScene selectionChanged 槽函数递归触发通用解决方案

你遇到的问题本质是槽函数内部修改选中状态的操作,再次触发了同一个selectionChanged信号,导致槽函数递归执行。以下是两种通用、可复用于所有同类信号槽递归场景的解决方案:

方案1:重入标志位(全版本兼容)

通过自定义布尔标志位标记槽函数的执行状态,从逻辑上拦截递归调用,兼容性最好,支持所有PyQt5版本。

实现步骤:

  1. 类初始化时新增一个私有布尔属性_in_selection_slot,默认值为False
  2. 进入槽函数时先判断标志位:如果为True说明是递归调用,直接返回
  3. 标记标志位为True后执行业务逻辑,用try...finally保证标志位最终一定会被复位

代码示例:

class Template(QMainWindow):
    def __init__(self):
        super().__init__()
        self._in_selection_slot = False # 新增重入标志位
        self.scene = QGraphicsScene(0, 0, 200, 400)
        c = self.scene.addEllipse(8, 8, 34, 34, Qt.black, Qt.black)
        c.setPos(0, 350)
        c.setFlag(QGraphicsItem.ItemIsSelectable)
        self.scene.selectionChanged.connect(self.move)
        
        view = QGraphicsView(self.scene)
        view.setRenderHint(QPainter.Antialiasing)
        self.setCentralWidget(view)   

    def move(self):
        # 递归调用直接拦截
        if self._in_selection_slot:
            return
        # 标记进入槽函数
        self._in_selection_slot = True
        try:
            # 原有业务逻辑完全不用改
            selected = self.scene.selectedItems()
            if selected:
                selected[0].moveBy(0, -50)
                selected[0].setSelected(False)
        finally:
            # 无论是否出现异常,最终复位标志位
            self._in_selection_slot = False

适用场景:

所有需要避免槽函数递归调用的场景,不需要关心信号发射源,哪怕逻辑中会触发多个对象的多个信号都可以正常拦截。

方案2:QSignalBlocker(Qt 5.7+ 适用)

Qt内置的RAII工具类,创建时自动阻塞指定对象的所有信号,对象销毁时自动恢复信号状态,不需要手动调用blockSignals,避免手动操作遗漏导致的信号永久阻塞问题。

代码示例:

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

class Template(QMainWindow):
    def __init__(self):
        super().__init__()
        self.scene = QGraphicsScene(0, 0, 200, 400)
        c = self.scene.addEllipse(8, 8, 34, 34, Qt.black, Qt.black)
        c.setPos(0, 350)
        c.setFlag(QGraphicsItem.ItemIsSelectable)
        self.scene.selectionChanged.connect(self.move)
        
        view = QGraphicsView(self.scene)
        view.setRenderHint(QPainter.Antialiasing)
        self.setCentralWidget(view)   

    def move(self):
        selected = self.scene.selectedItems()
        if selected:
            selected[0].moveBy(0, -50)
            # 阻塞scene的所有信号,当前作用域结束后自动恢复
            blocker = QSignalBlocker(self.scene)
            selected[0].setSelected(False)

适用场景:

明确知道信号发射源的场景,代码更简洁,不需要维护额外的状态变量。


内容的提问来源于stack exchange,提问作者alec

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.10 15:45:01