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ビューで使用したデータモデルの定義で構成されます。

データモデル

データモデルMyListModelQtAbstractListModel の子クラスで、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つのパラメータを取ります:QtModelIndexInt は、それぞれデータ要素のインデックスとロールを表します。

"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.の 商標です。その他すべての商標は、それぞれの所有者に帰属します。