QtAbstractListModel für QML verfügbar machen

Überblick

Dieses Beispiel besteht aus zwei separaten Projekten: Einem QML-Projekt und einem Kotlin-basierten Android-Projekt, das den QML-Inhalt hosten und anzeigen wird. Es zeigt, wie QtAbstractListModel verwendet wird, um Daten von der Android-Seite an die QML-Ansicht weiterzugeben, die die Daten unter Verwendung einer ListView anzeigt.

Ausführen des Beispiels

Um dieses Beispiel auszuführen, benötigen Sie Android Studio und eine Qt für Android Installation.

Das Qt Gradle Plugin wird verwendet, um das QML-Projekt während des Build-Prozesses des Android-Projekts zu erstellen. Zu diesem Zweck enthält das Beispiel einige Plugin-Konfigurationen in der build.gradle.kts-Datei auf App-Ebene, die möglicherweise geändert werden müssen, wenn das Plugin z. B. das Qt-Kit-Verzeichnis nicht finden kann.

QtBuild {
    // Relative for Qt (Installer or MaintenanceTool) installations.
    qtPath = file("../../../../../../../6.8.2")
    projectPath = file("../../qtabstractlistmodel")
}

Weitere Informationen zur Konfiguration des Plugins finden Sie in der Dokumentation zum Qt Gradle Plugin.

QML-Projekt

Das QML-Projekt ist recht einfach, es definiert ein Datenmodell als Eigenschaft des Root-Objekts und einige UI-Elemente zur Anzeige der Daten aus diesem Modell.

Rectangle {
    id: mainRectangle

    property AbstractItemModel dataModel

Um die Daten aus dem Modell anzuzeigen, wird ein ListView erstellt. Die Eigenschaft model wird dann auf das zuvor deklarierte Datenmodell gesetzt.

    ListView {
        id: listView

        model: mainRectangle.dataModel

Um das Datenmodell anzuzeigen, benötigt ListView einen Delegaten, der für jedes Element des Datenmodells instanziiert wird. In diesem Fall wird der Delegat ein Rectangle sein, der zwei Text Elemente in einem Column enthält und die Daten von jedem Element im Datenmodell anzeigt.

        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-Projekt

Die Android-Seite besteht aus einer einzigen Activity und der Definition für das Datenmodell, das zuvor in der QML-Ansicht verwendet wurde.

Datenmodell

Das Datenmodell MyListModel ist eine Unterklasse von QtAbstractListModel, mit ArrayList<String> als internes Speichersystem für Daten. Im Initialisierungsblock von MyListModel werden einige zufällige Daten für die Liste erzeugt.

class MyListModel : QtAbstractListModel() {
    private val m_dataList = ArrayList<String>()

    init {
        synchronized(this) {
            for (row in 0..4) {
                m_dataList.add(UUID.randomUUID().toString())
            }
        }
    }

Jedem Element im Modell ist ein Satz von Datenelementen zugeordnet, jedes mit seiner eigenen Rolle. Benutzerdefinierte Implementierungen von QtAbstractItemModel müssen eine eigene Rolle für jedes Datenelement definieren. Jede Rolle hat einen zugehörigen Int-Wert, der beim Abrufen der Daten verwendet wird, und einen String-Wert, der den Namen des Datenelements angibt, wenn es von QML aus verwendet wird.

    @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
    }

Während die Int -Werte in der "roleNames()" -Methode fest kodiert sein können, wird in diesem Beispiel eine benutzerdefinierte Enum-Klasse DataRole innerhalb von MyListModel angegeben, die beim Verweis auf diese Werte verwendet wird. In diesem Beispiel definieren wir zwei Rollen: UUID und 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
            }
        }
    }

Wenn es um die Rückgabe von Daten aus dem Datenmodell geht, muss die Klasse die Methode "QtAbstractListModel::data()" außer Kraft setzen. Diese Methode benötigt zwei Parameter: QtModelIndex und Int, die sich jeweils auf den Index und die Rolle des Datenelements beziehen.

In "MyDataModel::data()" gibt die Rolle UUID die Daten ab dem angegebenen Index in den internen Daten zurück, während die Rolle Row die Zeile des angeforderten Elements zurückgibt.

Hinweis: Diese Methode und einige andere sind mit einem @Synchronized-Tag versehen. Dies ist darauf zurückzuführen, dass die Aufrufe dieser Methoden aus dem Qt-Thread stammen und auf die zugrunde liegenden Daten möglicherweise zur gleichen Zeit zugreifen wie die Anfragen aus dem Android-Thread über die Methoden "addRow()" und "removeRow()".

    @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 -> ""
        }
    }

Um externen Akteuren die Möglichkeit zu geben, das QtAbstractItemModel zu manipulieren, fügt das Beispiel zwei zusätzliche Methoden zu MyDataModel hinzu. Um der Zeile Daten hinzuzufügen, gibt es die Methode "addRow()"; um Daten zu entfernen, gibt es die Methode "removeRow()". Diese werden von der Hauptaktivität aus verwendet.

    @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()
        }
    }

Hauptaktivität

Die Klasse MainActivity ist eine einfache Kotlin-basierte Activity, implementiert aber auch die Schnittstelle QtQmlStatusChangeListener, um auf QML-Ladestatusereignisse zu hören. Sie speichert auch das QtQuickViewContent Objekt für die Hauptansicht der QML-Anwendung und eine Instanz des oben beschriebenen Datenmodells.

class MainActivity : AppCompatActivity(), QtQmlStatusChangeListener {
    private val m_mainQmlContent: Main = Main()
    private val m_listModel = MyListModel()

Beim Erstellen der Hauptaktivität der Anwendung wird im Beispiel zunächst eine QtQuickView erstellt und in die View-Hierarchie eingefügt.

        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)

Nach dem Hinzufügen der QtQuickView in die Benutzeroberfläche findet das Beispiel die Schaltflächen, die zur Manipulation des Datenmodells verwendet werden, und setzt einige Klick-Listener, um addRow() und removeRow() auf dem Mitgliedsdatenmodell aufzurufen.

        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()
        }

Sobald das UI-Setup und die Listener fertig sind, kann die QML-Komponente vorbereitet und geladen werden. Im Beispiel wird MainActivity als Listener für das Statusänderungssignal der QML-Komponente festgelegt und QtQuickView angewiesen, die QML-Komponente zu laden.

        m_mainQmlContent.setStatusChangeListener(this)
        qtQuickView.loadContent(m_mainQmlContent)

Sobald die QML-Komponente erfolgreich geladen wurde, weist das Beispiel den Wert der MyDataModel-Instanz der Eigenschaft dataModel in der QML-Komponente zu.

    override fun onStatusChanged(qtQmlStatus: QtQmlStatus) {
        if (qtQmlStatus === QtQmlStatus.READY) {
            m_mainQmlContent.setDataModel(m_listModel)
        }
    }

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.