PyQt5 上下文菜单针对不同的应用程序具有不同的操作

PyQt5 Context Menu w different actions for different applications

我有多个带有 QTableView 的 PyQt5 应用程序,它们继承自 QTableView 的子类“MyTable”。通过这样做,我能够在一个地方 (gen_context_menu) 维护上下文菜单的代码,这对所有使用 MyTable 的应用程序都很有用。

但今天我遇到了一个新应用程序的需求,它既能获得 MyTable 中现有上下文菜单的好处,又能获得 new/additional 我不想授予的上下文菜单选项 legacy/universal 应用程序访问。

在下面的 MRE 中,我写了几个虚拟函数来展示我脑海中模糊的实现目标的想法——我想我会封装 'standard menu' 的获取(使用 get_standard_menu ), MyTable 会在 legacy/universal 情况下调用它,然后对于新应用程序,可能会通过参数或 MyTable 的小子类调用 'new' 菜单函数 (get_sending_menu)。当我编写代码时,我意识到由于 'actions' 与菜单定义相结合,它并不像我想象的那样简单,然后我立即想到 'there must be a better way'...

有吗?

import logging
import sys

from PyQt5 import QtWidgets, QtCore
from PyQt5.Qt import QAbstractTableModel, QVariant, Qt, QMainWindow

__log__ = logging.getLogger()


def get_sending_menu():
    menu = get_standard_menu()
    action_send = menu.addAction("Send!")
    return menu


def get_standard_menu():
    menu = QtWidgets.QMenu()
    action_get_ticks = menu.addAction("Get Ticks")
    action_get_events = menu.addAction("Get Events")
    action_get_overview = menu.addAction("Get Overview")
    return menu


class MyModel(QAbstractTableModel):
    rows = [('X'), ('Y'), ('Z')]
    columns = ['Letter']
    
    def rowCount(self, parent):
        return len(MyModel.rows)

    def columnCount(self, parent):
        return len(MyModel.columns)
    
    def data(self, index, role):
        if role != Qt.DisplayRole:
            return QVariant()
        return MyModel.rows[index.row()][index.column()]


class MyTable(QtWidgets.QTableView):
    
    def __init__(self, parent=None, ):
        super().__init__()
        self.tick_windows = []
        self.events_windows = []
        self.overview_windows = []
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.gen_context_menu)
        mre_model = MyModel()
        self.setModel(mre_model)
    
    def gen_context_menu(self, pos):
        index = self.indexAt(pos)
        if not index.isValid() or index.column() != 0:
            return
        input_value = index.sibling(index.row(), index.column()).data()
        menu = QtWidgets.QMenu()
        action_get_ticks = menu.addAction("Get Ticks")
        action_get_events = menu.addAction("Get Events")
        action_get_overview = menu.addAction("Get Overview")
        action = menu.exec_(self.viewport().mapToGlobal(pos))
        if action == action_get_ticks:
            model_window = QMainWindow()
            __log__.info(input_value)
            model_window.show()
            self.tick_windows.append(model_window)
        elif action == action_get_events:
            model_window = QMainWindow()
            __log__.info(input_value)
            model_window.show()
            self.events_windows.append(model_window)
        elif action == action_get_overview:
            model_window = QMainWindow()
            __log__.info(input_value)
            model_window.show()
            self.overview_windows.append(model_window)
            
class App(QtWidgets.QApplication):
    
    def __init__(self, sys_argv):
        super(App, self).__init__(sys_argv)
        self.main_view = MyTable()
        self.main_view.show()
            
if __name__ == '__main__':
    logging.basicConfig()
    app = App(sys.argv)
    try:
        sys.exit(app.exec_())
    except Exception as e:
        __log__.error('%s', e)```

@musicamante 我认为这个答案主要采纳了你的建议。我不确定您是否建议我将 MyTable 子类化为 MySpecialTable。如果你是,我不确定我是否有效地超载 gen_context_menu。最后,我也不确定我是否有效地实施了 pos/event 内容。但是,这似乎确实有效,请听取您关于 standardContextMenutriggeredcontextMenuEvent 的建议。在某种程度上,这充其量只是 practice/good 习惯的一种努力,请随时进一步完善它。谢谢。

import logging
import sys

from PyQt5 import QtWidgets
from PyQt5.Qt import QAbstractTableModel, QVariant, Qt, QMainWindow

__log__ = logging.getLogger()


class MyModel(QAbstractTableModel):
    rows = [('X'), ('Y'), ('Z')]
    columns = ['Letter']
    
    def rowCount(self, parent):
        return len(MyModel.rows)

    def columnCount(self, parent):
        return len(MyModel.columns)
    
    def data(self, index, role):
        if role != Qt.DisplayRole:
            return QVariant()
        return MyModel.rows[index.row()][index.column()]


class MyTable(QtWidgets.QTableView):
    
    def __init__(self, parent=None):
        super().__init__()
        self.tick_windows = []
        self.events_windows = []
        self.overview_windows = []
        mre_model = MyModel()
        self.setModel(mre_model)
        
    def get_input_value(self, pos):
        index = self.indexAt(pos)
        if not index.isValid() or index.column() != 0:
            return None
        input_value = index.sibling(index.row(), index.column()).data()
        return input_value

    def contextMenuEvent(self, event):
        pos = event.globalPos()
        input_value = self.get_input_value(self.viewport().mapFromGlobal(pos))
        if not input_value:
            return
        menu = self.gen_context_menu(input_value)
        menu.exec(event.globalPos())

    def get_ticks_triggered(self, input_value):
        model_window = QMainWindow()
        __log__.info(input_value)
        model_window.show()
        self.tick_windows.append(model_window)
        
    def get_events_triggered(self, input_value):
        model_window = QMainWindow()
        __log__.info(input_value)
        model_window.show()
        self.tick_windows.append(model_window)
        
    def get_overview_triggered(self, input_value):
        model_window = QMainWindow()
        __log__.info(input_value)
        model_window.show()
        self.tick_windows.append(model_window)
        
    def gen_context_menu(self, input_value):
        menu = QtWidgets.QMenu()
        menu = self.get_standard_menu(menu, input_value)
        return menu

    def get_standard_menu(self, menu, input_value):
        action_get_ticks = menu.addAction("Get Ticks")
        action_get_ticks.triggered.connect(lambda: self.get_ticks_triggered(input_value))
        action_get_events = menu.addAction("Get Events")
        action_get_events.triggered.connect(lambda: self.get_events_triggered(input_value))
        action_get_overview = menu.addAction("Get Overview")
        action_get_overview.triggered.connect(lambda: self.get_overview_triggered(input_value))
        return menu


class MyTableSpecial(MyTable):
    
    def get_special_menu(self, menu, input_value):
        action_send = menu.addAction("Send!")
        action_send.triggered.connect(lambda: self.send_triggered(input_value))
        return menu
    
    def send_triggered(self, input_value):
        __log__.info(input_value)
    
    def gen_context_menu(self, input_value):
        menu = QtWidgets.QMenu()
        menu = super().get_standard_menu(menu, input_value)
        menu = self.get_special_menu(menu, input_value)
        return menu
    

class App(QtWidgets.QApplication):
    
    def __init__(self, sys_argv):
        super(App, self).__init__(sys_argv)
        self.main_view = MyTableSpecial()
        self.main_view.show()


if __name__ == '__main__':
    logging.basicConfig(level=logging.INFO)
    app = App(sys.argv)
    try:
        sys.exit(app.exec_())
    except Exception as e:
        __log__.error('%s', e)