QML(Qt)のドラッグ&ドロップはQt5で簡単に!

今回は、Qt5で新たに増えた新機能の紹介です。
DragエレメントとDragDropAreaエレメントです。
今まではMouseArea::onPressedとonPositionChangedとonReleasedシグナルを使用して自力で座標計算をしないといけませんでしたが、とってもお手軽になります。
それでも大したことは無かったですけど。


いくつか気になったところとかを説明します。
説明用のサンプルは後述。

Drag.hotSpot.xとyプロパティ
これは、DropAreaエレメントへの当たり判定に使うための座標です。
デフォルトでは(0, 0)なので、ドラッグするエレメントの左上がDropAreaに入ったら反応することになります。
サンプルでは、ドラッグ対象の中心に設定しています。

つまり、当たり判定は1ピクセルしか無いので注意です。
ただ、ドラッグするエレメントの範囲ぎりぎりで判定したい場合は、DropAreaエレメントのサイズを見た目より広く設定すればOKです。見えませんので。
処理の簡略化でしょうね。


DropAreaエレメントでのイベント発生タイミング
ドラッグ対象のDrag::activeがtrueでエリア内に入ったとき以下のシグナルが発生します。
onDropped : エリア内でDrag::drop()が呼ばれたとき。
  サンプルにありますが、MouseArea::onReleaseで、Drag.drop()を呼ばないと発生しません。
onEntered : ドラッグ対象がエリアに入ったとき。
onExited : ドラッグ対象がエリアを出たとき。
  エリア内でもonDroppedが起きなかったとき。
onPositionChanged : ドラッグ状態でマウスが動いたとき

サンプル画像で下側はdroppedで終わっているのでDropArea内でマウスを放しています。上側はDropAreaを通過させてます。


以下の情報は6/29 verのQt5のバイナリでなので最終的には状況が変わってるかも。
DropArea内でマウスを放した場合、そのタイミングでonDropped/onExistedが発生します。
その状態(つまりエリア内で放した後)にマウスをダウンしてもonEnteredは発生しません。最初のonPositionChangedのタイミングで発生するようです。(マウスで動かしてる感じでは同時?)
ただ、座標をみるとonExitedのときの座標なのでダウンのタイミングの情報を持って起きているように見えます。またカーソルがドラッグ対象のエリアを出るまで動き始めないのが原因かもですが。
スクリーンショットの2つ目を見ると青のアンダーバーのところの座標は一致しているのですが、赤のアンダーラインのところに(new Date()の値を表示しました)30くらいしか差がありません。
一度ドロップしてからマウスダウンonPositionChangedが起きるまでマウスを1・2秒以上かけてゆっくり操作しましたがほぼ同時に起きているようです。


なにがドロップされるかわからないとき
ドラッグ対象がLoaderなどで動的に生成される場合の扱い方として、DropArea::drag.sourceプロパティを使用します。
ドラッグされているエレメントのオブジェクトがセットされます。
スクリーンショットのログの前半と後半でQtQuickRectangleのアドレスが違うのがわかります。


サンプルスクリーンショット



/// サンプル ///
import QtQuick 2.0

Item {
    width: 400; height: 400

    Text{
        id: _text
        function add(t){
            text = t + "\n" + text;
        }
    }

    DropArea {
        id: _dropArea
        x: 75; y: 75
        width: 100; height: 100

        Rectangle {
            anchors.fill: parent
            color: "green"

            visible: parent.containsDrag
        }
        onDropped: {
            _text.add("dropped:" + drag.x + "," + drag.y + "," + drag.source);
        }
        onEntered: {
            _text.add("entered:" + drag.x + "," + drag.y);
        }
        onExited: {
            _text.add("exited:" + "," + _dropArea.drag.source);
        }
        onPositionChanged: {
            _text.add("positionChanged:" + drag.x + "," + drag.y + "," + _dropArea.drag.source);
        }
    }

    Rectangle {
        x: 10; y: 10
        width: 20; height: 20
        color: "red"

        Drag.active: _dragArea1.drag.active
        Drag.hotSpot.x: width / 2
        Drag.hotSpot.y: height / 2

        MouseArea {
            id: _dragArea1
            anchors.fill: parent

            drag.target: parent
            onReleased: parent.Drag.drop()
        }
    }

    Rectangle {
        x: 300; y: 10
        width: 20; height: 20
        color: "blue"

        Drag.active: _dragArea2.drag.active
        Drag.hotSpot.x: width / 2
        Drag.hotSpot.y: height / 2

        MouseArea {
            id: _dragArea2
            anchors.fill: parent

            drag.target: parent
            onReleased: parent.Drag.drop()
        }
    }
}