シグナルとハンドラのイベントシステム

アプリケーションとユーザーインターフェースのコンポーネントは、互いに通信する必要がある。例えば、ボタンはユーザーがクリックしたことを知る必要がある。ボタンはその状態を示すために色を変えたり、何らかのロジックを実行したりします。同様に、アプリケーションはユーザーがボタンをクリックしたかどうかを知る必要があります。アプリケーションは、このクリック・イベントを他のアプリケーションに中継する必要があるかもしれません。

QMLにはシグナルとハンドラのメカニズムがあり、シグナルはイベントであり、シグナルはシグナルハンドラを通して応答されます。シグナルが発生すると、対応するシグナルハンドラが呼び出されます。ハンドラにスクリプトなどのロジックを記述することで、コンポーネントはイベントに応答することができます。

シグナルハンドラでシグナルを受け取る

特定のオブジェクトに対して特定のシグナルが発せられたときに通知を受け取るには、オブジェクト定義でon<Signal>というシグナルハンドラを宣言します。シグナルハンドラには、シグナルハンドラが呼び出されたときに実行されるJavaScriptコードを記述する。

例えば、Qt Quick ControlsモジュールのButton タイプにはclicked シグナルがあります。この場合、このシグナルを受信するためのシグナル・ハンドラはonClicked になります。以下の例では、ボタンがクリックされるたびに、onClicked ハンドラが呼び出され、親のRectangle にランダムな色が適用されます:

import QtQuick
import QtQuick.Controls

Rectangle {
    id: rect
    width: 250; height: 250

    Button {
        anchors.bottom: parent.bottom
        anchors.horizontalCenter: parent.horizontalCenter
        text: "Change color!"
        onClicked: {
            rect.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1);
        }
    }
}

注意: シグナル・ハンドラはJavaScriptの関数に似ていますが、直接呼び出すべきではありません。シグナルハンドラと他の機能の間でコードを共有する必要がある場合は、別の関数にリファクタリングしてください。そうでなければ、シグナルハンドラを呼び出したい場合は、常にシグナルを発するようにしてください。同じシグナルに対して、異なるスコープで複数のハンドラが存在することがあります。

プロパティの変更シグナルハンドラ

QMLのプロパティの値が変更されると、自動的にシグナルが発せられます。この種のシグナルはプロパティ変更シグナルと呼ばれ、シグナルハンドラは on<Property>Changedという形式で記述されます。

例えば、MouseArea タイプには、pressed プロパティがあります。このプロパティが変更されるたびに通知を受け取るには、onPressedChanged という名前のシグナル・ハンドラを記述します:

import QtQuick

Rectangle {
    id: rect
    width: 100; height: 100

    TapHandler {
        onPressedChanged: console.log("taphandler pressed?", pressed)
    }
}

TapHandler のドキュメントにはonPressedChanged という名前のシグナル・ハンドラは記述されていませんが、pressed プロパティが存在することによって、シグナルは暗黙のうちに提供されます。

シグナル・パラメータ

シグナルはパラメータを持つことがあります。これらにアクセスするには、ハンドラに関数を割り当てる必要があります。アロー関数も無名関数も機能する。

以下の例では、errorOccurred シグナルを持つ Status コンポーネントを考えてみましょう (QML コンポーネントにシグナルを追加する方法については、カスタム QML タイプへのシグナルの追加を参照してください)。

// Status.qml
import QtQuick

Item {
    id: myitem

    signal errorOccurred(message: string, line: int, column: int)
}
Status {
    onErrorOccurred: (mgs, line, col) => console.log(`${line}:${col}: ${msg}`)
}

注意: 関数の正式なパラメータ名は、シグナルのパラメータ名と一致している必要はありません。

すべてのパラメータを処理する必要がない場合は、末尾のパラメータを省略することができる:

Status {
    onErrorOccurred: message => console.log(message)
}

しかし、読者に重要でないことを示すために、何らかのプレースホルダー名を使用することができます:

Status {
    onErrorOccurred: (_, _, col) => console.log(`Error happened at column ${col}`)
}

