Qt Quick で双方向のバインディングを実現する
はじめに
Qt Quick でよくハマるところの一つがプロパティのバインディングと代入まわりです。
特に、このオブジェクトのこのプロパティと、べつのオブジェクトのこのプロパティを相互に同期しようと以下のように記載すると
大量に
これを解決するためのいくつかの方法が Qt World Summit 2019 Berlin の QML Component Design: the two-way binding problem というセッションで紹介されていました。
この動画の冒頭では「うまくいかない方法」がたくさん紹介されています。とても勉強になるのでぜひご覧ください。
また、うまく行く方法として KDAB で実際に行っている方法が3パターン紹介されています。
プレゼン資料: Two way bindings: Component Design in QtQuick
一番スマートな方法を試してみた
今回試したのは最後に紹介されている方法です。
id: colorCheckbox
TwoWayBinding on checked {
backendObject: SomeController
backendProperty: "isBlue"
}
}
ここで、
Property Modifier Objects
文法的にはこれは「Property Modifier Objects」と呼ばれるもので、対象のプロパティに初期値を設定したり、何かの状態に応じて値を更新したりする際に用いられます。
Property Value Sources に具体的な作り方と、サンプルコードが記載されているので一度確認してみてください。
これを参考に、PropertySync というエレメントを作成しました。
冒頭のコードは以下のように書くことで、Slider と SpinBox を操作した際に相互の値が同期されるようになります。
フロントエンドとバックエンドの同期
Qt Quick でアプリケーションを開発する際は、C++ 側にロジックを書いて、それを Qt Quick から操作できるようにするのが一般的です。その際に、以下のように記載することで、バックエンドの API と、Qt Quick のコントロールのプロパティを同期することができるようになります。
anchors.fill: parent
Switch {
text: 'loading: ' + api.loading
PropertySync on checked {
target: api
propertyName: 'loading'
}
}
Button {
text: 'Toggle'
onClicked: api.loading = !api.loading
}
}
Switch の value が変更された際に、api.loading に反映されるようになっています。
また、api.loading が変更された際には Switch の value に反映されるようになっています。
同様のことを普通にやろうとすると、Connections で、api の loading の変更を捕まえたり、その他にもいくつかやり方はありますが、PropertySync を利用するととてもシンプルに書くことができます。
(もう一歩シンプルにならないかなぁとも思っています)
モデルビューでも使える
Delegate にモデルの値の表示と変更をするコントロールを置いて、変更時にモデルのデータを更新したり、裏でモデルのデータが更新された際には最新の値を表示するようなケースがよくあります。
その場合も以下のように、Delegate で利用可能な特殊な変数 model のプロパティと同期させることで簡単に扱うことができるようになります。
model: ListModel {
id: model
ListElement {
name: "Hello"
}
ListElement {
name: "World"
}
}
delegate: RowLayout {
width: ListView.view.width
height: 40
Text {
Layout.fillWidth: true
Layout.preferredWidth: 1
text: model.name
}
TextField {
Layout.fillWidth: true
Layout.preferredWidth: 1
PropertySync on text {
target: model
propertyName: "name"
}
}
}
Button {
anchors.right: parent.right
anchors.bottom: parent.bottom
text: 'Reset World'
onClicked: model.setProperty(1, 'name', 'World')
}
}
おわりに
Qt Quick で、2つのオブジェクトのプロパティを相互に同期する方法を紹介しました。
C++ 側での拡張が必要にはなりますが、記述がとても簡潔なので是非試していただければと思います。