Updated: 2022/10/22 CG sup. Tatsuya Yamagishi
Created: 2021/08/26 CG sup. Tatsuya Yamagishi

class CustumListWidget(QtWidgets.QListWidget):
    def __init__(self, parent=None):
        super(CustumListWidget, self).__init__(parent)
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.context_menu)
    def context_menu(self, point):
        menu = QtWidgets.QMenu(self)
        action = QtWidgets.QAction('Menu1', self)
        action.triggered.connect(self.menu1)
        action.setShortcut(QtGui.QKeySequence('Ctrl+Shift+C'))
        menu.addAction(action)
        action = QtWidgets.QAction('Menu2', self)
        menu.addAction(action)
        menu.addSeparator()
        action = QtWidgets.QAction('Menu3', self)
        menu.addAction(action)
        menu.exec_(self.mapToGlobal(point))
    def menu1(self):
        item = self.currentItem()
        if item:
            print(item.text())

import sys
from PySide2 import QtCore, QtGui, QtWidgets
class Test(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Test, self).__init__(parent)
app = QtWidgets.QApplication(sys.argv)
tool = Test()
tool.show()
app.exec_()
setContextMenuPolicy を使います。ほとんどのWidgetで使う事が出来ますが、ListWidgetItemなどでは使えない(?)事が多いので、その場合は親クラスのListWidget側で右クリックメニューを設定します。
この目に見える奴らWidgetと呼ばれているものの基底クラスがQWidgetクラスといっているようで、大体のWidgetはこれを継承して拡張されているっぽいです。例えばプッシュボタンのQPushButtonは以下のようになっています。

今回使うsetContextMenuPolicyは、QWidgetクラスの持つ関数のようです。なのでQWidgetから派生してる子のクラスは大体使えるんじゃないかと思います。(未確認)
import sys
from PySide2 import QtCore, QtGui, QtWidgets
class Test(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Test, self).__init__(parent)
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
app = QtWidgets.QApplication(sys.argv)
tool = Test()
tool.show()
app.exec_()
今回は、リストウィジェットに右クリックメニューを設定してみます。

import sys
from PySide2 import QtCore, QtGui, QtWidgets
class Test(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Test, self).__init__(parent)
        self.listWidget = QtWidgets.QListWidget(self)
app = QtWidgets.QApplication(sys.argv)
tool = Test()
tool.show()
app.exec_()
QLayoutを用いる事でレイアウトの労力が無くなる。使わないと各ウィジェットの位置や大きさの詳細な設定を記述しないとならない。
import sys
from PySide2 import QtCore, QtGui, QtWidgets
class Test(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Test, self).__init__(parent)
        # レイアウトの作成
        self.layout = QtWidgets.QVBoxLayout()
        # リストウィジェットの作成
        self.listWidget = QtWidgets.QListWidget(self)
        # リストウィジェットをレイアウトに追加
        self.layout.addWidget(self.listWidget)
        # レイアウトをクラスのベースとなっているウィジェットに追加
        self.setLayout(self.layout)
app = QtWidgets.QApplication(sys.argv)
tool = Test()
tool.resize(300,200)
tool.show()
app.exec_()
クラスを2つに分けずに、1つのクラス内で
self.listWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
と記述しても全く問題ないのですが、
可読性が高い管理がしやすいという理由で今回はカスタムクラスの方に右クリックメニューを追加するコードを書いていきます。
import sys
from PySide2 import QtCore, QtGui, QtWidgets
class CustumListWidget(QtWidgets.QListWidget):
    def __init__(self, parent=None):
        super(CustumListWidget, self).__init__(parent)
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.contextMenu)
    # 右クリックで呼び出される関数。マウスの位置を取得しそこにメニューを表示するため引数pointは必須。
    def contextMenu(self, point):
        print('Test')
class Test(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Test, self).__init__(parent)
        # レイアウトの作成
        self.layout = QtWidgets.QVBoxLayout()
        # リストウィジェットの作成
        self.listWidget = CustumListWidget(self)
        # リストウィジェットをレイアウトに追加
        self.layout.addWidget(self.listWidget)
        # レイアウトをクラスのベースとなっているウィジェットに追加
        self.setLayout(self.layout)
app = QtWidgets.QApplication(sys.argv)
tool = Test()
tool.resize(300,200)
tool.show()
app.exec_()
右クリックするたびに「Test」と表示される。

QMenu
と
QAction
でメニューと項目を作成します。

