【Qt】コード付き、QObject 逆引きリファレンス

どうもです、タドスケです。

Qt を扱う上での土台となる QObject クラス。

これまで派生クラスの QWidget の使い方などを調べるうちに、何となく QObject 側の機能を使っていましたが、ふと「QObject にどんな機能があるのか」をちゃんと調べたことがなかったなぁと思いまして。

QObject クラスに関して、「できること」ベースの逆引きリファレンスを作ることにしました。

執筆時点での検証環境は以下です。

  • Windows 11
  • Python 3.11
  • Pyside6
  • PyCharm 2023.1.4 (Community Edition)

バージョンが異なる場合、そのままのコードでは動かないことがありますのでご了承ください。

目次

コード

このページで紹介しているサンプルのコードは、以下にアップしています。

pyside6/object at master · tadosuke/pyside6 · GitHub

シグナルの発行を一時的に抑える

from PySide6.QtCore import QObject, Signal

def _on_done():
    print('done')

class MyObject(QObject):
    done = Signal()

    def do_something(self):
        self.done.emit()


my_obj = MyObject()
my_obj.done.connect(_on_done)

print(my_obj.signalsBlocked())
#  False

my_obj.do_something()
#  done

# シグナルをブロック
my_obj.blockSignals(True)
print(my_obj.signalsBlocked())
#  True

my_obj.do_something()
#  (何も出ない)

子オブジェクトを取得する

from PySide6.QtCore import QObject

parent = QObject()
child1 = QObject(parent=parent)
child2 = QObject(parent=parent)

print(parent.children())
#  [child1, child2]

シグナルとスロットを接続/切断する

from PySide6.QtCore import QObject, Signal, QMetaMethod


def slot():
    print('slot')

class MyObject(QObject):
    something_done = Signal()

    def do_something(self):
        self.something_done.emit()


obj = MyObject()

# 接続
obj.something_done.connect(slot)

# シグナルが接続されているかを調べる
meta_method = QMetaMethod.fromSignal(obj.something_done)
print(obj.isSignalConnected(meta_method))
#  True

obj.do_something()
#  slot

# 切断
obj.something_done.disconnect(slot)

obj.do_something()
#  (何も表示されない)
print(obj.isSignalConnected(meta_method))
#  False

オブジェクトのデバッグ情報を出力する

from PySide6.QtCore import QObject, Signal

class MyObject(QObject):
    signal = Signal()
    def __init__(self):
        super().__init__()
        self.value = 1

def slot():
    pass

obj = MyObject()
obj.signal.connect(slot)

obj.dumpObjectInfo()
# OBJECT MyObject::unnamed
#   SIGNALS OUT
#         signal: signal()
#           --> __GlobalReceiver__::unnamed slot29559d86480()
#   SIGNALS IN
#         <None>

dumpObjectInfo の実行結果はデバッグ出力に出るので、PyCharm などの IDE 上から実行すると何も出ないことがあります。
今回のサンプルでは、コマンドプロンプトからスクリプトを実行して確認しています。

オブジェクトツリーの構造を出力する

from PySide6.QtCore import QObject

obj_a = QObject()
obj_a.setObjectName('A')

obj_b = QObject(parent=obj_a)
obj_b.setObjectName('B')

obj_c = QObject(parent=obj_a)
obj_c.setObjectName('C')

obj_d = QObject(parent=obj_b)
obj_d.setObjectName('D')

obj_a.dumpObjectTree()
# QObject::A
#     QObject::B
#         QObject::D
#     QObject::C

出力内容には、setObjectName で設定したオブジェクト名が表示される。

dumpObjectInfo の実行結果はデバッグ出力に出るので、PyCharm などの IDE 上から実行すると何も出ないことがあります。
今回のサンプルでは、コマンドプロンプトからスクリプトを実行して確認しています。

プロパティを取得/設定する

from PySide6.QtCore import QObject, Property

class MyObject(QObject):
    def __init__(self):
        super().__init__()
        self._a = 1

    def get_a(self):
        return self._a

    def set_a(self, value):
        self._a = value

    property_a = Property(int, get_a, set_a)


obj = MyObject()

# プロパティを取得
print(obj.property('property_a'))
#  1

# 動的プロパティを追加
obj.setProperty('property_b', 'hoge')

# 動的に追加されたプロパティも取得できる
print(obj.property('property_b'))
#  hoge

# 動的プロパティの名前だけを取得する
for name in obj.dynamicPropertyNames():
    # dynamicPropertyNames は QByteArray を返すので、
    # 文字列表示するために変換
    print(bytes(name).decode())
#  property_b

ここでいう「プロパティ」とは、python 標準のプロパティ(@property で定義できるもの)とは違い、Qt 独自のものです。
Qt 側の プロパティには、変更時にシグナルを発行するなど、Qt ならではの機能があります。

シグナルを手動で発行する

from PySide6.QtWidgets import QPushButton, QApplication

