Create different context menus on several QTableViews

Viewed 422

I have two QTableViews inside a QMainWindow and I want to create a context menu on one of them and later another context menu on the other one.

Creating the context menu and define actions works so far. But the context menu pops up everywhere inside the whole application. I don't know how to limit it to only one specific table. I think it has to do with contextMenuEvent() which is a member of QMainWindow, but I don't know how to change this part. Trying to create a custom class that inherits QTableView didn't work, because I'm not sure where to start.

Here's what I tried:

The populate_table_1() and populate_table_2() methods are only for filling some data into the tables. The get_selected_item_TV1() method gets the necessary data from a row of table_1. The delete_file() method is an example for what I want do when calling the delete action from the contextMenuEvent() method. This code works so far, but I want the context menu to popup only if I right-click on a row of table_1 and that it don't appear at all when right-clicking elsewhere.

from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *



data_1 = ["file_name", "file_infos"]
data_2 = ["other_stuff_1", "other_stuff_2"]


class Ui_MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle("MyApp")
        self.resize(450, 280)
        centralwidget = QWidget(self)

        #]===================================================================[#

        # table_1
        table_1 = QTableView(
            centralwidget,
            selectionBehavior=QAbstractItemView.SelectRows,
            editTriggers=QAbstractItemView.NoEditTriggers
        )
        # table_1 models
        self.modelTV1 = QStandardItemModel(0, 2, centralwidget)
        self.modelTV1.setHorizontalHeaderLabels(["column 1", "column 2"])
        table_1.setModel(self.modelTV1)

        self.selectionModelTV1 = table_1.selectionModel()

        #]===================================================================[#

        # table_2
        table_2 = QTableView(
            centralwidget,
            selectionBehavior=QAbstractItemView.SelectRows,
            editTriggers=QAbstractItemView.NoEditTriggers,
        )
        # table_2 models
        self.modelTV2 = QStandardItemModel(0, 2, centralwidget)
        self.modelTV2.setHorizontalHeaderLabels(["column 1", "column 2"])
        table_2.setModel(self.modelTV2)

        self.selectionModelTV2 = table_2.selectionModel()

        v_Layout1 = QVBoxLayout()
        v_Layout1.addWidget(table_1)
        v_Layout1.addWidget(table_2)
        gridLayout = QGridLayout(centralwidget)
        gridLayout.addLayout(v_Layout1, 0, 0, 1, 1)
        self.setCentralWidget(centralwidget)


    def populate_table_1(self):
        self.modelTV1.setRowCount(0)
        for item in data_1:
            self.modelTV1.insertRow(0)
            for i, text in enumerate(data_1):
                self.modelTV1.setItem(0, i, QStandardItem(text))


    def populate_table_2(self):
        self.modelTV2.setRowCount(0)
        for item in data_2:
            self.modelTV2.insertRow(0)
            for i, text in enumerate(data_2):
                self.modelTV2.setItem(0, i, QStandardItem(text))


    def contextMenuEvent(self, event):
        self.contextMenu = QMenu(self)
        deleteAction = QAction("Delete", self)

        self.contextMenu.addAction(deleteAction)
        deleteAction.triggered.connect(lambda: self.delete_file(event))

        self.contextMenu.popup(QCursor.pos())


    def get_selected_item_TV1(self):
        # get the row's text from the first column in table_1
        listed_items = self.selectionModelTV1.selectedRows()
        for index in listed_items:
            selected_item = index.data()
            return f"table_1 - row_{index.row()} - {selected_item}"


    def delete_file(self, event):
        item = self.get_selected_item_TV1()
        print(f"Deleting: {item}")



if __name__ == "__main__":
    import sys

    app = QApplication(sys.argv)
    mainUI = Ui_MainWindow()

    mainUI.populate_table_1()
    mainUI.populate_table_2()
    mainUI.show()

    sys.exit(app.exec_())

1 Answers

There are many alternatives:

  • Detect that when the mouse is pressed it is in a certain area, for example the first QTableView. In this case the widget must be accessible in the contextMenuEvent() so you must change table_1 to self.table_1 and then use underMouse():

    def contextMenuEvent(self, event):
        if self.table_1.underMouse():
            self.contextMenu = QMenu(self)
            deleteAction = QAction("Delete", self)
    
            self.contextMenu.addAction(deleteAction)
            deleteAction.triggered.connect(lambda: self.delete_file(event))
    
            self.contextMenu.popup(QCursor.pos())
    
  • Implement the contextMenuEvent method for each QTableView:

    class TableView(QTableView):
        def contextMenuEvent(self, event):
            self.contextMenu = QMenu(self)
            deleteAction = QAction("Delete", self)
    
            self.contextMenu.addAction(deleteAction)
            deleteAction.triggered.connect(lambda: self.delete_file(event))
    
            self.contextMenu.popup(QCursor.pos())
    
        def get_selected_item_TV1(self):
            # get the row's text from the first column in table_1
            listed_items = self.selectionModel().selectedRows()
            for index in listed_items:
                selected_item = index.data()
                return f"table_1 - row_{index.row()} - {selected_item}"
    
        def delete_file(self, event):
            item = self.get_selected_item_TV1()
            print(f"Deleting: {item}")
    

    and then you must change table_1 = QTableView(... for table_1 = TableView(...

  • Another alternative is to use the customContextMenuRequested signal, for this you must enable the Qt::CustomContextMenu flag:

    table_1 = QTableView(
        centralwidget,
        selectionBehavior=QAbstractItemView.SelectRows,
        editTriggers=QAbstractItemView.NoEditTriggers,
        contextMenuPolicy=Qt.CustomContextMenu
    )
    table_1.customContextMenuRequested.connect(self.on_customContextMenuRequested)
    
    def on_customContextMenuRequested(self):
        self.contextMenu = QMenu(self)
        deleteAction = QAction("Delete", self)
    
        self.contextMenu.addAction(deleteAction)
        deleteAction.triggered.connect(lambda: self.delete_file())
    
        self.contextMenu.popup(QCursor.pos())
    
    def delete_file(self):
        item = self.get_selected_item_TV1()
        print(f"Deleting: {item}")
    
Related