单个 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.