注: 関数を使用する代わりに、プレーンなコードブロックを使用することも可能ですが、お勧めしません。その場合、すべてのシグナル・パラメータはブロックのスコープにインジェクションされます。しかし、この場合、パラメータがどこから来るのかが不明確になるため、コードが読みづらくなり、QMLエンジンのルックアップが遅くなります。また、QMLエンジンでパラメータを検索する際に時間がかかります。このようなパラメータのインジェクションは非推奨であり、実際にパラメータが使用された場合には実行時に警告が発生します。

コネクション型の使用

シグナルを発するオブジェクトの外からシグナルにアクセスしたい場合があります。このような目的のために、QtQuick モジュールは、任意のオブジェクトのシグナルに接続するためのConnections 型を提供しています。Connections オブジェクトは、指定されたtarget から任意のシグナルを受け取ることができます。

例えば、先ほどの例のonClicked ハンドラーは、onClicked ハンドラーをtargetbutton に設定したConnections オブジェクトに配置することで、代わりにルートRectangle で受け取ることができました:

import QtQuick
import QtQuick.Controls

Rectangle {
    id: rect
    width: 250; height: 250

    Button {
        id: button
        anchors.bottom: parent.bottom
        anchors.horizontalCenter: parent.horizontalCenter
        text: "Change color!"
    }

    Connections {
        target: button
        function onClicked() {
            rect.color = Qt.rgba(Math.random(), Math.random(), Math.random(), 1);
        }
    }
}

付属のシグナルハンドラ

アタッチドシグナルハンドラは、ハンドラが宣言されたオブジェクトではなく、アタッチド型からシグナルを受け取ります。

例えば、Component.onCompleted はアタッチドシグナルハンドラです。これは、JavaScriptの作成処理が完了したときに、JavaScriptのコードを実行するためによく使われます。以下はその例です:

import QtQuick

Rectangle {
    width: 200; height: 200
    color: Qt.rgba(Qt.random(), Qt.random(), Qt.random(), 1)

    Component.onCompleted: {
        console.log("The rectangle's color is", color)
    }
}

onCompleted ハンドラーは、Rectangle 型からのcompleted シグナルに応答していません。その代わりに、completed シグナルを持つComponent アタッチ型のオブジェクトが、QMLエンジンによって自動的にRectangle オブジェクトにアタッチされています。エンジンはRectangleオブジェクトの生成時にこのシグナルを発行し、Component.onCompleted シグナルハンドラをトリガします。

シグナルハンドラがアタッチされることで、各オブジェクトにとって重要な特定のシグナルが通知されるようになります。例えば、Component.onCompleted アタッチド・シグナル・ハンドラがなかった場合、オブジェクトは、特別なオブジェクトからの特別なシグナルに登録することなく、この通知を受け取ることができませんでした。アタッチド・シグナル・ハンドラのメカニズムにより、オブジェクトは余分なコードなしに特定のシグナルを受け取ることができる。

アタッチドシグナルハンドラの詳細については、アタッチドプロパティとアタッチドシグナルハンドラを参照してください。

カスタムQML型にシグナルを追加する

signal 、カスタムQML型にシグナルを追加することができます。

新しいシグナルを定義するための構文は以下の通りです:

signal <name>[([<type> <parameter name>[, ...]])]

シグナルはメソッドとしてシグナルを呼び出すことで発生します。

例えば、以下のコードはSquareButton.qml という名前のファイルで定義されています。ルートRectangle オブジェクトはactivated シグナルを持っており、子TapHandlertapped になるたびにシグナルが発せられます。この例では、アクティブ化されたシグナルはマウスクリックの x 座標と y 座標で発せられます:

// SquareButton.qml
import QtQuick

Rectangle {
    id: root

    signal activated(real xPosition, real yPosition)
    property point mouseXY
    property int side: 100
    width: side; height: side

    TapHandler {
        id: handler
        onTapped: root.activated(root.mouseXY.x, root.mouseXY.y)
        onPressedChanged: root.mouseXY = handler.point.position
    }
}

これで、SquareButton のどのオブジェクトも、onActivated シグナル・ハンドラを使用して、activated シグナルに接続できるようになります:

// myapplication.qml
SquareButton {
    onActivated: (xPosition, yPosition) => console.log(`Activated at {xPosition}, ${yPosition}`)
}

カスタムQMLタイプのシグナルの書き方についてはシグナル属性を参照してください。

