You need to enable JavaScript to run this app. PyQt5单个QTableView绑定多套上下文菜单显示异常问题求助
优惠活动
大模型
产品
解决方案
定价
更多

PyQt5单个QTableView绑定多套上下文菜单显示异常问题求助

问题根因

两个异常的核心诱因都来自信号槽的重复绑定,附加少量代码逻辑冗余:

  • 你每次调用func1/func2时,都会给同一个QTableView的customContextMenuRequested信号绑定新的槽函数。Qt的信号槽默认支持多绑定,触发信号时所有绑定的槽函数会按绑定顺序依次执行:先点按钮1绑定了菜单1的槽,再点按钮2绑定菜单2的槽,后续右键会先弹出菜单1,关闭后菜单2又会弹出,视觉上表现为菜单始终不切换、关闭后还会弹出,看起来关不掉。
  • 额外冗余逻辑:你在创建菜单时已经通过menu.addAction()返回了动作对象,后续又重复调用menu.addAction()添加同一个动作;且menu.exec_()是阻塞调用,用户选择菜单项/点击空白关闭菜单后方法才会返回,菜单会自动销毁,无需手动调用menu.close()。
修复方案

推荐使用「状态标记+统一槽函数」的方案,逻辑更清晰,也不会出现信号槽解绑遗漏的问题:

  1. 新增一个变量标记当前展示的是哪套数据,在func1/func2中修改这个标记
  2. 只在初始化时绑定一次右键菜单信号,在统一的槽函数中根据标记生成对应菜单
  3. 移除冗余的重复添加动作、手动关闭菜单的代码

修改后完整可运行代码

from PyQt5 import QtCore, QtWidgets
import sys
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMenu

class TableModel(QtCore.QAbstractTableModel):
    def __init__(self, data):
        super(TableModel, self).__init__()
        self._data = data
    def data(self, index, role):
        if role == Qt.DisplayRole:
            return self._data[index.row()][index.column()]
    def rowCount(self, index):
        return len(self._data)
    def columnCount(self, index):
        return len(self._data[0])


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(800, 600)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout.setObjectName("verticalLayout")
        self.frame = QtWidgets.QFrame(self.centralwidget)
        self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
        self.frame.setFrameShadow(QtWidgets.QFrame.Raised)
        self.frame.setObjectName("frame")
        self.horizontalLayout = QtWidgets.QHBoxLayout(self.frame)
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.pushButton1 = QtWidgets.QPushButton(self.frame)
        self.pushButton1.setObjectName("pushButton1")
        self.horizontalLayout.addWidget(self.pushButton1)
        self.pushButton_2 = QtWidgets.QPushButton(self.frame)
        self.pushButton_2.setObjectName("pushButton_2")
        self.horizontalLayout.addWidget(self.pushButton_2)
        spacerItem = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout.addItem(spacerItem)
        self.verticalLayout.addWidget(self.frame)
        self.tableView = QtWidgets.QTableView(self.centralwidget)
        self.tableView.setObjectName("tableView")
        self.verticalLayout.addWidget(self.tableView)
        MainWindow.setCentralWidget(self.centralwidget)
        # 新增:初始化右键菜单绑定,只绑一次
        self.tableView.setContextMenuPolicy(Qt.CustomContextMenu)
        self.tableView.customContextMenuRequested.connect(self.onCustomContextMenuRequested)
        # 新增:状态标记,1对应func1的菜单,2对应func2的菜单
        self.current_menu_type = 1
        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
        self.pushButton1.setText(_translate("MainWindow", "PushButton1"))
        self.pushButton_2.setText(_translate("MainWindow", "PushButton2"))
        self.pushButton1.clicked.connect(self.func1)
        self.pushButton_2.clicked.connect(self.func2)

    def func1(self):
        print('funct1 called')
        data = [
          [4, 9, 2],
          [1, 0, 0],
          [3, 5, 0],
          [3, 3, 2],
          [7, 8, 9],
        ]
        
        self.model = TableModel(data)
        self.tableView.setModel(self.model)
        # 修改:更新菜单标记
        self.current_menu_type = 1
       
       
    def func2(self):
        data2 = [
          [0, 0, 0],
          [0, 0, 0],
          [0, 0, 0],
          [0, 0, 0],
          [0, 0, 0],
        ]
        self.model = TableModel(data2)
        self.tableView.setModel(self.model)
        # 修改:更新菜单标记
        self.current_menu_type = 2
        
    def onCustomContextMenuRequested(self, pos):
        index = self.tableView.indexAt(pos)
        if not index.isValid():
            return
        menu = QMenu()
        # 根据标记生成对应菜单
        if self.current_menu_type == 1:
            CutAction  = menu.addAction("&function 3")
            CutAction2 = menu.addAction("&function 4")
            CutAction.triggered.connect(self.function4)
        else:
            CutAction  = menu.addAction("&function 5")
            CutAction2 = menu.addAction("&function 6")
            CutAction.triggered.connect(self.function5)
        # 直接执行菜单,无需手动close
        menu.exec_(self.tableView.viewport().mapToGlobal(pos))

    def function4(self):
        print("function 4")

    def function5(self):
        print("function 5")
if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    MainWindow = QtWidgets.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

如果你坚持要使用动态切换槽的方案,只需要在每次绑定新槽前先断开所有旧的绑定即可,修改func1和func2的绑定逻辑:

# func1中绑定前先断开所有旧连接
try:
    self.tableView.customContextMenuRequested.disconnect()
except:
    pass
self.tableView.customContextMenuRequested.connect(self.onCustomContextMenuRequested)

# func2中绑定前先断开所有旧连接
try:
    self.tableView.customContextMenuRequested.disconnect()
except:
    pass
self.tableView.customContextMenuRequested.connect(self.onCustomContextMenuRequested_2)

这种方案要注意如果信号没有绑定任何槽时调用disconnect()会报错,需要加异常捕获,所以更推荐第一种方案。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.17 20:36:08