コンテキストメニューを作る
いろいろな 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」 を作成します。
| 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 クラスを使用します。
| action_01 = menu.addAction('ほげほげ')
action_01.triggered.connect(lambda: self.action('A'))
|
まず、実行させたいWidgetを親にしたMenuを作成します。
そして、メニューに追加するには addAction(メニュー名) を追加します。
追加すると、QActionオブジェクトが帰ってくるので
このメニューをクリックしたときに実行される関数(Slot)を
.triggered.connect(###) で、指定します。
このQActionというのは、各種メニュー、ツールボタン、キーボードのショートカット
等を介していろんなコマンドを呼び出すのに使用するものです。
セパレーターを追加する
Menuの途中に線を入れたい場合は、入れたいところでこのSeparatorを追加します。
サブメニューを追加する
| subMenu = menu.addMenu('SubMenu')
action_03 = subMenu.addAction('さぶめにゅー1')
action_03.triggered.connect(self.subMenu)
|
サブメニューを作りたい場合は addMenuコマンドを使用します。
このコマンドを使用すると、QMenuオブジェクトが返ってきます。
ので、あとは上と同じくそのMenuオブジェクトに対して addActionでアクションを追加すると
サブメニューを作成できます。
サブメニューにさらにサブメニューを追加したい場合も同様に
addMenuを追加すればOKです。
表示する
最後に、設定ができたら表示します。
| menu.exec_(self.list.mapToGlobal(pos))
|
表示するには exec_(Menuを表示するPoint) を使用します。
customContextMenuRequested のSignalは、現在のマウスポジションを引数で受け取るのですが
このPointは現在のWidgetsのローカル座標が返ってきてしまいます。
しかしながら、ContextMenuの表示位置はグローバル座標である必要があります。
ので、Local→Globalの座標返還を行うために mapToGLobal関数を使用します。
これは、この関数のオブジェクト(この例だと self.list )の座標系を
Globalに変換してくれます。
ので、この関数を使用すればローカル座標だった pos を グローバルに変換して
正しい位置にメニューを表示することができます。
一応、
| # QCursorでカーソルのグローバルポジションを取得する
cursor = QtGui.QCursor()
menu.exec_(cursor.pos())
|
でも、同じ効果となります。