PySide・PyQTでのイベント処理
ボタンだったらclickedシグナルが送られ、
それをconnectすればイベント処理できます。
今回はそういう標準的イベントではなく、
- 独自にシグナル(SIGNAL)を送信したい
- 独自にスロット(SLOT)を定義したい
そういう場面に遭遇しました。
少し落とし穴的なトラップもあったので、
PySideのシグナル・スロットの実装方法まとめます。
このページの目次
大前提はSIGNALをQObject継承クラスで定義すること
これはハマりがちなポイント1つめです。
「必ずQObject継承クラスでSIGNAL定義する」
普通のクラス内ではSignalは定義できません。
▼ これが分からないと悩み続けます。
- 独自シグナルを○○クラスで定義しただろ…
- それでそのシグナルを emit すると…
- アレ?どうして発火してくれないの?
それはシグナル定義そのものが間違ってるからです。
QObject以外でSIGNAL定義だと no attribute エラーが発生
QObject以外でシグナル定義するのはダメ!
例えばダメなコード例を少し考えてみます。
▼ このようなコードは期待通りに動かない
1 2 3 4 5 6 7 8 9 10 11 12 13 |
from PySide2.QtCore import Signal, QObject class Phone: ## SIGNAL定義 mailReceived = QtCore.Signal(str) def __init__(self): pass ## インスタンス生成 phone = Phone() ## 試しにシグナル送信する phone.mailReceived.emit("hello") |
▼ 次のようなエラーが発生した
1 2 3 4 5 6 |
Traceback (most recent call last): File "main.py", line 294, in <module> app = App() File "main.py", line 32, in __init__ phone.mailReceived.emit() AttributeError: 'PySide2.QtCore.Signal' object has no attribute 'signal' |
単純にSignalを定義するのは間違い
純粋なクラスでSignal定義しても全く無意味です。
必ずQObject継承クラスでSIGNAL定義すべし
先ほどのコードを修正します。
▼ 修正後の正しいコード例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from PySide2.QtCore import Signal, QObject class Phone(QtCore.QObject): ## <= 修正 ## SIGNAL定義 mailReceived = QtCore.Signal(str) def __init__(self): ## 修正 : QObject初期化 QObject.__init__(self) ## インスタンス生成 phone = Phone() ## 試しにシグナル送信する phone.mailReceived.emit("hello") |
このようにシグナル・スロットを使うには、
「必ずQObjectを継承させること」
これはPySide・PyQtに限った話でもありません。
C++版QTでもこの仕様は同じです。要注意
任意に独自シグナル送信 ⇒ スロットで受け取る方法
今度はSignal送信とSlotの受け取り方について
こういうコードが分かりやすいと思います。
▼ Signal送信・Slot受け取りコード例
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 |
from PySide2 import QtCore from PySide2.QtCore import Signal, QObject class Phone(QtCore.QObject): ## SIGNAL定義 mailReceived = QtCore.Signal(str) def __init__(self): ## QObject初期化 QObject.__init__(self) def receiveMail(self, text): ## 独自シグナル送信 self.mailReceived.emit(text) ## 任意のスロット関数 ## 引数に文字列型を1つ受け取る @QtCore.Slot(str) def onMailReceived(text): print("Mail received!!") print("Message : %s" % text) ## インスタンス生成 phone = Phone() ## シグナルとスロットを結び付ける phone.mailReceived.connect(onMailReceived) ## 試しにシグナル送信する phone.receiveMail("hello") |
これがSIGNAL/SLOTのミニマムな実装例
重要な点は2つあります。
1.シグナル(SIGNAL)送信にはemit()を実行する
上記コードの次の部分です。
▼ こちらのコード
1 2 |
## 独自シグナル送信 self.mailReceived.emit(text) |
もし受け取り側(SLOT)が文字列型の引数を受け取りなら、この例のように引数付きで emit() を実行すればOKです。(SLOT関数については後述)
それで登録済のSLOT関数が実行されるだけ
2.スロット(SLOT)関数は明示的に@SLOTを付ける
上記コードの以下の部分がそれ
▼ こちらのコード
1 2 3 4 5 6 |
## 任意のスロット関数 ## 引数に文字列型を1つ受け取る @QtCore.Slot(str) def onMailReceived(text): print("Mail received!!") print("Message : %s" % text) |
ただスロット関数を定義するではダメ
このように@QtCore.Slotデコレーターを使い、明示的にどういった引数を取って・どういった返り値を返すのか定義してあげます。
ちなみに返り値は result によって指定可能です。
▼ @Slotによる返り値指定のコード例
1 2 3 4 5 |
@QtCore.Slot(str, result=str) def onMailReceived(text): print("Mail received!!") print("Message : %s" % text) return str |
ここはあまり深く触れないことにします。
(自分もこの部分をよく理解してないため)
とりあえず@Slotデコレーターが必須ということ
最後にシグナル・スロットの要点をまとめ
少しまとまりのない内容だったかもしれない。
そこで重要な点だけをまとめたいと思います。
- SIGNALはQObject継承クラスで定義する
- SIGNAL送信にはemit()を使う
- SLOT関数には明示的に@SLOTを付ける
これだけを抑えとけば問題ないはずです。