QML(Qt)でマウスイベントの拾い方

マウスイベントの処理方法です。

今回は、プロジェクトを作成した際に作成される「Hello World」をベースに解説します。
前回の記事「QML(Qt)のアプリケーションを作る(プロジェクト作成)」を参考に「Qt Quick UI」プロジェクトを作成してください。

こんなソースが出来るはずです。
import QtQuick 1.0

Rectangle {
    width: 360
    height: 360
    Text {
        anchors.centerIn: parent
        text: "Hello World"
    }
    MouseArea {
        anchors.fill: parent
        onClicked: {
            Qt.quit();
        }
    }
}

これを実行するとウインドウの真ん中に「Hello World」と表示されて適当な位置を左クリックするとアプリケーションが終了します。
「MouseArea」エレメントでマウスの入力を拾うオブジェクトを定義しています。
また、「onClicked」ハンドラでクリックが発生した後に実行する処理を記述します。
「anchors.fill: parent」になっていますので親の「Rectangle」全体が対象範囲になります。

以上!で終わったら面白く無いです。

で、

/// 今回のポイント ///
・右クリック、左クリックの扱いに気をつける
・イベントを拾うオブジェクトがいないと一番後ろのオブジェクトにまで届く
・pressedButtonsプロパティに注意

/// 右クリック、左クリックの扱いに気をつける ///
こちらから。
上記のサンプルだと左クリックしか反応しません。
「MouseArea#acceptedButtons」プロパティがデフォルトで「Qt.LeftButton」となっているからです。
サンプルを下のように書き換えてみましょう。
右クリックも反応するようになります。

import QtQuick 1.0

Rectangle {
    width: 360
    height: 360
    Text {
        anchors.centerIn: parent
        text: "Hello World"
    }
    MouseArea {
        anchors.fill: parent
        //右クリックも受け付けるように追加
        acceptedButtons: Qt.LeftButton | Qt.RightButton    
        onClicked: {
            Qt.quit();
        }
    }
}


/// イベントを拾うオブジェクトがいないと一番後ろのオブジェクトにまで届く ///

言葉通りなのですが、下のようにサンプルを書き換えてみましょう。

import QtQuick 1.0

Rectangle {
    width: 360
    height: 360
    Text {
        anchors.centerIn: parent
        text: "Hello World"
        // テキストにもエリアを作成
        MouseArea {
            anchors.fill: parent
            onClicked: {
                // クリックしたら文字列を変更する
                parent.text = "clicked!";
            }
        }
    }
    MouseArea {
        anchors.fill: parent
        acceptedButtons: Qt.LeftButton | Qt.RightButton
        onClicked: {
            Qt.quit();
        }
    }
}

テキストをクリックしたら「Hello World」が「clicked!」になると思いきやアプリが終了してしまいます。
これは「MouseArea」が「Text」の手前にいるからです。
QMLでは基本的に後に定義したものが手前にきます。

では、下記のようにサンプルを書き換えてみましょう。

import QtQuick 1.0

Rectangle {
    width: 360
    height: 360
    MouseArea {
        anchors.fill: parent
        acceptedButtons: Qt.LeftButton | Qt.RightButton
        onClicked: {
            Qt.quit();
        }
    }
    Text {
        anchors.centerIn: parent
        text: "Hello World"
        // テキストにもエリアを作成
        MouseArea {
            anchors.fill: parent
            onClicked: {
                // クリックしたら文字列を変更する
                parent.text = "clicked!";
            }
        }
    }
}

今度は「Hello World」が「clicked!」に変わります。
ただし、左クリックした場合のみです。右クリックは「Text」に配置した「MouseArea」で拾うように設定されていないので後ろのいる「MouseArea」までイベントが届いてアプリが終了します。
つまり、正しく左右中クリックを処理しないと意図しないオブジェクトがイベントを拾ってしまいます。


/// pressedButtonsプロパティに注意 ///
どのボタンが押されたかがわかるプロパティですが注意が必要です。
本当に押されている時にしか値がセットされません。

サンプルを更に下のように書き換えてどのボタンが押されたかわかるようにしてみましょう。
import QtQuick 1.0

Rectangle {
    width: 360
    height: 360
    MouseArea {
        anchors.fill: parent
        acceptedButtons: Qt.LeftButton | Qt.RightButton
        onClicked: {
            Qt.quit();
        }
    }
    Text {
        anchors.centerIn: parent
        text: "Hello World"
        // テキストにもエリアを作成
        MouseArea {
            anchors.fill: parent
            acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
            onClicked: {
                // クリックしたら文字列を変更する
                switch(pressedButtons){
                case Qt.LeftButton:
                    parent.text = "left button clicked!";
                    break;
                case Qt.RightButton:
                    parent.text = "right button clicked!";
                    break;
                case Qt.MiddleButton:
                    parent.text = "middle button clicked!";
                    break;
                default:
                    parent.text = "unknown button clicked!";
                    break;
                }
            }
        }
    }
}

どのボタンを押しても「unknown button clicked」と表示されてしまうはずです。
実際には「pressedButtons = 0」となっていてボタンは「押されていない」状況になります。
「onClicked」ハンドラはクリックされた後に発生するイベントなので当然なのですが、なんとなく押された過去の状況が保存されているのかな?と勘違いしてたある意味恥ずかしい話だったりします。

なので「pressedButtons」を活用する場合は下のようにサンプルを変更します。

import QtQuick 1.0

Rectangle {
    width: 360
    height: 360
    MouseArea {
        anchors.fill: parent
        acceptedButtons: Qt.LeftButton | Qt.RightButton
        onClicked: {
            Qt.quit();
        }
    }
    Text {
        anchors.centerIn: parent
        text: "Hello World"
        // テキストにもエリアを作成
        MouseArea {
            anchors.fill: parent
            acceptedButtons: Qt.LeftButton | Qt.RightButton | Qt.MiddleButton
            // 押した時のイベントに変更
            onPressed: {
                // クリックしたら文字列を変更する
                switch(pressedButtons){
                case Qt.LeftButton:
                    parent.text = "left button clicked!";
                    break;
                case Qt.RightButton:
                    parent.text = "right button clicked!";
                    break;
                case Qt.MiddleButton:
                    parent.text = "middle button clicked!";
                    break;
                default:
                    parent.text = "unknown button clicked!";
                    break;
                }
            }
            // 放した時のイベントを追加
            onReleased: {
                // 放したら元に戻す
                parent.text = "Hello World";
            }
        }
    }
}

今度はボタンが押されている時だけメッセージが正しく表示されます。