class MyButton(QPushButton):
    def __init__(self):
        super().__init__('Button')

    def emulate_click(self):
        """クリックしたことにする."""
        self.clicked.emit()

def _on_clicked():
    print('clicked')


app = QApplication()
button = MyButton()
button.clicked.connect(_on_clicked)
button.emulate_click()
# クリックしていなくても「clicked」が出力される

通常はユーザー操作でしか発生しない、QPushButton の clicked シグナルを発行している。

これを利用すれば、スクリプトによる自動操作などもできる。

子オブジェクトを検索する

from PySide6.QtCore import QObject

obj_a = QObject()
obj_a.setObjectName('A')

# 子オブジェクトを追加
obj_b = QObject(parent=obj_a)  # B の親を A に設定
obj_b.setObjectName('B')
obj_c = QObject(parent=obj_a)
obj_c.setObjectName('C')

# 子オブジェクトを一つだけ検索する
print(obj_a.findChild(QObject))
#  最初に登録した B のみが取得される

# QObject タイプの子オブジェクトを全て取得する
print(obj_a.findChildren(QObject))
#  B, C が取得される

# 名前が「C」の QObject を検索する
print(obj_a.findChildren(QObject, name='C'))
#  C のみが取得される

指定クラスを継承しているかを調べる

from PySide6.QtWidgets import QWidget, QApplication, QPushButton

app = QApplication()

widget = QWidget()
button = QPushButton('Button')

print(widget.inherits('QObject'))
#  True
print(button.inherits('QWidget'))
#  True
print(button.inherits('QObject'))  # 親の親でも判定可能
#  True
print(widget.inherits('QPushButton'))
#  False

【派生クラス専用】オブジェクトのイベントを検知する

from PySide6.QtCore import QObject, QEvent, Qt
from PySide6.QtWidgets import QPushButton, QApplication

class MyButton(QPushButton):
    def __init__(self):
        super().__init__('Button')
        # イベント発生時に eventFilter が呼ばれるようになる
        self.installEventFilter(self)

        # イベントフィルターを削除したいときに呼ぶ
        # self.removeEventFilter(self)

    def eventFilter(self, watched: QObject, event: QEvent):
        # 右クリックイベントを処理する
        if event.type() == QEvent.MouseButtonPress:
            mouse_event = event
            if mouse_event.button() == Qt.RightButton:
                print("Right Click")
                return True  # 処理したら True を返す
        return False  # 処理しなかったら False を返す

app = QApplication()
button = MyButton()
button.show()
app.exec()

installEventFilter にQObject を渡すと、イベント発生時にオブジェクトの eventFilter という関数が呼ばれる。

QObject クラスの eventFilter は空になっており、何もしない。

イベントの種類は、QObject の派生クラス(QWidget、QPushButton など)に応じて様々なものがある。

↓詳しくはこちら

今回のサンプルでは、ボタンを右クリックするとメッセージが出力される。

removeEventFilter を呼ぶと、eventFilter は呼ばれなくなる。

ウィジェット/ウィンドウかどうかを調べる

from PySide6 import QtGui, QtWidgets
from PySide6.QtWidgets import QApplication


app = QApplication()

main_window = QtWidgets.QMainWindow()
print(main_window.isWidgetType())
print(main_window.isWindowType())
#  True, False

gui_window = QtGui.QWindow()
print(gui_window.isWidgetType())
print(gui_window.isWindowType())
#  False, True

ややこしいのは、QtWidgets にある QMainWindow はウィジェットとして扱われる点。

公式のリファレンスによれば、inherits(“QWindow”) を呼ぶのと同じとのこと。
QtWidgets 以下のクラスを使っている限りは、使う機会は無いように思える。

よく似た関数に QWidget.isWindow() があり、そちらは QMainWindow でも True を返します。

一定間隔でイベントを発生させる

from PySide6.QtCore import QObject, QTimerEvent
from PySide6.QtWidgets import QApplication

class MyObject(QObject):
    def __init__(self) -> None:
        super(MyObject, self).__init__()
        # 1000ミリ秒(1秒)ごとにタイマーイベントを発生させる
        self.timer_id = self.startTimer(1000)
        self.num = 0

    def timerEvent(self, event: QTimerEvent) -> None:
        print(f'timerEvent:{self.num}')
        self.num += 1
        if 5 <= self.num:
            # タイマーイベントを停止させる
            self.killTimer(self.timer_id)

app = QApplication()
obj = MyObject()
app.exec()

実行すると1秒ごとにログが出力され、5回呼ばれると停止する。

オブジェクトの名前を取得/設定する

from PySide6.QtCore import QObject

def _on_object_name_changed(name):
    """オブジェクト名が変更されたときに呼ばれる."""
    print(f'ObjectNameChanged : {name}')

obj = QObject()
obj.objectNameChanged.connect(_on_object_name_changed)  # 変更時のシグナルに接続

obj.setObjectName('A')
#  ObjectNameChanged : A

