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

PyQt5槽函数首次调用后参数不更新问题排查

问题原因
  • 核心错误是重复绑定信号且未断开旧连接:每次触发单元格选中逻辑onCellSelect时,你都会给taggingTable.itemChanged信号新增一个lambda槽连接,但从未断开之前的旧连接。Qt信号支持绑定多个槽,信号触发时所有历史绑定的槽会按绑定顺序依次执行,所以第一次绑定的、持有旧xStart和旧rowVal的槽永远会被调用,后续新选中单元格绑定的新槽也会同时执行,直接导致始终操作第一次编辑的区间,旧key被删除后再访问就触发KeyError崩溃。
  • 冗余的inCounter防重入逻辑不可靠,本质是为了掩盖重复绑定槽导致的多次触发问题,没有解决根本原因。
  • 设计逻辑不合理:信号只需要在初始化时绑定一次,不需要每次选中单元格都重复绑定。
正确实现方案
  • 初始化阶段只绑定一次itemChanged信号,不要在选中单元格的回调里重复绑定。
  • 新增类成员变量self.currentEditRow记录当前选中的行号,选中单元格时仅更新这个变量的值即可。
  • 编辑单元格触发槽函数时,必要时先阻塞表格的信号避免递归触发,再根据当前选中行读取新旧区间值,更新绘图上的高亮区域,最后恢复信号、刷新画布。
  • 操作highlights字典时,先匹配取出旧区间对象再删除key,避免key不存在的报错。
修正后的可运行代码
import sys
from PyQt5.QtWidgets import *
from PyQt5 import QtCore
from PyQt5.QtCore import *
from PyQt5.QtGui import *

from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import matplotlib.widgets as mwidgets

class Window(QMainWindow):
    def __init__(self, parent = None):
        super(Window, self).__init__(parent)
        self.resize(1600, 800)
        self.MyUI()
        
    def MyUI(self):
        canvas = Canvas(self, width=14, height=12, dpi=100)
        canvas.move(0,0)

class Canvas(FigureCanvas):
    def __init__(self, parent, width = 14, height = 12, dpi = 100):
        Plot = Figure(figsize=(width, height), dpi=dpi)
        self.Axes = Plot.add_subplot(111)
        self.Axes.set_position([0.05, 0.58, 0.66, 0.55])
        self.rowCount = 0
        self.currentEditRow = -1 # 记录当前选中编辑的行
        super().__init__(Plot)
        self.setParent(parent)

        # 绘制基础曲线
        self.Axes.plot([0,1,2,3,4], [3, 4, 5, 6, 7])
        self.Axes.set_xlabel('Frame', fontsize = 10) 
        self.Axes.grid()
        self.Axes.set_aspect(1)
        Plot.canvas.draw()

        # 高亮区间存储结构
        class highlight:
            def __init__(self, tag, xStart, xEnd, highlightObj):
                self.tag = tag
                self.xStart = xStart
                self.xEnd = xEnd
                self.highlightObj = highlightObj
        self.highlights = {}

        # 初始化编辑表格
        self.taggingTable = QTableWidget(self)
        self.taggingTable.setColumnCount(3)
        self.taggingTable.setRowCount(100)
        self.taggingTable.setGeometry(QRect(1005,85, 330, 310))
        self.taggingTable.setHorizontalHeaderLabels(['Behavior','Start Frame', 'End Frame'])

        # 区间选择回调
        def onHighlight(xStart, xEnd):
            tagName = "No Tag"
            self.taggingTable.blockSignals(True) # 阻塞信号避免触发编辑回调
            self.taggingTable.setItem(self.rowCount, 0, QTableWidgetItem(tagName))
            self.taggingTable.setItem(self.rowCount, 1, QTableWidgetItem(str(int(xStart))))
            self.taggingTable.setItem(self.rowCount, 2, QTableWidgetItem(str(int(xEnd))))
            self.taggingTable.blockSignals(False)
            self.rowCount = self.rowCount + 1
            highlightObj = self.Axes.axvspan(xStart, xEnd, color = 'blue', alpha = 0.5)
            self.highlights[int(xStart)] = highlight(tagName, xStart, xEnd, highlightObj)
            self.draw_idle()
                
        self.span = mwidgets.SpanSelector(self.Axes, onHighlight, "horizontal", 
                                        interactive = True, useblit=True, props=dict(alpha=0.5, facecolor="blue"),)

        # 单元格内容修改回调:初始化时只绑定一次
        def onItemChanged(item):
            # 只处理起始帧列的修改
            if item.column() != 1 or self.currentEditRow < 0:
                return
            rowVal = self.currentEditRow
            # 匹配读取旧起始值
            oldXStart = None
            for k in list(self.highlights.keys()):
                if abs(self.highlights[k].xEnd - int(self.taggingTable.item(rowVal,2).text())) < 1e-6:
                    oldXStart = k
                    break
            if oldXStart is None:
                return
            xStartNew = int(item.text())
            xEnd = self.highlights[oldXStart].xEnd
            tagName = self.highlights[oldXStart].tag
            # 删除旧高亮
            self.highlights[oldXStart].highlightObj.remove()
            del self.highlights[oldXStart]
            # 添加新高亮
            highlightObj = self.Axes.axvspan(xStartNew, xEnd, color = 'blue', alpha = 0.5)
            self.highlights[xStartNew] = highlight(tagName, xStartNew, xEnd, highlightObj)
            self.taggingTable.clearSelection()
            self.currentEditRow = -1
            self.draw_idle()

        # 单元格选中回调:只更新当前编辑行号,不重复绑定信号
        def onCellSelect():
            index = self.taggingTable.selectedIndexes()
            if len(index) != 0:
                rowVal = index[0].row()
                if self.taggingTable.item(rowVal, 1) is not None:
                    self.currentEditRow = rowVal

        # 统一绑定所有信号,仅绑定一次
        self.taggingTable.selectionModel().selectionChanged.connect(onCellSelect)
        self.taggingTable.itemChanged.connect(onItemChanged)
        self.draw_idle()


app = QApplication(sys.argv)
window = Window()
window.show()
app.exec()

补充说明:如果需要同时支持结束帧xEnd的编辑,只需要在onItemChanged槽里增加列判断逻辑,对应更新xEnd值和高亮区间即可,不需要新增重复的信号绑定。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 12:54:32