Maya PySide2 / PySide チュートリアル 初学編 ⑧ – Signals & Slotsを作成する

python,qt,PySide,PySide2,Tutorial

このチュートリアルはSignals & Slotsを作成する方法について学んでいきます

前回のチュートリアルではSignals & Slotsとは何かという解説をしていきました
今回はもう少し踏み込んだ内容でQtCore.SignalQtCore.Slotを使用して新しいシグナルを作成してみます

QtCore.Signal

シグナルはQtCore.Signal()クラスを使って定義でき、C++に対応するPythonの型かC++の型をパラメータとして渡すことができます

QtCore.Slot

スロットは QtCore.Slot()というデコレーターを使って割り当てたり、オーバーロードしたりします
シグネチャを定義するには、QtCore.Signal()クラスのように型を渡すだけです

本格的にカスタムするチュートリアルは後半のほうからしていくとして
手始めにQtの公式ドキュメントのコードがとても単純で分かりやすいのでそちらを参考にしていきます
そのままだといつも通りMayaが落ちてしまうので一部コードを書き替えています

import sys

try:
    from PySide2.QtWidgets import *
    from PySide2.QtCore import *
except ImportError:
    from PySide.QtGui import *
    from PySide.QtCore import *

どのコードにも上のコードを先頭に追加して実行してください

@Slot(str)
def say_some_words(words):
    print(words)


class Communicate(QObject):
    speak = Signal(str)


someone = Communicate()
someone.speak.connect(say_some_words)
someone.speak.emit("Hello everybody!")

このコードはとても単純なものでemitの引数に入った文字列をプリントするというようなサンプルコードとなっています
20210831_01

@Slot(str)
def say_some_words(words):
    print(words)

str型を受け取り、'saySomeWords'という名前を持つ新しいスロットを定義します

speak = Signal(str)

speakという新しいシグナルを定義しています

someone.speak.connect(say_some_words)

シグナルとスロットの接続

someone.speak.emit("Hello everybody!")

'speak' シグナルを発信

このコードは上のコードをもう少し拡張したもので2種類の型を扱えるようにしたものになります

@Slot(int)
@Slot(str)
def say_something(stuff):
    print(stuff)


class Communicate(QObject):
    speak_number = Signal(int)
    speak_word = Signal(str)


someone = Communicate()
someone.speak_number.connect(say_something)
someone.speak_word.connect(say_something)
someone.speak_number.emit(10)
someone.speak_word.emit("Hello everybody!")

20210831_02

@Slot(int)
@Slot(str)

'int'または'str'を受け取り、'saySomething'を名前に持つ新しいスロットを定義します

speak_number = Signal(int)
speak_word = Signal(str)

新しいシグナルを作成:speak_numberはint型を、speak_wordはstr型を扱います

someone.speak_number.connect(say_something)
someone.speak_word.connect(say_something)

シグナルとスロットの接続

someone.speak_number.emit(10)
someone.speak_word.emit("Hello everybody!")

'speak' シグナルを発信

複数の型が扱えるのはとても便利ですがもう少しすっきりとした書き方も可能です

@Slot(int)
@Slot(str)
def say_something(stuff):
    print(stuff)

class Communicate(QObject):
    speak = Signal((int,), (str,))

someone = Communicate()
someone.speak.connect(say_something)
someone.speak[str].connect(say_something)

someone.speak.emit(10)
someone.speak[str].emit("Hello everybody!")

speak = Signal((int,), (str,))

ひとつ前のコードではspeak_number = Signal(int)speak_word = Signal(str)という風にそれぞれで宣言していましたが個々のコードではspeak = Signal((int,), (str,))変数一つで宣言しています
型が一つの場合はSignal(Object)であったり、Signal(QByteArray)と書き方が単純ですが
複数の場合は書き方がSignal((), (str,), (int,))になったりするため、少し特殊な書き方になりますがこういうものだと思ってもらえればいいかと思います

QPushButtonを拡張してみる

これまでの内容を基にQPushButtonを拡張してみます
このコードではPushbuttonをクリックすると事前にインプットしていた内容のコードを実行できます
20210831_03

class customPushButton(QPushButton):
    clickedEmitCode = Signal(str)

    def __init__(self, text="", parent=None, code=""):
        super(customPushButton, self).__init__(text, parent)
        self.__code = code
        self.clicked.connect(self.click)
        self.clickedEmitCode[str].connect(self.emitText)

    @property
    def code(self):
        pass

    @code.getter
    def code(self):
        return self.__code

    def click(self):
        self.clickedGetText.emit(self.code)

    @Slot(str)
    def emitText(self, codeText):
        exec(codeText)


class Example(QWidget):
    def __init__(self, parent=None):
        super(Example, self).__init__(parent)
        self.setGeometry(500, 300, 400, 270)
        self.setWindowTitle("Signals & Slots")

        layout = QVBoxLayout(self)

        textList = {
            "polySphere": "cmds.polySphere()",
            "polyCube": "cmds.polyCube()",
            "polyCylinder": "cmds.polyCube()"
        }
        buttons = []
        for count, text in enumerate(textList.keys()):
            buttons.append(customPushButton(text, self, code=textList))
            layout.addWidget(buttons[count])


def main():
    app = QApplication.instance()
    ex = Example()
    ex.show()
    sys.exit()
    app.exec_()


main()

class customPushButton(QPushButton):
    clickedEmitCode = Signal(str)

customPushButtonにclickedEmitCodeというシグナルを用意します

    def __init__(self, text="", parent=None, code=""):
        super(customPushButton, self).__init__(text, parent)
        self.__code = code
    @property
    def code(self):
        pass

    @code.getter
    def code(self):
        return self.__code

codeというプロパティをデコレートします
これは実行したいコードを取得できるようにするためです
codeの設定は宣言時に行うようにしています
ほかにもPushButtonに設定するテキストなども準備しています

    def __init__(self, text="", parent=None, code=""):
        self.clicked.connect(self.click)
        self.clickedEmitCode[str].connect(self.emitText)

    def click(self):
        self.clickedGetText.emit(self.code)

    @Slot(str)
    def emitText(self, codeText):
        exec(codeText)

self.clicked.connect(self.click)でPushbuttonを実行した際にclickedGetTextが発信されるようにしています
clickedGetTextにはemitTextを宣言しておりこれは引数のcodeTextをexec()を使用して実行します

class Example(QWidget):
    def __init__(self, parent=None
        textList = {
            "polySphere": "cmds.polySphere()",
            "polyCube": "cmds.polyCube()",
            "polyCylinder": "cmds.polyCube()"
        }

textListにはkeyに使用したいテキスト情報valueにコード情報を設定しており、textListをforでループすることでcustomPushButtonを生成しています

今回のような例のような活用方法であれば、clickedで済むことが多いですが複数のウィジェットを組み合わせ、カスタムウィジェットなどを作成するときに便利ですので是非覚えてみてください


※ おまけ
こういった事例もあるそうです
Is the PySide Slot Decorator Necessary?
| シグナルをデコレーションされたPythonメソッドに接続することで、使用するメモリ量が減り、若干速くなるという利点もあります。
| マルチスレッド環境では、シグナルが間違ったスレッドに送られる可能性があるため、pyside Slotデコレーターを使用しないことが必須となる場合があります

Maya PySide2 / PySide チュートリアルのこのパートではSignals & Slotsを作成する方法を扱いました

引用及び参考
Qt for Python Signals and Slots