コンテンツにスキップ

コンテキストメニューを作る

いろいろな Widgets を作っていくと、右クリックメニューを作りたくなるかと思います。
今回は、PySide で右クリックメニューを作る方法を説明したいと思います。

コード

まずはコード。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# -*- coding: utf-8 -*-

import sys

from PySide2 import QtCore, QtGui, QtWidgets

class UISample(QtWidgets.QDialog):

    def __init__(self, parent=None):
        super(UISample, self).__init__(parent)

        layout = QtWidgets.QVBoxLayout()
        # カスタムUIを作成
        self.list = QtWidgets.QTreeView()
        layout.addWidget(self.list)
        self.list2 = QtWidgets.QTreeView()
        # List1で表示するContextMenuを設定
        self.list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.list.customContextMenuRequested.connect(self.listContext)
        # List2で表示するContextMenuを指定
        self.list2.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.list2.customContextMenuRequested.connect(self.list2Context)
        layout.addWidget(self.list2)
        # ここまでUI作成

        self.setLayout(layout)

    def listContext(self, pos):

        menu = QtWidgets.QMenu(self.list)
        action_01 = menu.addAction('ほげほげ')
        action_01.triggered.connect(lambda: self.action('A'))
        action_02 = menu.addAction('ふがふが')
        action_02.triggered.connect(lambda: self.action('B'))
        menu.addSeparator()
        subMenu = menu.addMenu('SubMenu')
        action_03 = subMenu.addAction('さぶめにゅー1')
        action_03.triggered.connect(self.subMenu)

        menu.exec_(self.list.mapToGlobal(pos))

    def action(self, actionText):
        print(actionText)

    def subMenu(self):
        print("SUBMENU")

    def list2Context(self, pos):

        menu = QtWidgets.QMenu(self.list2)
        menu.addAction('list2_Menu')
        menu.addAction('list2_Menu2')
        menu.addAction('list2_Menu2')

        menu.exec_(self.list2.mapToGlobal(pos))


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    a = UISample()
    a.show()
    sys.exit(app.exec_())

実行すると、こんな感じのUIが表示されます。
以下、詳しい解説。

Signal-Slotを作る

まず、メニューを表示したい場合は、PySideのお約束の 「Signal-Slot」 を作成します。

1
2
        self.list.setContextMenuPolicy(QtCore.Qt.CustomContextMenu)
        self.list.customContextMenuRequested.connect(self.listContext)

それがこの部分になります。
PySideでは、右クリックで表示されるメニューのことを「コンテキストメニュー」と呼びます。
今回のように、自分でこのコンテキストメニューを作りたい場合は
Signal-Slotとは別に、setContexdtMenuPolicy でCustomContextMenuの設定をしておきます。
このフラグをONにしたら、次にメニューを呼び出すためのシグナルを作成します。

このコンテキストメニューは、Widgets単位で作成することができます。
ので、例えば今回のサンプルのように
2つのListViewがあった場合に、それぞれ別のメニューを表示出来るようにしたい場合
それぞれのWidgetsごとにこのContextMenuの設定を行います。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
    def listContext(self, pos):

        menu = QtWidgets.QMenu(self.list)
        action_01 = menu.addAction('ほげほげ')
        action_01.triggered.connect(lambda: self.action('A'))
        action_02 = menu.addAction('ふがふが')
        action_02.triggered.connect(lambda: self.action('B'))
        menu.addSeparator()
        subMenu = menu.addMenu('SubMenu')
        action_03 = subMenu.addAction('さぶめにゅー1')
        action_03.triggered.connect(self.subMenu)

        menu.exec_(self.list.mapToGlobal(pos))

そんでもって、右クリックを押したときに呼ばれる関数を作成します。
単純に右クリックに割り当てただけだと、もちろんメニューは表示されません。
メニュー部分は QWidgets.QMneu クラスを使用します。

1
2
action_01 = menu.addAction('ほげほげ')
action_01.triggered.connect(lambda: self.action('A'))

まず、実行させたいWidgetを親にしたMenuを作成します。
そして、メニューに追加するには addAction(メニュー名) を追加します。
追加すると、QActionオブジェクトが帰ってくるので
このメニューをクリックしたときに実行される関数(Slot)を
.triggered.connect(###) で、指定します。

このQActionというのは、各種メニュー、ツールボタン、キーボードのショートカット
等を介していろんなコマンドを呼び出すのに使用するものです。

セパレーターを追加する

1
menu.addSeparator()

Menuの途中に線を入れたい場合は、入れたいところでこのSeparatorを追加します。

サブメニューを追加する

1
2
3
        subMenu = menu.addMenu('SubMenu')
        action_03 = subMenu.addAction('さぶめにゅー1')
        action_03.triggered.connect(self.subMenu)

サブメニューを作りたい場合は addMenuコマンドを使用します。
このコマンドを使用すると、QMenuオブジェクトが返ってきます。
ので、あとは上と同じくそのMenuオブジェクトに対して addActionでアクションを追加すると
サブメニューを作成できます。
サブメニューにさらにサブメニューを追加したい場合も同様に
addMenuを追加すればOKです。

表示する

最後に、設定ができたら表示します。

1
    menu.exec_(self.list.mapToGlobal(pos))

表示するには exec_(Menuを表示するPoint) を使用します。

customContextMenuRequested のSignalは、現在のマウスポジションを引数で受け取るのですが
このPointは現在のWidgetsのローカル座標が返ってきてしまいます。
しかしながら、ContextMenuの表示位置はグローバル座標である必要があります。

ので、Local→Globalの座標返還を行うために mapToGLobal関数を使用します。
これは、この関数のオブジェクト(この例だと self.list )の座標系を
Globalに変換してくれます。
ので、この関数を使用すればローカル座標だった pos を グローバルに変換して
正しい位置にメニューを表示することができます。

一応、

1
2
3
# QCursorでカーソルのグローバルポジションを取得する
cursor = QtGui.QCursor()
menu.exec_(cursor.pos())

でも、同じ効果となります。