Search code examples
pythoncontextmenupyqt5qlistwidget

How do I make a context menu for each item in a QListWidget?


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)

Solution

  • 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.