QtAbstractListModel の QML への公開
概要
この例は、2つの別々のプロジェクトで構成されています:QML プロジェクトと、その QML コンテンツをホストし表示する Kotlin ベースの Android プロジェクトです。QtAbstractListModel を用いて、Android 側から QML ビューにデータを共有し、ListView を用いてデータを表示する方法を示します。
例の実行
このサンプルを実行するには、Android Studio とQt for Androidが必要です。
Qt Gradle Pluginは、Androidプロジェクトのビルドプロセス中にQMLプロジェクトをビルドするために使用されます。そのため、この例ではアプリレベルのbuild.gradle.ktsファイルにプラグインの設定を記述していますが、プラグインがQtキットのディレクトリを見つけられない場合などは、プラグインの設定を変更する必要があります。
QtBuild { // Relative for Qt (Installer or MaintenanceTool) installations. qtPath = file("../../../../../../../6.8.0") projectPath = file("../../qtabstractlistmodel") }
プラグインの詳細な設定については、Qt Gradle Pluginのドキュメントを参照してください。
QMLプロジェクト
QMLプロジェクトは非常にシンプルで、ルートオブジェクトのプロパティとしてデータモデルを定義し、そのモデルからのデータを表示するためのいくつかのUI要素を定義します。
Rectangle { id: mainRectangle property AbstractItemModel dataModel
モデルからのデータを表示するために、ListView が作成されます。そして、model
プロパティが、先に宣言されたデータ・モデルに設定される。
ListView { id: listView model: mainRectangle.dataModel
データモデルを表示するには、ListView 、データモデルの各項目ごとにインスタンス化されるデリゲートが必要です。この場合、デリゲートはRectangle 、2つのText 要素をColumn に保持し、データ・モデルの各要素のデータを表示する。
delegate: Rectangle { required property var model width: listView.width height: textColumn.height + (2 * textColumn.spacing) color: "#2CDE85" radius: 25 Column { id: textColumn height: idText.height + rowText.height + spacing spacing: 15 anchors { verticalCenter: parent.verticalCenter left: parent.left right: parent.right leftMargin: 20 rightMargin: 20 } Text { id: idText color: "#00414A" text: model.id font.pixelSize: 36 font.bold: true } Text { id: rowText color: "#00414A" text: model.row font.pixelSize: 36 font.bold: true } } }
Kotlinプロジェクト
Android側は、1つのアクティビティと、先ほどQMLビューで使用したデータモデルの定義で構成されます。
データモデル
データモデルMyListModel
はQtAbstractListModel の子クラスで、ArrayList<String>
がデータの内部ストレージシステムです。MyListModel
のイニシャライザー・ブロックでは、リスト用のランダムなデータを生成します。
class MyListModel : QtAbstractListModel() { private val m_dataList = ArrayList<String>() init { synchronized(this) { for (row in 0..4) { m_dataList.add(UUID.randomUUID().toString()) } } }
モデルの各項目は、それに関連付けられたデータ要素のセットを持っており、それぞれが独自の役割を持っています。QtAbstractItemModel のカスタム実装では、各データ要素に対してカスタムのロールを定義する必要があります。各ロールは、データを取得する際に使用されるInt値と、QML から使用する際のデータ要素の名前を指定するString値を持ちます。
@Synchronized override fun roleNames(): HashMap<Int, String> { val m_roles = HashMap<Int, String>() m_roles[DataRole.UUID.value()] = "id" m_roles[DataRole.Row.value()] = "row" return m_roles }
"roleNames()"
メソッド内のInt
の値はハードコードされているかもしれませんが、この例ではMyListModel
内にカスタム enum クラスDataRole
を指定し、これらの値を参照する際に使用しています。この例では、2つのロールを定義しています:UUIDとRowである。
private enum class DataRole(val m_value: Int) { UUID(0), Row(1); fun value(): Int { return m_value } companion object { fun valueOf(value: Int): DataRole? { val values = entries.toTypedArray() if (0 <= value && value < values.size) return values[value] return null } } }
データ・モデルからデータを返す場合、クラスは"QtAbstractListModel::data()"
メソッドをオーバーライドする必要があります。このメソッドは2つのパラメータを取ります:QtModelIndexとInt
は、それぞれデータ要素のインデックスとロールを表します。
"MyDataModel::data()"
では、UUID
の役割は内部データ内の指定されたインデックスからのデータを返し、Row
の役割は要求された要素の行を返します。
注: このメソッドは、他のいくつかのメソッドとともに、@Synchronizedタグでアノテーションされています。これは、これらのメソッドへの呼び出しがQtスレッドから発生し、"addRow()"
および"removeRow()"
メソッドを介したAndroidスレッドからのリクエストと同時に、基礎となるデータにアクセスする可能性があるためです。
@Synchronized override fun data(qtModelIndex: QtModelIndex, role: Int): Any { return when (DataRole.valueOf(role)) { DataRole.UUID -> "UUID: " + m_dataList[qtModelIndex.row()] DataRole.Row -> "Row: " + qtModelIndex.row() else -> "" } }
外部のアクターがQtAbstractItemModelを操作できるように、この例ではMyDataModel
に2つのメソッドを追加しています。行にデータを追加するには"addRow()"
、データを削除するには"removeRow()"
。これらはメイン・アクティビティから使用されます。
@Synchronized fun addRow() { beginInsertRows(QtModelIndex(), m_dataList.size, m_dataList.size) m_dataList.add(UUID.randomUUID().toString()) endInsertRows() } @Synchronized fun removeRow() { if (!m_dataList.isEmpty()) { beginRemoveRows(QtModelIndex(), m_dataList.size - 1, m_dataList.size - 1) m_dataList.removeAt(m_dataList.size - 1) endRemoveRows() } }
メイン・アクティビティ
MainActivity
クラスはシンプルなKotlinベースのアクティビティですが、QtQmlStatusChangeListenerというインターフェイスを実装しており、QMLの読み込みステータスイベントをリスニングします。また、QMLアプリケーションのメインビューのQtQuickViewContent
オブジェクトと、上記のデータモデルのインスタンスも格納しています。
class MainActivity : AppCompatActivity(), QtQmlStatusChangeListener { private val m_mainQmlContent: Main = Main() private val m_listModel = MyListModel()
アプリケーションのメインアクティビティを作成する際、このサンプルではまずQtQuickViewを作成し、ビュー階層に配置します。
val qtQuickView: QtQuickView = QtQuickView(this) val params: ViewGroup.LayoutParams = FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ) val qmlFrameLayout: FrameLayout = findViewById<FrameLayout>(R.id.qmlFrame) qmlFrameLayout.addView(qtQuickView, params)
QtQuickViewをUIに追加した後、データモデルを操作するためのボタンを見つけ、メンバーデータモデルのaddRow()
、removeRow()
を呼び出すためのクリックリスナーを設定します。
val addRowAtEndButton: Button = findViewById<Button>(R.id.addRow) val removeRowFromEndButton: Button = findViewById<Button>(R.id.removeRow) addRowAtEndButton.setOnClickListener { _: View? -> m_listModel.addRow() } removeRowFromEndButton.setOnClickListener { _: View? -> m_listModel.removeRow() }
UIのセットアップとリスナーが完了したら、QMLコンポーネントの準備と読み込みを行います。この例では、MainActivity
をQMLコンポーネントの状態変化シグナルのリスナーとして設定し、QtQuickView
にQMLコンポーネントを読み込むように指示しています。
m_mainQmlContent.setStatusChangeListener(this) qtQuickView.loadContent(m_mainQmlContent)
最後に、QML コンポーネントのロードが成功したら、MyDataModel インスタンスの値を QML コンポーネントのdataModel
プロパティに代入します。
override fun onStatusChanged(qtQmlStatus: QtQmlStatus) { if (qtQmlStatus === QtQmlStatus.READY) { m_mainQmlContent.setDataModel(m_listModel) } }
©2024 The Qt Company Ltd. 本書に含まれるドキュメントの著作権は、それぞれの所有者に帰属します。 本書で提供されるドキュメントは、Free Software Foundation が発行したGNU Free Documentation License version 1.3に基づいてライセンスされています。 Qtおよびそれぞれのロゴは、フィンランドおよびその他の国におけるThe Qt Company Ltd.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。