import sys
from PySide2 import QtCore, QtGui, QtWidgets
class CustumListWidget(QtWidgets.QListWidget):
    def __init__(self, parent=None):
        super(CustumListWidget, self).__init__(parent)
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.contextMenu)
    def contextMenu(self, point):
        # メニューの作成
        menu = QtWidgets.QMenu(self)
        # メニュー1の作成、登録
        action = QtWidgets.QAction('Menu1', self) # QAction('&Menu1', self)と&つける書き方もあるけど、あれは何だろうか?
        menu.addAction(action)
        # メニュー2の作成、登録
        action = QtWidgets.QAction('Menu2', self)
        menu.addAction(action)
        # セパレートの追加
        menu.addSeparator()
        # メニュー3の作成、登録
        action = QtWidgets.QAction('Menu3', self)
        menu.addAction(action)
        # メニュー表示。必須
        menu.exec_(self.mapToGlobal(point))
class Test(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Test, self).__init__(parent)
        # レイアウトの作成
        self.layout = QtWidgets.QVBoxLayout()
        # レイアウトの余白を無くす
        self.layout.setContentsMargins(0, 0, 0, 0)
        # リストウィジェットの作成
        self.listWidget = CustumListWidget(self)
        # リストウィジェットをレイアウトに追加
        self.layout.addWidget(self.listWidget)
        # レイアウトをクラスのベースとなっているウィジェットに追加
        self.setLayout(self.layout)
app = QtWidgets.QApplication(sys.argv)
tool = Test()
tool.resize(300,200)
tool.show()
app.exec_()
Tips:MainWindowにメニューを作成する方法も同じ方法

メニューを選択した際のシグナルを設定します。今回はMenu1だけ設定します。
import sys
from PySide2 import QtCore, QtGui, QtWidgets
class CustumListWidget(QtWidgets.QListWidget):
    def __init__(self, parent=None):
        super(CustumListWidget, self).__init__(parent)
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.contextMenu)
    def contextMenu(self, point):
        # メニューの作成
        menu = QtWidgets.QMenu(self)
        # メニュー1の作成、登録
        action = QtWidgets.QAction('Menu1', self)
        action.triggered.connect(self.menu1)
        menu.addAction(action)
        # メニュー2の作成、登録
        action = QtWidgets.QAction('Menu2', self)
        menu.addAction(action)
        # セパレートの追加
        menu.addSeparator()
        # メニュー3の作成、登録
        action = QtWidgets.QAction('Menu3', self)
        menu.addAction(action)
        # メニュー表示。必須
        menu.exec_(self.mapToGlobal(point))
    def menu1(self):
        print('Menu1')
class Test(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Test, self).__init__(parent)
        # レイアウトの作成
        self.layout = QtWidgets.QVBoxLayout()
        # レイアウトの余白を無くす
        self.layout.setContentsMargins(0, 0, 0, 0)
        # リストウィジェットの作成
        self.listWidget = CustumListWidget(self)
        # リストウィジェットをレイアウトに追加
        self.layout.addWidget(self.listWidget)
        # レイアウトをクラスのベースとなっているウィジェットに追加
        self.setLayout(self.layout)
app = QtWidgets.QApplication(sys.argv)
tool = Test()
tool.resize(300,200)
tool.show()
app.exec_()
Menu1を選択すると
Menu1
と表示される。

import sys
from PySide2 import QtCore, QtGui, QtWidgets
class CustumListWidget(QtWidgets.QListWidget):
    def __init__(self, parent=None):
        super(CustumListWidget, self).__init__(parent)
        self.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.customContextMenuRequested.connect(self.contextMenu)
    def contextMenu(self, point):
        # メニューの作成
        menu = QtWidgets.QMenu(self)
        # メニュー1の作成、登録
        action = QtWidgets.QAction('Menu1', self)
        action.triggered.connect(self.menu1)
        menu.addAction(action)
        # メニュー2の作成、登録
        action = QtWidgets.QAction('Menu2', self)
        menu.addAction(action)
        # セパレートの追加
        menu.addSeparator()
        # メニュー3の作成、登録
        action = QtWidgets.QAction('Menu3', self)
        menu.addAction(action)
        # メニュー表示。必須
        menu.exec_(self.mapToGlobal(point))
    def menu1(self):
        item = self.currentItem()
        if item:
            print(item.text())
