PyQt5单个QTableView绑定多套上下文菜单显示异常问题求助
问题根因
两个异常的核心诱因都来自信号槽的重复绑定,附加少量代码逻辑冗余:
- 你每次调用
func1/func2时,都会给同一个QTableView的customContextMenuRequested信号绑定新的槽函数。Qt的信号槽默认支持多绑定,触发信号时所有绑定的槽函数会按绑定顺序依次执行:先点按钮1绑定了菜单1的槽,再点按钮2绑定菜单2的槽,后续右键会先弹出菜单1,关闭后菜单2又会弹出,视觉上表现为菜单始终不切换、关闭后还会弹出,看起来关不掉。 - 额外冗余逻辑:你在创建菜单时已经通过
menu.addAction()返回了动作对象,后续又重复调用menu.addAction()添加同一个动作;且menu.exec_()是阻塞调用,用户选择菜单项/点击空白关闭菜单后方法才会返回,菜单会自动销毁,无需手动调用menu.close()。
修复方案
推荐使用「状态标记+统一槽函数」的方案,逻辑更清晰,也不会出现信号槽解绑遗漏的问题:
- 新增一个变量标记当前展示的是哪套数据,在
func1/func2中修改这个标记 - 只在初始化时绑定一次右键菜单信号,在统一的槽函数中根据标记生成对应菜单
- 移除冗余的重复添加动作、手动关闭菜单的代码
修改后完整可运行代码
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
相关产品推荐
相关产品推荐

