I have multiple PyQt5 applications with QTableViews that inherit from a subclass "MyTable" of QTableView. In so doing, I am able to maintain the code for a context menu in one place (gen_context_menu) that's useful to all apps that use MyTable.
But today I encountered a need for a new application that will both get the benefit of the existing context menu in MyTable, but also get new/additional context menu options that I do NOT want grant legacy/universal applications access to.
In the MRE below, I wrote a couple dummy functions to show what was vaguely in my mind to accomplish that - I thought I'd encapsulate the getting of the 'standard menu' (with get_standard_menu), MyTable would call that in the legacy/universal case, and then for the new application, call a 'new' menu function (get_sending_menu) through a parameter or small subclass of MyTable perhaps. As I went to code that, I realized that since the 'actions' are coupled with the menu definition, it wasn't simple the way I thought it would be, and then I immediately thought 'there must be a better way'...
Is there?
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)```
CodePudding user response:
@musicamante i think this answer takes your advice - mostly. i am not sure if you were suggesting i subclass MyTable with MySpecialTable. if you were, i am not sure i am efficiently overloading gen_context_menu. finally, i am also not sure i implemented the pos/event stuff efficiently. however, this does seem to work and take your advice around standardContextMenu
, triggered
, and contextMenuEvent
. To the extent this is an effort at best practice/good habits, please feel free to refine it further. Thank you.
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)