シグナルをメソッドやシグナルに接続する

シグナルオブジェクトには、シグナルをメソッドや別のシグナルに接続するためのconnect() メソッドがあります。シグナルがメソッドに接続されると、そのシグナルが発せられるたびに自動的にそのメソッドが呼び出されます。このメカニズムにより、シグナルをシグナル・ハンドラの代わりにメソッドで受け取ることができます。

以下では、connect() メソッドを使って、messageReceived シグナルを3つのメソッドに接続しています:

import QtQuick

Rectangle {
    id: relay

    signal messageReceived(string person, string notice)

    Component.onCompleted: {
        relay.messageReceived.connect(sendToPost)
        relay.messageReceived.connect(sendToTelegraph)
        relay.messageReceived.connect(sendToEmail)
        relay.messageReceived("Tom", "Happy Birthday")
    }

    function sendToPost(person: string, notice: string) {
        console.log(`Sending to post: ${person}, ${notice}`)
    }
    function sendToTelegraph(person: string, notice: string) {
        console.log(`Sending to telegraph: ${person}, ${notice}`)
    }
    function sendToEmail(person: string, notice: string) {
        console.log(`Sending to email: ${person}, ${notice}`)
    }
}

多くの場合、connect()関数を使わず、シグナル・ハンドラでシグナルを受け取れば十分です。しかし、connect メソッドを使うと、先に示したように、シグナルを複数のメソッドで受け取ることができます。これは、シグナル・ハンドラでは一意に命名する必要があるため不可能です。また、connect メソッドは、動的に生成されるオブジェクトにシグナルを接続するときに便利です。

接続されたシグナルを削除するには、対応するdisconnect() メソッドがあります:

Rectangle {
    id: relay
    //...

    function removeTelegraphSignal() {
        relay.messageReceived.disconnect(sendToTelegraph)
    }
}

シグナルとシグナルの接続

信号を他の信号に接続することで、connect()

import QtQuick

Rectangle {
    id: forwarder
    width: 100; height: 100

    signal send()
    onSend: console.log("Send clicked")

    TapHandler {
        id: mousearea
        anchors.fill: parent
        onTapped: console.log("Mouse clicked")
    }

    Component.onCompleted: {
        mousearea.tapped.connect(send)
    }
}

TapHandlertapped シグナルが発信されると、自動的にsend シグナルも発信される。

output:
    MouseArea clicked
    Send clicked

注意: 関数オブジェクトへの接続は、シグナルの送信者が生きている限り、生き続けます。この動作は、C++のQObject::connect ()の3引数バージョンに似ています。

Window {
    visible: true
    width: 400
    height: 400

    Item {
        id: item
        property color globalColor: "red"

        Button {
            text: "Change global color"
            onPressed: {
                item.globalColor = item.globalColor === Qt.color("red") ? "green" : "red"
            }
        }

        Button {
            x: 150
            text: "Clear rectangles"
            onPressed: repeater.model = 0
        }

        Repeater {
            id: repeater
            model: 5
            Rectangle {
                id: rect
                color: "red"
                width: 50
                height: 50
                x: (width + 2) * index + 2
                y: 100
                Component.onCompleted: {
                    if (index % 2 === 0) {
                        item.globalColorChanged.connect(() => {
                            color = item.globalColor
                        })
                    }
                }
            }
        }
    }
}

上の作為的な例では、すべての偶数矩形の色を反転させ、あるグローバルな色に従うようにすることが目的である。これを達成するために、すべての偶数矩形について、globalColorChangedシグナルと矩形の色を設定する関数との接続が行われる。これは、矩形が生きている間は期待通りに動作する。しかし、クリアボタンが押されると、矩形は消えてしまいますが、シグナルが発せられるたびにシグナルを処理する関数が呼び出されます。これは、グローバル・カラーを変更するときにバックグラウンドで実行しようとする関数によって投げられるエラー・メッセージでわかる。

現在のセットアップでは、globalColorを保持しているアイテムが破棄されると、コネクションは破棄されます。接続が長引かないようにするには、矩形が破棄されるときに接続を明示的に切断します。

©2024 The Qt Company Ltd. 本書に含まれる文書の著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。