print(obj.objectName())
#  A

親オブジェクトを取得/設定する

from PySide6.QtCore import QObject

obj_a = QObject()
obj_a.setObjectName('A')

obj_b = QObject(parent=obj_a)  # B の親を A に設定
obj_b.setObjectName('B')
print(obj_b.parent().objectName())
#  A

obj_c = QObject()
obj_c.setObjectName('C')
obj_b.setParent(obj_c)  # 親を A → C に付け替える
print(obj_b.parent().objectName())
#  C

シグナルに接続されているスロットの数を取得する

from PySide6.QtCore import QObject, Signal, SIGNAL

def slot1():
    pass

def slot2():
    pass

def slot3():
    pass

class MyObject(QObject):
    my_signal = Signal()

obj = MyObject()
obj.my_signal.connect(slot1)
obj.my_signal.connect(slot2)
obj.my_signal.connect(slot3)

print(obj.receivers(SIGNAL('my_signal()')))
#  3

SIGNAL を使わずに文字列だけを指定したり、シグナル名のカッコ「()」を付け忘れると正しく取得できないので注意。

receivers のリファレンスを見ると、引数の型が str になっているが、ソースコード側の型ヒントは bytes になっている。
リファレンス通りのやり方で動いたので、ソースコード側の更新忘れかも?

シグナルを送信したオブジェクトについて調べる

from PySide6.QtCore import QObject, Signal

class MySlot(QObject):
    def func(self):
        print(self.sender())
        print(self.senderSignalIndex())

class MyObjectA(QObject):
    signal1 = Signal()
    signal2 = Signal()

    def do_something(self):
        self.signal1.emit()
        self.signal2.emit()

class MyObjectB(QObject):
    signal = Signal()

    def do_something(self):
        self.signal.emit()


slot = MySlot()

obj1 = MyObjectA()
obj1.signal1.connect(slot.func)
obj1.signal2.connect(slot.func)
obj1.do_something()
#  <__main__.MyObjectA(*) at *>
#  5
#  <__main__.MyObjectA(*) at *>
#  6

obj2 = MyObjectB()
obj2.signal.connect(slot.func)
obj2.do_something()
#  <__main__.MyObjectB(*) at *>
#  5

オブジェクトの削除を予約する/削除されたことを知らせる

from PySide6.QtWidgets import QApplication, QPushButton, QWidget

def _on_destroy():
    print('destroyed')
    if widget is not None:  # 破棄されても None にはならないので注意!
        # 既に破棄されているのでエラーになる
        print(widget)

def _on_clicked():
    print('deleteLater')
    widget.deleteLater()  # 削除を予約する
    # このタイミングではまだ生きている
    print(widget)


app = QApplication()

button = QPushButton('Button')
button.clicked.connect(_on_clicked)

widget = QWidget()
widget.destroyed.connect(_on_destroy)  # 削除されたときのシグナル

button.show()
app.exec()

deleteLater が呼ばれた瞬間ではなく、次のイベントループで削除されること、オブジェクトが削除されても変数は None にならないことに注意。

このサンプルでは、削除されたオブジェクトにアクセスしようとしてエラーが発生する。

オブジェクトを別スレッドで実行する/実行しているスレッドを調べる

import sys

from PySide6.QtCore import QObject, QThread
from PySide6.QtWidgets import QApplication


class Worker(QObject):

    def do_work(self) -> None:
        # 今いるスレッドを表示
        print(f"{self.thread()=}")


app = QApplication(sys.argv)

worker = Worker()
worker.do_work()

# ワーカーをスレッドに移動
thread = QThread()
worker.moveToThread(thread)
thread.start()

worker.do_work()

この方法で QWidget などの GUI 要素を別スレッドに移動してはいけません。
Qt に限らず、一般的に GUI はメインスレッドで実行されることが前提になっています。
別スレッドで実行した場合、予期せぬ不具合やクラッシュが発生する恐れがあります。
(QObject は GUI 要素ではないので大丈夫です)

翻訳されたテキストを取得する

from PySide6.QtCore import QObject, QTranslator
from PySide6.QtWidgets import QApplication


class MyObject(QObject):
    def __init__(self) -> None:
        super().__init__()
        print(self.tr("Hello!"))
        #  翻訳データを読み込んでいれば、翻訳テキストが代わりに表示される


if __name__ == "__main__":
    app = QApplication()

    # 翻訳データを読み込む
    translator = QTranslator()
    if translator.load("translation.qm"):
        app.installTranslator(translator)

    obj = MyObject()

翻訳データは別に用意する必要があります。
このサンプルでは、以下の記事を参考に qm ファイルを用意しました。

【PySide】ソフト文字列をQTranslatorで翻訳・多言語化する手順を解説! | PisukeCode – Web開発まとめ (pisuke-code.com)

※僕は pyside6 のツール類にパスを通していなかったので、ターミナルから exe を直接実行しました。

関連記事

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメント一覧 (1件)

コメントする

目次