4

I'm working on a QGIS plugin, where the UI is made with PyQt. I have a QListWidget and a function that fills it. I'd like to add a context menu for each item with only one option: to open another window.

I'm having trouble searching for info, since most of it works only on PyQt4 and I'm using version 5. The QListWidget that I want to add a context menu on is ds_list_widget. Here's some of the relevant code.

FORM_CLASS, _ = uic.loadUiType(os.path.join(
    os.path.dirname(__file__), 'dialog_base.ui'))

class Dialog(QDialog, FORM_CLASS):
    def __init__(self, parent=None):
        ...
        self.p_list_widget = self.findChild(QListWidget, 'projects_listWidget') 
        self.p_list_widget.itemClicked.connect(self.project_clicked)
        self.ds_list_widget = self.findChild(QListWidget, 'datasets_listWidget')        
        self.ds_list_widget.itemClicked.connect(self.dataset_clicked)
        ...


    def project_clicked(self, item):
        self.fill_datasets_list(str(item.data(Qt.UserRole)))        
        self.settings.setValue('projectIdValue', str(item.data(Qt.UserRole)))

    def fill_datasets_list(self, project_id):
        self.ds_list_widget.clear()
        dataset_list = self.anotherClass.fetch_dataset_list(project_id)

        for dataset in dataset_list:
            #Query stuff from remote
            ...
            item = QListWidgetItem(ds_name, self.ds_list_widget)
            item.setIcon(self.newIcon(ds_img))
            item.setData(Qt.UserRole, ds_id)
            self.ds_list_widget.addItem(item)
            self.ds_list_widget.setIconSize(self.iconSize)
ekhumoro
  • 115,249
  • 20
  • 229
  • 336
Arturo Cuya
  • 135
  • 3
  • 17
  • Looks to be a duplicate to: https://stackoverflow.com/questions/20930764/how-to-add-a-right-click-menu-to-each-cell-of-qtableview-in-pyqt – Zack Tarr Feb 20 '18 at 17:05
  • Possible duplicate of [how to add a right click menu to each cell of QTableView in PyQt](https://stackoverflow.com/questions/20930764/how-to-add-a-right-click-menu-to-each-cell-of-qtableview-in-pyqt) – eyllanesc Feb 20 '18 at 17:33

2 Answers2

15

Since your list-widget is created by Qt Designer, it is probably easiest to install an event-filter on it and trap the context-menu event. With that in place, the rest is quite straightforward - here is a simple demo:

import sys
from PyQt5 import QtCore, QtWidgets

class Dialog(QtWidgets.QDialog):
    def __init__(self, parent=None):
        super(Dialog, self).__init__()
        self.listWidget = QtWidgets.QListWidget()
        self.listWidget.addItems('One Two Three'.split())
        self.listWidget.installEventFilter(self)
        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.listWidget)

    def eventFilter(self, source, event):
        if (event.type() == QtCore.QEvent.ContextMenu and
            source is self.listWidget):
            menu = QtWidgets.QMenu()
            menu.addAction('Open Window')
            if menu.exec_(event.globalPos()):
                item = source.itemAt(event.pos())
                print(item.text())
            return True
        return super(Dialog, self).eventFilter(source, event)

if __name__ == '__main__':

    app = QtWidgets.QApplication(sys.argv)
    window = Dialog()
    window.setGeometry(600, 100, 300, 200)
    window.show()
    sys.exit(app.exec_())

PS:

You should also note that code like this:

self.p_list_widget = self.findChild(QListWidget, 'projects_listWidget')

is completely unnecessary. All the widgets from Qt Designer are automatically added as attributes to the form class using the object-name. So your code can be simplified to this:

self.projects_listWidget.itemClicked.connect(self.project_clicked)
self.datasets_listWidget.itemClicked.connect(self.dataset_clicked)

there is no need to use findChild.

ekhumoro
  • 115,249
  • 20
  • 229
  • 336
  • Thank you very much! Turns out I got wrong what my boss told me so there is no necessity to do the context menu anymore. But the PS was really useful! And the example will definitely be helpful for my own projects. – Arturo Cuya Feb 21 '18 at 05:38
2

In addition to the answer above, you can also set multiple QAction() submenu items to do multiple things. As you would a normal menu.

One way is to edit your eventFilter so that menu.exec() becomes a variable:

def eventFilter(self, source, event):
    if (event.type() == QtCore.QEvent.ContextMenu and source is self.listWidget):
        menu = QtWidgets.QMenu()
        open_window_1 = QAction("Open Window 1")
        open_window_2 = QAction("Open Window 2")
        menu.addAction(open_window_1)
        menu.addAction(open_window_2)
        menu_click = menu.exec(event.globalPos())
        
        try:
            item = source.itemAt(event.pos())
        except Exception as e:
            print(f"No item selected {e}")

        if menu_click == open_window_1 :
                print("Opening Window 1...")
                # Your code here
        if menu_click == open_window_2 :
                print("Opening Window 2...")
                # Your code here

        # and so on... You can now add as many items as you want
        return True
    return super(Dialog, self).eventFilter(source, event)
fas
  • 21
  • 1
  • 2