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 内容。但是,这似乎确实有效,请听取您关于 standardContextMenu
、triggered
和 contextMenuEvent
的建议。在某种程度上,这充其量只是 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)
我有多个带有 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 内容。但是,这似乎确实有效,请听取您关于 standardContextMenu
、triggered
和 contextMenuEvent
的建议。在某种程度上,这充其量只是 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)