Updated: 2022/10/22 CG sup. Tatsuya Yamagishi

Created: 2021/08/26 CG sup. Tatsuya Yamagishi

右クリックメニューの追加

Examples:

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_()

右クリックメニューを設定するWidgetの設定

setContextMenuPolicy を使います。ほとんどのWidgetで使う事が出来ますが、ListWidgetItemなどでは使えない(?)事が多いので、その場合は親クラスのListWidget側で右クリックメニューを設定します。

この目に見える奴らWidgetと呼ばれているものの基底クラスがQWidgetクラスといっているようで、大体のWidgetはこれを継承して拡張されているっぽいです。例えばプッシュボタンのQPushButtonは以下のようになっています。

今回使うsetContextMenuPolicyは、QWidgetクラスの持つ関数のようです。なのでQWidgetから派生してる子のクラスは大体使えるんじゃないかと思います。(未確認)

setContextMenuPolicyのサンプル

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例

参考として、同じ仕組みを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_()

Point(マウスのカーソル位置)を取得:

模索中。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

Examples