class Test(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Test, self).__init__(parent)
        # レイアウトの作成
        self.layout = QtWidgets.QVBoxLayout()
        # レイアウトの余白を無くす
        self.layout.setContentsMargins(0, 0, 0, 0)
        # リストウィジェットの作成
        self.listWidget = CustumListWidget(self)
        # リストウィジェットをレイアウトに追加
        self.layout.addWidget(self.listWidget)
        # レイアウトをクラスのベースとなっているウィジェットに追加
        self.setLayout(self.layout)
ITEMS = ['asset-a', 'asset-b', 'asset-c']
app = QtWidgets.QApplication(sys.argv)
tool = Test()
tool.resize(300,200)
tool.listWidget.addItems(ITEMS)
tool.show()
app.exec_()
参考として、同じ仕組みを1つのクラスで定義した場合のサンプルコードになります。駄目な例ではないのです。コードが混在する事で可読性が落ちる例として準備してみました。今は1つの処理しか書いてありませんが、沢山の処理を書く場合、要素毎にクラスや関数を分けるとでバックや管理が行いやすくなる事もあります。
また、サブメニューも追加してみました。

import sys
from PySide2 import QtCore, QtGui, QtWidgets
class Test(QtWidgets.QWidget):
    def __init__(self, parent=None):
        super(Test, self).__init__(parent)
        # レイアウトの作成
        self.layout = QtWidgets.QVBoxLayout()
        # レイアウトの余白を無くす
        self.layout.setContentsMargins(0, 0, 0, 0)
        # リストウィジェットの作成
        self.listWidget = QtWidgets.QListWidget(self)
        self.listWidget.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.listWidget.customContextMenuRequested.connect(self.contextMenu)
        # リストウィジェットをレイアウトに追加
        self.layout.addWidget(self.listWidget)
        # レイアウトをクラスのベースとなっているウィジェットに追加
        self.setLayout(self.layout)
    def contextMenu(self, point):
        widget = self.listWidget
        # メニューの作成
        menu = QtWidgets.QMenu(widget)
        # メニュー1の作成、登録
        action = QtWidgets.QAction('Menu1', widget)
        action.triggered.connect(self.menu1)
        menu.addAction(action)
        # メニュー2の作成、登録
        action = QtWidgets.QAction('Menu2', widget)
        menu.addAction(action)
        # セパレートの追加
        menu.addSeparator()
        # メニュー3の作成、登録
        action = QtWidgets.QAction('Menu3', widget)
        menu.addAction(action)
        # セパレートの追加
        menu.addSeparator()
        #######################################
        # サブメニューの追加
        #######################################
        submenu = QtWidgets.QMenu('Submenu', widget)
        menu.addAction(submenu.menuAction())
        # サブメニューにアクション追加
        action = QtWidgets.QAction('Sub1', widget)
        # action.triggered.connect(self.sub1)
        submenu.addAction(action)
        # サブメニューにアクション追加
        action = QtWidgets.QAction('Sub2', widget)
        submenu.addAction(action)
        # サブメニューにアクション追加
        action = QtWidgets.QAction('Sub3', widget)
        submenu.addAction(action)
        # メニュー表示。必須
        menu.exec_(widget.mapToGlobal(point))
    def menu1(self):
        item = self.listWidget.currentItem()
        if item:
            print(item.text())
ITEMS = ['asset-a', 'asset-b', 'asset-c']
app = QtWidgets.QApplication(sys.argv)
widget = Test()
widget.resize(300,200)
widget.move(150, 200)
widget.listWidget.addItems(ITEMS)
widget.show()
app.exec_()
模索中。connectの引数にありそうだけど、何を取得してどうやって渡すようにするのがよいのか?が不明。mouseEventか何かのPosition?
右クリックメニューをクラス内ではなく、関数で定義したい際に引数Pointの渡し方が分からず・・・。 とりあえず、menu側でpointを再計算する方法で対応・・・。
def init_context_menu(self):
    ui = self.treeWidget
    ui.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
    ui.customContextMenuRequested.connect(
        lambda: treeWidget_contextMenu(self))
def treeWidget_contextMenu(self):
    ui = self.treeWidget
    menu = QtWidgets.QMenu(self)
    action = QtWidgets.QAction('Menu1', self)
    # action.triggered.connect(self.menu1)
    menu.addAction(action)
    #point =ui.viewport().mapFromGlobal(QtGui.QCursor.pos())
    # menu.exec_(ui.mapToGlobal(point))
    point = QtGui.QCursor.pos()
    menu.exec_(point)
KIWAMIDEN QListViewに右クリックメニューを作る!! https://kiwamiden.com/create-right-click-menu-in-qlistview