单个 qTableView pyqt5 中的多个上下文菜单
Multiple context menu in a single qTableView pyqt5
我在两个不同的按钮中添加了两个功能。对于这两个功能,我都使用了一个通用的 qtableView。
我创建了两个上下文菜单,每当用户单击 pushButton1
时调用 func1
并且结果集将显示在 qtableView
中并且用户可以 select 来自 contextmenu1 的项目和 pushButton2
,其中 contextMenu2
将出现。但在我的代码上下文菜单中没有正确显示。
for ex:
如果用户点击按钮 1,则两个数据集的上下文菜单相同。所以上下文菜单取决于先点击哪个按钮。
select编辑项目后菜单也没有很快关闭,为什么!
main.py
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.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.tableView.setContextMenuPolicy(Qt.CustomContextMenu)
self.tableView.customContextMenuRequested.connect(self.onCustomContextMenuRequested)
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.tableView.customContextMenuRequested.connect(self.onCustomContextMenuRequested_2)
def onCustomContextMenuRequested(self, pos):
index = self.tableView.indexAt(pos)
if index.isValid():
menu = QMenu()
CutAction = menu.addAction("&function 3")
CutAction2 = menu.addAction("&function 4")
menu.addAction(CutAction)
menu.addAction(CutAction2)
CutAction.triggered.connect(self.function4)
menu.exec_(self.tableView.viewport().mapToGlobal(pos))
menu.close()
def onCustomContextMenuRequested_2(self, pos):
index = self.tableView.indexAt(pos)
if index.isValid():
menu = QMenu()
CutAction = menu.addAction("&function 5")
CutAction2 = menu.addAction("&function 6")
menu.addAction(CutAction)
menu.addAction(CutAction2)
CutAction.triggered.connect(self.function5)
menu.exec_(self.tableView.viewport().mapToGlobal(pos))
menu.close()
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_())
每个信号连接都会触发目标槽(函数)。如果将同一个信号连接到两个函数,两个函数都会被调用,即使您两次连接到 same 函数也是如此。事实上,当您说“select[ing] 项目后菜单没有快速关闭[d]”时,事实是菜单被多次打开。您可以很容易地检查是否只需向每个函数添加 print
语句。
根据情况有不同的解决方案(不幸的是,您的代码和函数命名不是很冗长,所以我们无法真正了解每个函数的作用)。
断开与 所有 个其他插槽的连接
这可以通过在信号上调用一个简单的 disconnect()
来完成,它与 所有 其他(已经)连接的插槽断开连接。请注意,如果不存在连接,这将引发异常,因此应在 try/except 块内完成:
def func1(self):
# ...
try:
self.tableView.customContextMenuRequested.disconnect()
except TypeError:
pass
self.tableView.customContextMenuRequested.connect(self.onCustomContextMenuRequested)
显然,func2
也是如此。
断开与 已知 个插槽的连接
与上一点类似,但仅断开与 other 个插槽的连接。
请注意,这在您的情况下效果不佳,因为您通过单击按钮进行连接(这可能发生不止一次,导致每次 调用菜单功能 单击按钮,如上所述)。
连接到单个插槽,用变量来选择菜单
对于这种情况,这可能是最好和最安全的方法。
与其不断更改连接目标,不如设置一个变量来指定引用哪个模型,并使用一个函数来根据该变量决定使用哪个菜单。
请注意,在这种情况下,我直接在 setupUi
中设置了上下文策略(实际上可以在 Designer 中完成)和连接,但实际上 none 这应该 永远完成(稍后会详细介绍)。
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
# ...
self.tableView.setContextMenuPolicy(Qt.CustomContextMenu)
self.tableView.customContextMenuRequested.connect(
self.onCustomContextMenuRequested)
def func1(self):
self.target = 1
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)
def func2(self):
self.target = 2
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)
def onCustomContextMenuRequested(self, pos):
index = self.tableView.indexAt(pos)
if not index.isValid():
return
menu = QMenu()
if self.target == 1:
CutAction = menu.addAction("&function 3")
CutAction2 = menu.addAction("&function 4")
menu.addAction(CutAction)
menu.addAction(CutAction2)
CutAction.triggered.connect(self.function4)
elif self.target == 2:
CutAction = menu.addAction("&function 5")
CutAction2 = menu.addAction("&function 6")
menu.addAction(CutAction)
menu.addAction(CutAction2)
CutAction.triggered.connect(self.function5)
menu.exec_(self.tableView.viewport().mapToGlobal(pos))
另一种可能是为模型设置属性,但概念几乎相同。
创建不同的永久菜单,但附加到模型
与上面类似,您可以为每个模型创建不同的菜单并使它们成为它们的属性,而不是设置变量(应该更冗长):
def func1(self):
# ...
menu = QMenu()
CutAction = menu.addAction("&function 3")
CutAction2 = menu.addAction("&function 4")
menu.addAction(CutAction)
menu.addAction(CutAction2)
CutAction.triggered.connect(self.function4)
self.model.menu = menu
def onCustomContextMenuRequested(self, pos):
index = self.tableView.indexAt(pos)
if index.isValid():
self.tableView.model().menu.exec_(
self.tableView.viewport().mapToGlobal(pos))
请注意,如果您正确使用 pyuic 文件而不是尝试编辑它们,上述任何实现都可能更简单(并且 OOP 一致),出于多种原因,这被认为是不好的做法,并且这种特殊情况,因为它经常使事情过于复杂,并可能造成对象结构、功能目的等方面的混乱。例如,信号连接应该不在retranslateUi
内完成,它有一个完全不同的目的。阅读更多关于 using Designer.
我在两个不同的按钮中添加了两个功能。对于这两个功能,我都使用了一个通用的 qtableView。
我创建了两个上下文菜单,每当用户单击 pushButton1
时调用 func1
并且结果集将显示在 qtableView
中并且用户可以 select 来自 contextmenu1 的项目和 pushButton2
,其中 contextMenu2
将出现。但在我的代码上下文菜单中没有正确显示。
for ex:
如果用户点击按钮 1,则两个数据集的上下文菜单相同。所以上下文菜单取决于先点击哪个按钮。
select编辑项目后菜单也没有很快关闭,为什么!
main.py
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.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.tableView.setContextMenuPolicy(Qt.CustomContextMenu)
self.tableView.customContextMenuRequested.connect(self.onCustomContextMenuRequested)
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.tableView.customContextMenuRequested.connect(self.onCustomContextMenuRequested_2)
def onCustomContextMenuRequested(self, pos):
index = self.tableView.indexAt(pos)
if index.isValid():
menu = QMenu()
CutAction = menu.addAction("&function 3")
CutAction2 = menu.addAction("&function 4")
menu.addAction(CutAction)
menu.addAction(CutAction2)
CutAction.triggered.connect(self.function4)
menu.exec_(self.tableView.viewport().mapToGlobal(pos))
menu.close()
def onCustomContextMenuRequested_2(self, pos):
index = self.tableView.indexAt(pos)
if index.isValid():
menu = QMenu()
CutAction = menu.addAction("&function 5")
CutAction2 = menu.addAction("&function 6")
menu.addAction(CutAction)
menu.addAction(CutAction2)
CutAction.triggered.connect(self.function5)
menu.exec_(self.tableView.viewport().mapToGlobal(pos))
menu.close()
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_())
每个信号连接都会触发目标槽(函数)。如果将同一个信号连接到两个函数,两个函数都会被调用,即使您两次连接到 same 函数也是如此。事实上,当您说“select[ing] 项目后菜单没有快速关闭[d]”时,事实是菜单被多次打开。您可以很容易地检查是否只需向每个函数添加 print
语句。
根据情况有不同的解决方案(不幸的是,您的代码和函数命名不是很冗长,所以我们无法真正了解每个函数的作用)。
断开与 所有 个其他插槽的连接
这可以通过在信号上调用一个简单的 disconnect()
来完成,它与 所有 其他(已经)连接的插槽断开连接。请注意,如果不存在连接,这将引发异常,因此应在 try/except 块内完成:
def func1(self):
# ...
try:
self.tableView.customContextMenuRequested.disconnect()
except TypeError:
pass
self.tableView.customContextMenuRequested.connect(self.onCustomContextMenuRequested)
显然,func2
也是如此。
断开与 已知 个插槽的连接
与上一点类似,但仅断开与 other 个插槽的连接。
请注意,这在您的情况下效果不佳,因为您通过单击按钮进行连接(这可能发生不止一次,导致每次 调用菜单功能 单击按钮,如上所述)。
连接到单个插槽,用变量来选择菜单
对于这种情况,这可能是最好和最安全的方法。
与其不断更改连接目标,不如设置一个变量来指定引用哪个模型,并使用一个函数来根据该变量决定使用哪个菜单。
请注意,在这种情况下,我直接在 setupUi
中设置了上下文策略(实际上可以在 Designer 中完成)和连接,但实际上 none 这应该 永远完成(稍后会详细介绍)。
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
# ...
self.tableView.setContextMenuPolicy(Qt.CustomContextMenu)
self.tableView.customContextMenuRequested.connect(
self.onCustomContextMenuRequested)
def func1(self):
self.target = 1
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)
def func2(self):
self.target = 2
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)
def onCustomContextMenuRequested(self, pos):
index = self.tableView.indexAt(pos)
if not index.isValid():
return
menu = QMenu()
if self.target == 1:
CutAction = menu.addAction("&function 3")
CutAction2 = menu.addAction("&function 4")
menu.addAction(CutAction)
menu.addAction(CutAction2)
CutAction.triggered.connect(self.function4)
elif self.target == 2:
CutAction = menu.addAction("&function 5")
CutAction2 = menu.addAction("&function 6")
menu.addAction(CutAction)
menu.addAction(CutAction2)
CutAction.triggered.connect(self.function5)
menu.exec_(self.tableView.viewport().mapToGlobal(pos))
另一种可能是为模型设置属性,但概念几乎相同。
创建不同的永久菜单,但附加到模型
与上面类似,您可以为每个模型创建不同的菜单并使它们成为它们的属性,而不是设置变量(应该更冗长):
def func1(self):
# ...
menu = QMenu()
CutAction = menu.addAction("&function 3")
CutAction2 = menu.addAction("&function 4")
menu.addAction(CutAction)
menu.addAction(CutAction2)
CutAction.triggered.connect(self.function4)
self.model.menu = menu
def onCustomContextMenuRequested(self, pos):
index = self.tableView.indexAt(pos)
if index.isValid():
self.tableView.model().menu.exec_(
self.tableView.viewport().mapToGlobal(pos))
请注意,如果您正确使用 pyuic 文件而不是尝试编辑它们,上述任何实现都可能更简单(并且 OOP 一致),出于多种原因,这被认为是不好的做法,并且这种特殊情况,因为它经常使事情过于复杂,并可能造成对象结构、功能目的等方面的混乱。例如,信号连接应该不在retranslateUi
内完成,它有一个完全不同的目的。阅读更多关于 using Designer.