Modelle und Ansichten: Listenmodell mit einem Worker-Thread zum Abrufen von Daten

Zeigt, wie ein Listenmodell mit einer responsiven Benutzeroberfläche unter Verwendung eines Worker-Threads zum Abrufen von Daten implementiert werden kann.

Screenshot der Anwendung, in der eine Songliste mit Albumcovern, Songnamen, Interpretennamen und Albumnamen zu sehen ist

In diesem Beispiel wird ein benutzerdefiniertes Elementmodell eingeführt, das von QAbstractListModel erbt. Das Modell bezieht seine Daten von einem Worker-Objekt, das sich in einem separaten QThread befindet und Daten von einer langsamen Datenquelle abruft.

Überblick über das Beispiel der Threaded Song List

Die Datenquelle simuliert eine langsame Datenquelle, indem sie eine Verzögerung von 100 Millisekunden pro abgerufenem Lied hinzufügt. Dies bedeutet, dass das Laden der gesamten Liste von 3600 Liedern 6 Minuten dauern würde, was das Öffnen der Anwendung unpraktisch macht. Diese Verzögerung wird dadurch gemildert, dass die Daten nur für den sichtbaren Bereich der Ansicht abgerufen werden, indem ein QObject in einem Worker-Thread platziert wird.

Das Worker-Objekt hat eine Obergrenze für die Anzahl der Abrufanforderungen, die es in der Warteschlange hält. Dadurch wird sichergestellt, dass nur die Elemente des derzeit sichtbaren Teils der Titelliste abgerufen werden und nicht darauf gewartet werden muss, dass der nicht sichtbare Teil der Liste geladen wird, wenn der Benutzer bereits über einen Teil der Liste gescrollt hat.

Der Schwerpunkt dieses Beispiels liegt auf dem Quellmodell der Ansicht. Die Ansicht selbst ist ein unmodifiziertes QML ListView mit einem einfachen Delegaten. Die Verwendung von Threads ist hinter der Implementierung der Datenverarbeitung des Modells versteckt, und ListView muss nicht angepasst werden, um sich an das Thread-basierte Modell anpassen zu können.

Da der Schwerpunkt auf dem Modell liegt, ist Qt Quick Controls so eingestellt, dass der Universal Style auf allen Plattformen verwendet wird, um ein identisches UI-Verhalten zu gewährleisten.

import QtQuick
import QtQuick.Controls.Universal

So funktioniert es

Die Geschäftslogik zur Bereitstellung der Songlistendaten ist in der Klasse DataStorage untergebracht, die eine einfache ID-basierte Schnittstelle für das Modell bietet.

QList<int> idList();
MediaElement item(int id) const;
std::optional<int> currentlyFetchedId() const;

Wenn das Modell Daten vom DataStorage anfordert, prüft der Speicher zunächst, ob die Daten bereits verfügbar sind. Ist dies der Fall, werden die Daten sofort zurückgegeben, wie es auch bei einem nicht-gethreateten Modell der Fall wäre. Falls die Daten nicht gefunden werden, sendet DataStorage ein dataFetchNeeded() Signal an das Worker-Objekt und fügt der Liste der bereits vorhandenen Daten ein leeres Element hinzu. Durch das Hinzufügen des leeren Elements wird sichergestellt, dass keine weiteren Signale für dasselbe Listenelement an den Worker gesendet werden.

if (!m_items.contains(id)) {
    m_items.insert(id, MediaElement{});
    emit dataFetchNeeded(m_idList.indexOf(id));
}
return m_items.value(id);

QueueWorker - das Worker-Thread-Objekt - verarbeitet die empfangenen dataFetchNeeded()-Signale, indem es ein Signal an sich selbst sendet, wodurch es möglich wird, alle Signale zu empfangen, die sich bereits in der QEventQueue befinden, bevor der langsame Datenlesevorgang beginnt.

Anwendung des Ansatzes auf dynamische Modelle

Wenn man die Lösung auf einen Fall erweitern möchte, in dem Elemente hinzugefügt, verschoben oder aus der Datenquelle (in diesem Fall RemoteMedia) entfernt werden können, muss DataStorage mit Signalen aktualisiert werden, die mit QAbstractItemModel::rowsMoved(), QAbstractItemModel::rowsInserted() und zwei Signalen übereinstimmen, um QAbstractItemModel::beginRemoveRows() und QAbstractItemModel::endRemoveRows() im ThreadedListModel auszulösen.

Für das Einfügen und Verschieben kann das ThreadedListModel einfach QAbstractItemModel::beginInsertRows() aufrufen, dann neue IDs zu seiner ID-Liste hinzufügen und QAbstractItemModel::endInsertRows() aufrufen. Da ThreadedListModel eine Kopie der ID-Liste besitzt und auf den Speicher über die ID zugreift, ist es nicht erforderlich, den Beginn und das Ende vom Speicher zu signalisieren. Ebenso kann ThreadedListModel QAbstractItemModel::beginMoveRows() aufrufen, IDs in seiner ID-Liste verschieben und dann QAbstractItemModel::endMoveRows() aufrufen.

Das Entfernen ist ein etwas komplexerer Fall. Die Ansicht muss eine Möglichkeit haben, die zu entfernenden Daten anzufordern, bevor sie tatsächlich entfernt werden. DataStorage muss also eine Warnung vor dem Entfernen signalisieren, was das Model veranlasst, QAbstractItemModel::beginRemoveRows() aufzurufen. In dieser Phase kann ThreadedListModel einen oder mehrere data() -Aufrufe erhalten. Sobald der Aufruf des direkt angeschlossenen Signals bei DataStorage zurückkommt, ist es für DataStorage in Ordnung, das Element zu entfernen und dann dem Modell erneut ein Signal zu geben, das das Modell zum Aufruf von QAbstractItemModel::endRemoveRows() veranlasst.

Ausführen des Beispiels

Zum Ausführen des Beispiels von Qt Creatorauszuführen, öffnen Sie den Modus Welcome und wählen Sie das Beispiel aus Examples aus. Weitere Informationen finden Sie unter Qt Creator: Tutorial: Erstellen und Ausführen.

Beispielprojekt @ code.qt.io

© 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.