Modell-View-Programmierung
Einführung in die Modell-Ansicht-Programmierung
Qt enthält eine Reihe von Item-View-Klassen, die eine Model-View-Architektur verwenden, um die Beziehung zwischen Daten und deren Darstellung für den Benutzer zu verwalten. Die durch diese Architektur eingeführte Trennung der Funktionen bietet Entwicklern größere Flexibilität bei der Anpassung der Darstellung von Elementen und stellt eine Standard-Modellschnittstelle bereit, die die Verwendung einer Vielzahl von Datenquellen mit bestehenden Item-Views ermöglicht. In diesem Dokument geben wir eine kurze Einführung in das Modell-Ansicht-Paradigma, skizzieren die damit verbundenen Konzepte und beschreiben die Architektur des Elementansichtssystems. Jede der Komponenten der Architektur wird erläutert, und es werden Beispiele gegeben, die zeigen, wie die bereitgestellten Klassen verwendet werden.
Die Modell-Ansicht-Architektur
Model-View-Controller (MVC) ist ein aus Smalltalk stammendes Entwurfsmuster, das häufig bei der Erstellung von Benutzeroberflächen zum Einsatz kommt. In „Design Patterns“ schreiben Gamma et al.:
MVC besteht aus drei Arten von Objekten. Das Modell ist das Anwendungsobjekt, die Ansicht ist dessen Bildschirmdarstellung, und der Controller definiert, wie die Benutzeroberfläche auf Benutzereingaben reagiert. Vor MVC wurden diese Objekte bei der Gestaltung von Benutzeroberflächen häufig in einem Block zusammengefasst. MVC entkoppelt sie, um Flexibilität und Wiederverwendbarkeit zu erhöhen.
Werden die View- und Controller-Objekte kombiniert, ergibt sich die Model-View-Architektur. Diese trennt zwar weiterhin die Art und Weise, wie Daten gespeichert werden, von der Art und Weise, wie sie dem Benutzer präsentiert werden, bietet jedoch ein einfacheres Framework, das auf denselben Prinzipien basiert. Diese Trennung ermöglicht es, dieselben Daten in mehreren verschiedenen Ansichten darzustellen und neue Arten von Ansichten zu implementieren, ohne die zugrunde liegenden Datenstrukturen zu ändern. Um eine flexible Verarbeitung von Benutzereingaben zu ermöglichen, führen wir das Konzept des Delegaten ein. Der Vorteil eines Delegaten in diesem Framework besteht darin, dass sich die Art und Weise, wie Datenelemente dargestellt und bearbeitet werden, individuell anpassen lässt.
| Die Model-View-Architektur Das Modell kommuniziert mit einer Datenquelle und stellt eine Schnittstelle für die anderen Komponenten der Architektur bereit. Die Art der Kommunikation hängt von der Art der Datenquelle und der Art und Weise ab, wie das Modell implementiert ist. Die Ansicht erhält vom Modell Modellindizes; dabei handelt es sich um Verweise auf Datenelemente. Durch die Übergabe von Modellindizes an das Modell kann die Ansicht Datenelemente aus der Datenquelle abrufen. In Standard-Views übernimmt ein Delegat die Darstellung der Datenelemente. Wenn ein Element bearbeitet wird, kommuniziert der Delegat mithilfe der Modellindizes direkt mit dem Modell. |
Im Allgemeinen lassen sich die Modell-/Ansichtsklassen in die drei oben beschriebenen Gruppen unterteilen: Modelle, Ansichten und Delegaten. Jede dieser Komponenten wird durch abstrakte Klassen definiert, die gemeinsame Schnittstellen und in einigen Fällen Standardimplementierungen von Funktionen bereitstellen. Abstrakte Klassen sind dazu gedacht, unterklassifiziert zu werden, um den vollständigen Funktionsumfang bereitzustellen, den andere Komponenten erwarten; dies ermöglicht auch die Erstellung spezialisierter Komponenten.
Modelle, Ansichten und Delegaten kommunizieren untereinander mithilfe von Signalen und Slots:
- Signale vom Modell informieren die Ansicht über Änderungen an den von der Datenquelle verwalteten Daten.
- Signale aus der Ansicht liefern Informationen über die Interaktion des Benutzers mit den angezeigten Elementen.
- Signale vom Delegaten werden während der Bearbeitung verwendet, um dem Modell und der Ansicht den Status des Editors mitzuteilen.
Modelle
Alle Elementmodelle basieren auf der Klasse „ QAbstractItemModel “. Diese Klasse definiert eine Schnittstelle, über die Ansichten und Delegaten auf Daten zugreifen. Die Daten selbst müssen nicht im Modell gespeichert sein; sie können in einer Datenstruktur oder einem Repository abgelegt sein, die bzw. das von einer separaten Klasse, einer Datei, einer Datenbank oder einer anderen Anwendungskomponente bereitgestellt wird.
Die grundlegenden Konzepte rund um Modelle werden im Abschnitt „Modellklassen“ vorgestellt.
QAbstractItemModel bietet eine Schnittstelle zu Daten, die flexibel genug ist, um Ansichten zu verarbeiten, die Daten in Form von Tabellen, Listen und Baumstrukturen darstellen. Bei der Implementierung neuer Modelle für listen- und tabellenähnliche Datenstrukturen sind jedoch die Klassen „ QAbstractListModel “ und „ QAbstractTableModel “ bessere Ausgangspunkte, da sie geeignete Standardimplementierungen gängiger Funktionen bereitstellen. Jede dieser Klassen kann unterklassifiziert werden, um Modelle bereitzustellen, die spezielle Arten von Listen und Tabellen unterstützen.
Der Prozess der Unterklassenbildung von Modellen wird im Abschnitt „Erstellen neuer Modelle“ behandelt.
Qt stellt einige vorgefertigte Modelle zur Verfügung, die zur Verarbeitung von Datenelementen verwendet werden können:
- QRangeModel passt einen bestehenden C++-Container oder einen beliebigen iterierbaren C++-Bereich ohne Unterklassenbildung an das Model-View-Framework an.
- QStringListModel wird verwendet, um eine einfache Liste von „ QString “-Elementen zu speichern.
- QStandardItemModel verwaltet komplexere Baumstrukturen von Elementen, von denen jedes beliebige Daten enthalten kann.
- QFileSystemModel liefert Informationen zu Dateien und Verzeichnissen im lokalen Dateisystem.
- QSqlQueryModel, ` QSqlTableModel` und ` QSqlRelationalTableModel ` dienen dem Zugriff auf Datenbanken unter Verwendung der Model-View-Konventionen.
Wenn diese Standardmodelle Ihren Anforderungen nicht entsprechen, können Sie Unterklassen von „ QAbstractItemModel “, „ QAbstractListModel “ oder „ QAbstractTableModel “ erstellen, um eigene benutzerdefinierte Modelle zu entwickeln. Alternativ kann „ QRangeModel “ Ihre Daten oft auch ohne Unterklassenbildung anpassen, sofern diese bereits in einem C++-Container oder -Bereich vorliegen.
Ansichten
Für verschiedene Arten von Ansichten stehen vollständige Implementierungen zur Verfügung: QListView zeigt eine Liste von Elementen an, QTableView stellt Daten aus einem Modell in einer Tabelle dar und QTreeView zeigt Modellelemente in einer hierarchischen Liste an. Jede dieser Klassen basiert auf der abstrakten Basisklasse QAbstractItemView. Obwohl es sich bei diesen Klassen um einsatzbereite Implementierungen handelt, können sie auch unterklassifiziert werden, um benutzerdefinierte Ansichten bereitzustellen.
Die verfügbaren Ansichten werden im Abschnitt über Ansichtsklassen näher erläutert.
Delegaten
QAbstractItemDelegate ist die abstrakte Basisklasse für Delegaten im Modell-Ansicht-Framework. Die Standardimplementierung für Delegaten wird von ` QStyledItemDelegate` bereitgestellt und von den Standardansichten von Qt als Standarddelegat verwendet. ` QStyledItemDelegate ` und ` QItemDelegate ` sind jedoch eigenständige Alternativen zum Zeichnen und Bereitstellen von Editoren für Elemente in Ansichten. Der Unterschied zwischen ihnen besteht darin, dass QStyledItemDelegate den aktuellen Stil zum Zeichnen seiner Elemente verwendet. Wir empfehlen daher, QStyledItemDelegate als Basisklasse zu verwenden, wenn benutzerdefinierte Delegaten implementiert werden oder mit Qt-Stylesheets gearbeitet wird.
Delegaten werden im Abschnitt über Delegatenklassen beschrieben.
Sortieren
Es gibt zwei Ansätze für die Sortierung in der Modell-Ansicht-Architektur; welcher Ansatz gewählt wird, hängt von Ihrem zugrunde liegenden Modell ab.
Wenn Ihr Modell sortierbar ist, d. h., wenn es die Funktion ` QAbstractItemModel::sort()` neu implementiert, bieten sowohl ` QTableView ` als auch ` QTreeView ` eine API, mit der Sie Ihre Modelldaten programmgesteuert sortieren können. Darüber hinaus können Sie die interaktive Sortierung aktivieren (d. h., den Benutzern ermöglichen, die Daten durch Klicken auf die Kopfzeilen der Ansicht zu sortieren), indem Sie das Signal „ QHeaderView::sortIndicatorChanged()“ jeweils mit dem Slot „ QTableView::sortByColumn()“ oder dem Slot „ QTreeView::sortByColumn()“ verbinden.
Der alternative Ansatz, falls Ihr Modell nicht über die erforderliche Schnittstelle verfügt oder Sie eine Listenansicht zur Darstellung Ihrer Daten verwenden möchten, besteht darin, ein Proxy-Modell zu nutzen, um die Struktur Ihres Modells umzuwandeln, bevor die Daten in der Ansicht dargestellt werden. Dies wird im Abschnitt über Proxy-Modelle ausführlich behandelt.
Hilfsklassen
Eine Reihe von Komfortklassen wurde von den Standard-Ansichtsklassen abgeleitet, um Anwendungen zu unterstützen, die auf den elementbasierten Elementansichts- und Tabellenklassen von Qt basieren. Sie sind nicht dazu gedacht, unterklassifiziert zu werden.
Beispiele für solche Klassen sind „ QListWidget “, „ QTreeWidget “ und „ QTableWidget “.
Diese Klassen sind weniger flexibel als die Ansichtsklassen und können nicht mit beliebigen Modellen verwendet werden. Wir empfehlen, für die Datenverarbeitung in Elementansichten einen Modell-Ansicht-Ansatz zu verwenden, es sei denn, Sie benötigen unbedingt einen elementbasierten Klassensatz.
Wenn Sie die Vorteile des Modell-Ansicht-Ansatzes nutzen und gleichzeitig eine elementbasierte Schnittstelle verwenden möchten, sollten Sie die Verwendung von Ansichtsklassen wie „ QListView “, „ QTableView “ und „ QTreeView “ in Verbindung mit „ QStandardItemModel “ in Betracht ziehen.
Verwendung von Modellen und Views
In den folgenden Abschnitten wird erläutert, wie das Model-View-Muster in Qt verwendet wird. Jeder Abschnitt enthält ein Beispiel und wird von einem Abschnitt gefolgt, in dem gezeigt wird, wie neue Komponenten erstellt werden.
Zwei in Qt enthaltene Modelle
Zwei der von Qt bereitgestellten Standardmodelle sind „ QStandardItemModel “ und „ QFileSystemModel “. „ QStandardItemModel “ ist ein vielseitiges Modell, das zur Darstellung verschiedener Datenstrukturen verwendet werden kann, die von Listen-, Tabellen- und Baumansichten benötigt werden. Dieses Modell enthält auch die einzelnen Datenelemente. „ QFileSystemModel “ ist ein Modell, das Informationen über den Inhalt eines Verzeichnisses verwaltet. Daher enthält es selbst keine Datenelemente, sondern stellt lediglich Dateien und Verzeichnisse im lokalen Dateisystem dar.
QFileSystemModel bietet ein einsatzbereites Modell zum Experimentieren und lässt sich leicht für die Verwendung mit vorhandenen Daten konfigurieren. Anhand dieses Modells können wir zeigen, wie ein Modell für die Verwendung mit vorgefertigten Ansichten eingerichtet wird, und untersuchen, wie Daten mithilfe von Modellindizes bearbeitet werden können.
Verwendung von Ansichten mit einem bestehenden Modell
Die Klassen „ QListView “ und „ QTreeView “ eignen sich am besten für die Verwendung mit „ QFileSystemModel “. Das unten gezeigte Beispiel stellt den Inhalt eines Verzeichnisses in einer Baumansicht neben denselben Informationen in einer Listenansicht dar. Die Ansichten teilen sich die Auswahl des Benutzers, sodass die ausgewählten Elemente in beiden Ansichten hervorgehoben werden.

Wir richten ein „ QFileSystemModel “ so ein, dass es einsatzbereit ist, und erstellen einige Ansichten zur Darstellung des Inhalts eines Verzeichnisses. Dies zeigt die einfachste Art, ein Modell zu verwenden. Die Erstellung und Nutzung des Modells erfolgt innerhalb einer einzigen „ main() “-Funktion:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QSplitter *splitter = new QSplitter;
QFileSystemModel *model = new QFileSystemModel;
model->setRootPath(QDir::currentPath());Das Modell ist so eingerichtet, dass es Daten aus einem bestimmten Dateisystem verwendet. Der Aufruf von ` setRootPath()` teilt dem Modell mit, welches Laufwerk des Dateisystems den Ansichten zur Verfügung gestellt werden soll.
Wir erstellen zwei Ansichten, damit wir die im Modell enthaltenen Elemente auf zwei verschiedene Arten untersuchen können:
QTreeView *tree = new QTreeView(splitter);
tree->setModel(model);
tree->setRootIndex(model->index(QDir::currentPath()));
QListView *list = new QListView(splitter);
list->setModel(model);
list->setRootIndex(model->index(QDir::currentPath()));Die Ansichten werden auf dieselbe Weise wie andere Widgets erstellt. Um eine Ansicht für die Anzeige der Elemente im Modell einzurichten, muss lediglich deren Funktion ` setModel()` mit dem Verzeichnismodell als Argument aufgerufen werden. Wir filtern die vom Modell bereitgestellten Daten, indem wir in jeder Ansicht die Funktion ` setRootIndex()` aufrufen und dabei einen geeigneten Modellindex aus dem Dateisystemmodell für das aktuelle Verzeichnis übergeben.
Die in diesem Fall verwendete Funktion ` index() ` ist spezifisch für ` QFileSystemModel`; wir übergeben ihr ein Verzeichnis und sie gibt einen Modellindex zurück. Modellindizes werden im Abschnitt „Modellklassen“ behandelt.
Der Rest der Funktion zeigt lediglich die Ansichten in einem Splitter-Widget an und führt die Ereignisschleife der Anwendung aus:
splitter->setWindowTitle("Two views onto the same file system model");
splitter->show();
return app.exec();
}Im obigen Beispiel haben wir nicht erwähnt, wie die Auswahl von Elementen behandelt wird. Dieses Thema wird im Abschnitt über die Behandlung von Auswahlen in Elementansichten ausführlicher behandelt.
Modellklassen
Bevor wir uns mit der Handhabung von Auswahlen befassen, ist es vielleicht hilfreich, die im Model-View-Framework verwendeten Konzepte zu betrachten.
Grundlegende Konzepte
In der Model-View-Architektur stellt das Modell eine Standardschnittstelle bereit, über die Views und Delegates auf Daten zugreifen. In Qt wird die Standardschnittstelle durch die Klasse „ QAbstractItemModel “ definiert. Unabhängig davon, wie die Datenelemente in einer zugrunde liegenden Datenstruktur gespeichert sind, stellen alle Unterklassen von ` QAbstractItemModel ` die Daten als hierarchische Struktur dar, die Tabellen mit Elementen enthält. Ansichten nutzen diese Konvention, um auf Datenelemente im Modell zuzugreifen, sind jedoch nicht darin eingeschränkt, wie sie diese Informationen dem Benutzer präsentieren.
Modelle benachrichtigen außerdem alle angehängten Ansichten über Änderungen an den Daten über den Signal- und-Slot-Mechanismus.
Dieser Abschnitt beschreibt einige grundlegende Konzepte, die für den Zugriff anderer Komponenten auf Datenelemente über eine Modellklasse von zentraler Bedeutung sind. Fortgeschrittenere Konzepte werden in späteren Abschnitten behandelt.
Modellindizes
Um sicherzustellen, dass die Darstellung der Daten von der Art und Weise ihres Zugriffs getrennt bleibt, wird das Konzept eines Modellindexes eingeführt. Jede Information, die über ein Modell abgerufen werden kann, wird durch einen Modellindex repräsentiert. Ansichten und Delegaten nutzen diese Indizes, um Datenelemente zur Anzeige anzufordern.
Dadurch muss nur das Modell wissen, wie Daten abgerufen werden, und der vom Modell verwaltete Datentyp kann relativ allgemein definiert werden. Modellindizes enthalten einen Zeiger auf das Modell, das sie erstellt hat, was Verwechslungen bei der Arbeit mit mehreren Modellen verhindert.
const QAbstractItemModel *model = index.model();Modellindizes stellen temporäre Verweise auf Informationen bereit und können verwendet werden, um Daten über das Modell abzurufen oder zu ändern. Da Modelle ihre internen Strukturen von Zeit zu Zeit neu organisieren können, können Modellindizes ungültig werden und sollten daher nicht gespeichert werden. Wenn ein langfristiger Verweis auf eine Information erforderlich ist, muss ein persistenter Modellindex erstellt werden. Dieser stellt einen Verweis auf die Information bereit, die vom Modell stets auf dem neuesten Stand gehalten wird. Temporäre Modellindizes werden von der Klasse „ QModelIndex “ bereitgestellt, persistente Modellindizes von der Klasse „ QPersistentModelIndex “.
Um einen Modellindex zu erhalten, der einem Datenelement entspricht, müssen dem Modell drei Eigenschaften angegeben werden: eine Zeilennummer, eine Spaltennummer und der Modellindex eines übergeordneten Elements. In den folgenden Abschnitten werden diese Eigenschaften ausführlich beschrieben und erläutert.
Zeilen und Spalten
In seiner grundlegendsten Form kann auf ein Modell als einfache Tabelle zugegriffen werden, in der Elemente anhand ihrer Zeilen- und Spaltennummern lokalisiert werden. Dies bedeutet nicht, dass die zugrunde liegenden Daten in einer Array-Struktur gespeichert sind; die Verwendung von Zeilen- und Spaltennummern ist lediglich eine Konvention, die es Komponenten ermöglicht, miteinander zu kommunizieren. Wir können Informationen zu einem beliebigen Element abrufen, indem wir dem Modell dessen Zeilen- und Spaltennummern angeben, und erhalten daraufhin einen Index, der das Element repräsentiert:
QModelIndex index = model->index(row, column /*...*/);Modelle, die Schnittstellen zu einfachen, einstufigen Datenstrukturen wie Listen und Tabellen bereitstellen, benötigen keine weiteren Informationen; wie der obige Code jedoch zeigt, müssen wir beim Abrufen eines Modellindexes zusätzliche Informationen bereitstellen.
| Zeilen und Spalten Das Diagramm zeigt eine Darstellung eines einfachen Tabellenmodells, in dem jedes Element durch ein Paar aus Zeilen- und Spaltennummern lokalisiert wird. Wir erhalten einen Modellindex, der auf ein Datenelement verweist, indem wir die entsprechenden Zeilen- und Spaltennummern an das Modell übergeben. Auf die obersten Elemente in einem Modell wird immer durch Angabe von „ |
Elternelemente
Die tabellenartige Schnittstelle zu Elementdaten, die von Modellen bereitgestellt wird, eignet sich ideal für die Verwendung von Daten in einer Tabellen- oder Listenansicht; das System aus Zeilen- und Spaltennummern entspricht genau der Art und Weise, wie die Ansichten Elemente anzeigen. Strukturen wie Baumansichten erfordern jedoch, dass das Modell eine flexiblere Schnittstelle zu den darin enthaltenen Elementen bereitstellt. Daher kann jedes Element auch das übergeordnete Element einer weiteren Tabelle mit Elementen sein, ähnlich wie ein Element der obersten Ebene in einer Baumansicht eine weitere Liste von Elementen enthalten kann.
Bei der Abfrage eines Index für ein Modellelement müssen wir einige Informationen über das übergeordnete Element des Elements angeben. Außerhalb des Modells ist die einzige Möglichkeit, auf ein Element zu verweisen, über einen Modellindex; daher muss auch ein übergeordneter Modellindex angegeben werden:
QModelIndex index = model->index(row, column, parent);| Elternelemente, Zeilen und Spalten Das Diagramm zeigt eine Darstellung eines Baummodells, in dem jedes Element durch ein übergeordnetes Element, eine Zeilennummer und eine Spaltennummer referenziert wird. Die Elemente „A“ und „C“ werden im Modell als gleichrangige Elemente der obersten Ebene dargestellt: Element „A“ hat mehrere untergeordnete Elemente. Ein Modellindex für Element „B“ wird mit dem folgenden Code abgerufen: |
Rollen von Elementen
Elemente in einem Modell können verschiedene Rollen für andere Komponenten übernehmen, wodurch je nach Situation unterschiedliche Arten von Daten bereitgestellt werden können. Beispielsweise wird die Rolle „ Qt::DisplayRole “ verwendet, um auf eine Zeichenkette zuzugreifen, die in einer Ansicht als Text angezeigt werden kann. In der Regel enthalten Elemente Daten für eine Reihe verschiedener Rollen, wobei die Standardrollen durch „ Qt::ItemDataRole “ definiert sind.
Wir können die Daten des Elements vom Modell abfragen, indem wir ihm den dem Element entsprechenden Modellindex übergeben und eine Rolle angeben, um den gewünschten Datentyp zu erhalten:
QVariant value = model->data(index, role);![]() | Elementrollen Die Rolle gibt dem Modell an, auf welche Art von Daten Bezug genommen wird. Ansichten können die Rollen auf unterschiedliche Weise darstellen, daher ist es wichtig, für jede Rolle geeignete Informationen bereitzustellen. Der Abschnitt „Erstellen neuer Modelle “ behandelt einige spezifische Anwendungsfälle von Rollen ausführlicher. |
Die gängigsten Verwendungszwecke für Elementdaten werden durch die in Qt::ItemDataRole definierten Standardrollen abgedeckt. Durch die Bereitstellung geeigneter Elementdaten für jede Rolle können Modelle Ansichten und Delegaten Hinweise darauf geben, wie Elemente dem Benutzer präsentiert werden sollen. Verschiedene Arten von Ansichten haben die Freiheit, diese Informationen je nach Bedarf zu interpretieren oder zu ignorieren. Es ist auch möglich, zusätzliche Rollen für anwendungsspezifische Zwecke zu definieren.
Zusammenfassung
- Modellindizes liefern Ansichten und Delegaten Informationen über die Position von Elementen, die von Modellen bereitgestellt werden, und zwar unabhängig von den zugrunde liegenden Datenstrukturen.
- Elemente werden anhand ihrer Zeilen- und Spaltennummern sowie anhand des Modellindexes ihrer übergeordneten Elemente referenziert.
- Modellindizes werden von Modellen auf Anfrage anderer Komponenten, wie z. B. Ansichten und Delegaten, erstellt.
- Wird bei der Anforderung eines Indexes mit ` index()` ein gültiger Modellindex für das übergeordnete Element angegeben, bezieht sich der zurückgegebene Index auf ein Element unterhalb dieses übergeordneten Elements im Modell. Der erhaltene Index bezieht sich auf ein untergeordnetes Element dieses Elements.
- Wird bei der Abfrage eines Indexes mit ` index()` ein ungültiger Modellindex für das übergeordnete Element angegeben, bezieht sich der zurückgegebene Index auf ein Element der obersten Ebene im Modell.
- Die Funktion ` role ` unterscheidet zwischen den verschiedenen Datentypen, die mit einem Element verknüpft sind.
Verwendung von Modellindizes
Um zu veranschaulichen, wie Daten mithilfe von Modellindizes aus einem Modell abgerufen werden können, richten wir eine ` QFileSystemModel ` ohne Ansicht ein und zeigen die Namen von Dateien und Verzeichnissen in einem Widget an. Dies entspricht zwar nicht der üblichen Verwendung eines Modells, veranschaulicht jedoch die Konventionen, die Modelle beim Umgang mit Modellindizes anwenden.
QFileSystemModel Das Laden erfolgt asynchron, um den Verbrauch von Systemressourcen zu minimieren. Dies müssen wir bei der Arbeit mit diesem Modell berücksichtigen.
Wir erstellen ein Dateisystemmodell auf folgende Weise:
auto *model = new QFileSystemModel;
auto onDirectoryLoaded = [model, layout, &window](const QString &directory) {
QModelIndex parentIndex = model->index(directory);
const int numRows = model->rowCount(parentIndex);
for (int row = 0; row < numRows; ++row) {
QModelIndex index = model->index(row, 0, parentIndex);
QString text = model->data(index, Qt::DisplayRole).toString();
// Display the text in a widget.
auto *label = new QLabel(text, &window);
layout->addWidget(label);
}
};
QObject::connect(model, &QFileSystemModel::directoryLoaded, onDirectoryLoaded);
model->setRootPath(QDir::currentPath());In diesem Fall richten wir zunächst ein Standard- QFileSystemModel ein. Wir verbinden dessen Signal „ directoryLoaded(QString) “ mit einem Lambda-Ausdruck, in dem wir mithilfe einer spezifischen Implementierung der von diesem Modell bereitgestellten Funktion „ index()“ einen übergeordneten Index für das Verzeichnis ermitteln.
In der Lambda-Funktion ermitteln wir die Anzahl der Zeilen im Modell mithilfe der Funktion ` rowCount()`.
Der Einfachheit halber interessieren wir uns nur für die Elemente in der ersten Spalte des Modells. Wir untersuchen nacheinander jede Zeile, ermitteln einen Modellindex für das erste Element in jeder Zeile und lesen die für dieses Element im Modell gespeicherten Daten aus.
for (int row = 0; row < numRows; ++row) {
QModelIndex index = model->index(row, 0, parentIndex);Um einen Modellindex zu erhalten, geben wir die Zeilennummer, die Spaltennummer (Null für die erste Spalte) und den entsprechenden Modellindex für das übergeordnete Element aller gewünschten Elemente an. Der in jedem Element gespeicherte Text wird mithilfe der Funktion „ data()“ des Modells abgerufen. Wir geben den Modellindex und die „ DisplayRole “ an, um die Daten für das Element in Form einer Zeichenkette zu erhalten.
Schließlich legen wir den Stammpfad von ` QFileSystemModel ` fest, damit das Laden der Daten beginnt und das Lambda-Funktionsskript ausgelöst wird.
Das obige Beispiel veranschaulicht die Grundprinzipien zum Abrufen von Daten aus einem Modell:
- Die Dimensionen eines Modells lassen sich mit den Funktionen „ rowCount()“ und „ columnCount()“ ermitteln. Für diese Funktionen muss in der Regel ein übergeordneter Modellindex angegeben werden.
- Modellindizes dienen dem Zugriff auf Elemente im Modell. Zur Angabe des Elements sind der Zeilen-, Spalten- und übergeordneter Modellindex erforderlich.
- Um auf Elemente der obersten Ebene in einem Modell zuzugreifen, geben Sie mit `
QModelIndex()` einen Null-Modellindex als übergeordneten Index an. - Elemente enthalten Daten für verschiedene Rollen. Um die Daten für eine bestimmte Rolle abzurufen, müssen sowohl der Modellindex als auch die Rolle an das Modell übergeben werden.
Weiterführende Informationen
Neue Modelle können durch Implementierung der von „ QAbstractItemModel “ bereitgestellten Standardschnittstelle erstellt werden. Im Abschnitt „Neue Modelle erstellen“ veranschaulichen wir dies anhand der Erstellung eines praktischen, einsatzbereiten Modells zur Speicherung von Zeichenfolgenlisten.
Klassen anzeigen
Konzepte
In der Modell-Darstellungs-Architektur bezieht die Darstellung Datenelemente aus dem Modell und präsentiert sie dem Benutzer. Die Art und Weise, wie die Daten dargestellt werden, muss nicht der Darstellung der vom Modell bereitgestellten Daten ähneln und kann sich vollständig von der zugrunde liegenden Datenstruktur unterscheiden, die zur Speicherung der Datenelemente verwendet wird.
Die Trennung von Inhalt und Darstellung wird durch die Verwendung einer Standard-Modellschnittstelle ( QAbstractItemModel), einer Standard-View-Schnittstelle ( QAbstractItemView) sowie durch den Einsatz von Modellindizes erreicht, die Datenelemente auf allgemeine Weise repräsentieren. Ansichten verwalten in der Regel das Gesamtlayout der aus Modellen bezogenen Daten. Sie können einzelne Datenelemente selbst darstellen oder Delegaten einsetzen, um sowohl Darstellungs- als auch Bearbeitungsfunktionen zu übernehmen.
Neben der Darstellung von Daten übernehmen Ansichten die Navigation zwischen Elementen sowie bestimmte Aspekte der Elementauswahl. Die Ansichten implementieren zudem grundlegende Funktionen der Benutzeroberfläche, wie z. B. Kontextmenüs und Drag-and-Drop. Eine Ansicht kann Standardbearbeitungsfunktionen für Elemente bereitstellen oder mithilfe eines Delegaten einen benutzerdefinierten Editor bereitstellen.
Eine Ansicht kann ohne Modell erstellt werden, doch muss ein Modell bereitgestellt werden, bevor sie nützliche Informationen anzeigen kann. Ansichten behalten den Überblick über die Elemente, die der Benutzer ausgewählt hat, mithilfe von Auswahlen, die für jede Ansicht separat verwaltet oder von mehreren Ansichten gemeinsam genutzt werden können.
Einige Ansichten, wie beispielsweise „ QTableView “ und „ QTreeView “, zeigen neben Elementen auch Kopfzeilen an. Diese werden ebenfalls durch eine Ansichtsklasse implementiert: „ QHeaderView “. Kopfzeilen greifen in der Regel auf dasselbe Modell zu wie die Ansicht, in der sie enthalten sind. Sie rufen Daten aus dem Modell mithilfe der Funktion „ QAbstractItemModel::headerData()“ ab und zeigen die Kopfzeileninformationen üblicherweise in Form einer Beschriftung an. Neue Kopfzeilen können als Unterklassen der Klasse „ QHeaderView “ erstellt werden, um speziellere Beschriftungen für Ansichten bereitzustellen.
Verwendung einer vorhandenen Ansicht
Qt stellt drei gebrauchsfertige Ansichtsklassen bereit, die Daten aus Modellen auf eine Weise darstellen, die den meisten Benutzern vertraut ist. „ QListView “ kann Elemente aus einem Modell als einfache Liste oder in Form einer klassischen Symbolansicht anzeigen. „ QTreeView “ zeigt Elemente aus einem Modell als Hierarchie von Listen an, wodurch tief verschachtelte Strukturen kompakt dargestellt werden können. QTableView präsentiert Elemente aus einem Modell in Form einer Tabelle, ähnlich dem Layout einer Tabellenkalkulationsanwendung.

Das Standardverhalten der oben gezeigten Standardansichten sollte für die meisten Anwendungen ausreichen. Sie bieten grundlegende Bearbeitungsfunktionen und lassen sich an die Anforderungen spezialisierterer Benutzeroberflächen anpassen.
Verwendung eines Modells
Wir nehmen das von uns erstellte String-Listen-Modell als Beispielmodell, füllen es mit einigen Daten und erstellen eine Ansicht, um den Inhalt des Modells anzuzeigen. All dies lässt sich in einer einzigen Funktion umsetzen:
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// Unindented for quoting purposes:
QStringList numbers;
numbers << "One" << "Two" << "Three" << "Four" << "Five";
QAbstractItemModel *model = new StringListModel(numbers);Beachten Sie, dass das „ StringListModel “ als „ QAbstractItemModel “ deklariert ist. Dies ermöglicht es uns, die abstrakte Schnittstelle zum Modell zu verwenden, und stellt sicher, dass der Code auch dann noch funktioniert, wenn wir das String-List-Modell durch ein anderes Modell ersetzen.
Die von ` QListView ` bereitgestellte Listenansicht reicht aus, um die Elemente im String-List-Modell darzustellen. Wir erstellen die Ansicht und richten das Modell mit den folgenden Codezeilen ein:
Die Ansicht wird auf die übliche Weise angezeigt:
view->show();
return app.exec();
}Die Ansicht rendert den Inhalt eines Modells und greift dabei über die Schnittstelle des Modells auf die Daten zu. Wenn der Benutzer versucht, ein Element zu bearbeiten, verwendet die Ansicht einen Standard-Delegaten, um ein Editor-Widget bereitzustellen.

Die obige Abbildung zeigt, wie ein „ QListView “ die Daten im String-List-Modell darstellt. Da das Modell bearbeitbar ist, ermöglicht die Ansicht automatisch die Bearbeitung jedes Elements in der Liste mithilfe des Standarddelegaten.
Verwendung mehrerer Ansichten eines Modells
Um mehrere Ansichten für dasselbe Modell bereitzustellen, muss lediglich für jede Ansicht dasselbe Modell festgelegt werden. Im folgenden Code erstellen wir zwei Tabellenansichten, die jeweils dasselbe einfache Tabellenmodell verwenden, das wir für dieses Beispiel erstellt haben:
QTableView *firstTableView = new QTableView;
QTableView *secondTableView = new QTableView;
firstTableView->setModel(model);
secondTableView->setModel(model);Die Verwendung von Signalen und Slots in der Modell-Ansicht-Architektur bedeutet, dass Änderungen am Modell an alle verbundenen Ansichten weitergegeben werden können, wodurch sichergestellt wird, dass wir unabhängig von der verwendeten Ansicht immer auf dieselben Daten zugreifen können.

Die obige Abbildung zeigt zwei verschiedene Ansichten desselben Modells, die jeweils eine Reihe ausgewählter Elemente enthalten. Obwohl die Daten aus dem Modell in allen Ansichten einheitlich dargestellt werden, verwaltet jede Ansicht ihr eigenes internes Auswahlmodell. Dies kann in bestimmten Situationen nützlich sein, doch für viele Anwendungen ist ein gemeinsames Auswahlmodell wünschenswert.
Behandlung von Elementauswahlen
Der Mechanismus zur Handhabung von Elementauswahlen innerhalb von Ansichten wird von der Klasse „ QItemSelectionModel “ bereitgestellt. Alle Standardansichten erstellen standardmäßig ihre eigenen Auswahlmodelle und interagieren auf übliche Weise mit diesen. Das von einer Ansicht verwendete Auswahlmodell kann über die Funktion ` selectionModel()` abgerufen werden, und ein Ersatzauswahlmodell kann mit ` setSelectionModel()` festgelegt werden. Die Möglichkeit, das von einer Ansicht verwendete Auswahlmodell zu steuern, ist nützlich, wenn wir mehrere konsistente Ansichten auf dieselben Modelldaten bereitstellen möchten.
Im Allgemeinen müssen Sie den Inhalt von Auswahlen nicht direkt bearbeiten, es sei denn, Sie erstellen eine Unterklasse eines Modells oder einer Ansicht. Bei Bedarf kann jedoch auf die Schnittstelle zum Auswahlmodell zugegriffen werden; dies wird im Abschnitt „Umgang mit Auswahlen in Elementansichten“ näher erläutert.
Gemeinsame Nutzung von Auswahlen zwischen Ansichten
Obwohl es praktisch ist, dass die Ansichtsklassen standardmäßig ihre eigenen Auswahlmodelle bereitstellen, ist es bei der Verwendung mehrerer Ansichten auf dasselbe Modell oft wünschenswert, dass sowohl die Modelldaten als auch die Auswahl des Benutzers in allen Ansichten konsistent dargestellt werden. Da die Ansichtsklassen das Ersetzen ihrer internen Auswahlmodelle zulassen, können wir mit der folgenden Zeile eine einheitliche Auswahl zwischen den Ansichten erreichen:
secondTableView->setSelectionModel(firstTableView->selectionModel());Der zweiten Ansicht wird das Auswahlmodell der ersten Ansicht zugewiesen. Beide Ansichten arbeiten nun mit demselben Auswahlmodell, wodurch sowohl die Daten als auch die ausgewählten Elemente synchronisiert bleiben.

Im oben gezeigten Beispiel wurden zwei Ansichten desselben Typs verwendet, um die Daten desselben Modells anzuzeigen. Würden jedoch zwei verschiedene Ansichtstypen verwendet, könnten die ausgewählten Elemente in jeder Ansicht sehr unterschiedlich dargestellt werden; so kann beispielsweise eine zusammenhängende Auswahl in einer Tabellenansicht in einer Baumansicht als fragmentierte Menge hervorgehobener Elemente dargestellt werden.
Delegatklassen
Konzepte
Im Gegensatz zum Model-View-Controller-Muster enthält das Model-View-Design keine vollständig separate Komponente zur Verwaltung der Interaktion mit dem Benutzer. Im Allgemeinen ist die Ansicht für die Darstellung der Modelldaten gegenüber dem Benutzer sowie für die Verarbeitung von Benutzereingaben zuständig. Um eine gewisse Flexibilität bei der Erfassung dieser Eingaben zu ermöglichen, wird die Interaktion durch Delegaten durchgeführt. Diese Komponenten bieten Eingabefunktionen und sind in einigen Ansichten auch für die Darstellung einzelner Elemente verantwortlich. Die Standardschnittstelle zur Steuerung von Delegaten ist in der Klasse „ QAbstractItemDelegate “ definiert.
Von Delegaten wird erwartet, dass sie ihre Inhalte selbst darstellen können, indem sie die Funktionen ` paint()` und ` sizeHint()` implementieren. Einfache, auf Widgets basierende Delegaten können jedoch anstelle von ` QAbstractItemDelegate` eine Unterklasse von ` QStyledItemDelegate ` bilden und die Standardimplementierungen dieser Funktionen nutzen.
Editoren für Delegaten können entweder mithilfe von Widgets zur Steuerung des Bearbeitungsprozesses oder durch direkte Ereignisbehandlung implementiert werden. Der erste Ansatz wird später in diesem Abschnitt behandelt.
Verwendung eines vorhandenen Delegaten
Die mit Qt bereitgestellten Standardansichten verwenden Instanzen von ` QStyledItemDelegate `, um Bearbeitungsfunktionen bereitzustellen. Diese Standardimplementierung der Delegaten-Schnittstelle rendert Elemente im üblichen Stil für jede der Standardansichten: ` QListView`, ` QTableView` und ` QTreeView`.
Alle Standardrollen werden von dem Standard-Delegaten verarbeitet, der von den Standardansichten verwendet wird. Wie diese interpretiert werden, ist in der Dokumentation zu „ QStyledItemDelegate “ beschrieben.
Der von einer Ansicht verwendete Delegat wird von der Funktion „ itemDelegate()“ zurückgegeben. Mit der Funktion „ setItemDelegate()“ können Sie einen benutzerdefinierten Delegaten für eine Standardansicht installieren; die Verwendung dieser Funktion ist erforderlich, wenn Sie den Delegaten für eine benutzerdefinierte Ansicht festlegen.
Ein einfacher Delegat
Der hier implementierte Delegat nutzt ein „ QSpinBox “, um Bearbeitungsfunktionen bereitzustellen, und ist hauptsächlich für die Verwendung mit Modellen vorgesehen, die Ganzzahlen anzeigen. Obwohl wir zu diesem Zweck ein benutzerdefiniertes, auf Ganzzahlen basierendes Tabellenmodell eingerichtet haben, hätten wir stattdessen problemlos „ QStandardItemModel “ verwenden können, da der benutzerdefinierte Delegat die Dateneingabe steuert. Wir erstellen eine Tabellenansicht zur Darstellung des Modellinhalts, die den benutzerdefinierten Delegaten für die Bearbeitung verwendet.

Wir erstellen eine Unterklasse des Delegaten von „ QStyledItemDelegate “, da wir keine benutzerdefinierten Anzeigefunktionen schreiben möchten. Wir müssen jedoch weiterhin Funktionen zur Verwaltung des Editor-Widgets bereitstellen:
class SpinBoxDelegate : public QStyledItemDelegate
{
Q_OBJECT
public:
SpinBoxDelegate(QObject *parent = nullptr);
QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
void setEditorData(QWidget *editor, const QModelIndex &index) const override;
void setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const override;
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option,
const QModelIndex &index) const override;
};
SpinBoxDelegate::SpinBoxDelegate(QObject *parent)
: QStyledItemDelegate(parent)
{
}Beachten Sie, dass beim Erstellen des Delegaten keine Editor-Widgets eingerichtet werden. Wir erstellen ein Editor-Widget erst dann, wenn es benötigt wird.
Bereitstellung eines Editor-Widgets
In diesem Beispiel fordert die Tabellenansicht, wenn sie einen Editor bereitstellen muss, den Delegaten auf, ein Editor-Widget bereitzustellen, das für das zu bearbeitende Element geeignet ist. Die Funktion ` createEditor()` erhält alle Informationen, die der Delegat benötigt, um ein passendes Widget einzurichten:
QWidget *SpinBoxDelegate::createEditor(QWidget *parent,
const QStyleOptionViewItem &/* option */,
const QModelIndex &/* index */) const
{
QSpinBox *editor = new QSpinBox(parent);
editor->setFrame(false);
editor->setMinimum(0);
editor->setMaximum(100);
return editor;
}Beachten Sie, dass wir keinen Zeiger auf das Editor-Widget speichern müssen, da die Ansicht dafür sorgt, dass es zerstört wird, sobald es nicht mehr benötigt wird.
Wir wenden den Standard-Ereignisfilter des Delegaten auf den Editor an, um sicherzustellen, dass er die Standard-Bearbeitungskurzbefehle bereitstellt, die Benutzer erwarten. Dem Editor können zusätzliche Kurzbefehle hinzugefügt werden, um ein ausgefeilteres Verhalten zu ermöglichen; diese werden im Abschnitt über Bearbeitungshinweise behandelt.
Die Ansicht stellt sicher, dass die Daten und die Geometrie des Editors korrekt gesetzt sind, indem sie Funktionen aufruft, die wir später für diese Zwecke definieren. Wir können je nach dem von der Ansicht bereitgestellten Modellindex unterschiedliche Editoren erstellen. Wenn wir beispielsweise eine Spalte mit Ganzzahlen und eine Spalte mit Zeichenfolgen haben, könnten wir je nachdem, welche Spalte gerade bearbeitet wird, entweder einen ` QSpinBox ` oder einen ` QLineEdit` zurückgeben.
Der Delegat muss eine Funktion bereitstellen, um Modelldaten in den Editor zu kopieren. In diesem Beispiel lesen wir die im ` display role` gespeicherten Daten aus und setzen den Wert im Spin-Feld entsprechend.
void SpinBoxDelegate::setEditorData(QWidget *editor,
const QModelIndex &index) const
{
int value = index.data(Qt::EditRole).toInt();
QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
spinBox->setValue(value);
}In diesem Beispiel wissen wir, dass es sich bei dem Editor-Widget um ein Drehfeld handelt, aber wir hätten für verschiedene Datentypen im Modell auch unterschiedliche Editoren bereitstellen können; in diesem Fall müssten wir das Widget vor dem Zugriff auf seine Mitgliedsfunktionen in den entsprechenden Typ umwandeln.
Daten an das Modell übermitteln
Wenn der Benutzer die Bearbeitung des Werts im Drehfeld abgeschlossen hat, fordert die Ansicht den Delegaten auf, den bearbeiteten Wert im Modell zu speichern, indem sie die Funktion ` setModelData()` aufruft.
void SpinBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model,
const QModelIndex &index) const
{
QSpinBox *spinBox = static_cast<QSpinBox*>(editor);
spinBox->interpretText();
int value = spinBox->value();
model->setData(index, value, Qt::EditRole);
}Da die Ansicht die Editor-Widgets für den Delegaten verwaltet, müssen wir lediglich das Modell mit den vom Editor bereitgestellten Inhalten aktualisieren. In diesem Fall stellen wir sicher, dass das Drehfeld auf dem neuesten Stand ist, und aktualisieren das Modell mit dem darin enthaltenen Wert unter Verwendung des angegebenen Index.
Die Standardklasse „ QStyledItemDelegate “ informiert die Ansicht durch das Aussenden des Signals „ closeEditor()“, sobald die Bearbeitung abgeschlossen ist. Die Ansicht sorgt dafür, dass das Editor-Widget geschlossen und zerstört wird. In diesem Beispiel stellen wir nur einfache Bearbeitungsfunktionen bereit, sodass wir dieses Signal nie aussenden müssen.
Alle Operationen an den Daten werden über die von ` QAbstractItemModel` bereitgestellte Schnittstelle ausgeführt. Dadurch ist der Delegat weitgehend unabhängig vom Typ der Daten, die er bearbeitet; allerdings müssen einige Annahmen getroffen werden, um bestimmte Arten von Editor-Widgets verwenden zu können. In diesem Beispiel sind wir davon ausgegangen, dass das Modell stets ganzzahlige Werte enthält; dennoch können wir diesen Delegaten mit verschiedenen Modelltypen verwenden, da ` QVariant ` sinnvolle Standardwerte für unerwartete Daten bereitstellt.
Aktualisieren der Geometrie des Editors
Es liegt in der Verantwortung des Delegaten, die Geometrie des Editors zu verwalten. Die Geometrie muss beim Erstellen des Editors sowie bei Änderungen der Größe oder Position des Elements in der Ansicht festgelegt werden. Glücklicherweise stellt die Ansicht alle erforderlichen Geometrieinformationen innerhalb eines „ view option “-Objekts bereit.
void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
const QStyleOptionViewItem &option,
const QModelIndex &/* index */) const
{
editor->setGeometry(option.rect);
}In diesem Fall verwenden wir einfach die Geometrieinformationen, die von der Ansicht über die Option „Elementrechteck“ bereitgestellt werden. Ein Delegat, der Elemente mit mehreren Bestandteilen rendert, würde das Elementrechteck nicht direkt verwenden. Er würde den Editor relativ zu den anderen Elementen im Element positionieren.
Bearbeitungstipps
Nach der Bearbeitung sollten Delegaten den anderen Komponenten Hinweise zum Ergebnis des Bearbeitungsvorgangs geben und Hinweise bereitstellen, die bei nachfolgenden Bearbeitungsvorgängen helfen. Dies wird durch das Auslösen des Signals ` closeEditor()` mit einem geeigneten Hinweis erreicht. Dies wird durch den Standard-Ereignisfilter ` QStyledItemDelegate ` übernommen, den wir beim Erstellen des Spin-Feldes installiert haben.
Das Verhalten des Drehfelds lässt sich anpassen, um es benutzerfreundlicher zu gestalten. Im von „ QStyledItemDelegate “ bereitgestellten Standard-Ereignisfilter übergibt der Delegat den Wert an das Modell und schließt das Drehfeld, sobald der Benutzer auf „ Return “ klickt, um seine Auswahl im Drehfeld zu bestätigen. Wir können dieses Verhalten ändern, indem wir unseren eigenen Ereignisfilter für das Drehfeld installieren und Bearbeitungshinweise bereitstellen, die unseren Anforderungen entsprechen; beispielsweise könnten wir ` closeEditor()` mit dem Hinweis „ EditNextItem “ auslösen, um automatisch mit der Bearbeitung des nächsten Elements in der Ansicht zu beginnen.
Ein weiterer Ansatz, der keinen Einsatz eines Ereignisfilters erfordert, besteht darin, ein eigenes Editor-Widget bereitzustellen – vielleicht als Unterklasse von ` QSpinBox `, um die Arbeit zu vereinfachen. Dieser alternative Ansatz würde uns mehr Kontrolle über das Verhalten des Editor-Widgets geben, allerdings auf Kosten von zusätzlichem Programmieraufwand. In der Regel ist es einfacher, einen Ereignisfilter im Delegaten zu implementieren, wenn Sie das Verhalten eines Standard-Qt-Editor-Widgets anpassen müssen.
Delegaten müssen diese Hinweise nicht unbedingt ausgeben, doch diejenigen, die dies nicht tun, lassen sich weniger gut in Anwendungen integrieren und sind weniger benutzerfreundlich als solche, die Hinweise zur Unterstützung gängiger Bearbeitungsaktionen ausgeben.
Umgang mit Auswahlen in Elementansichten
Konzepte
Das in den Elementansichtsklassen verwendete Auswahlmodell bietet eine allgemeine Beschreibung von Auswahlen auf der Grundlage der Möglichkeiten der Modell-View-Architektur. Obwohl die Standardklassen zur Bearbeitung von Auswahlen für die bereitgestellten Elementansichten ausreichend sind, ermöglicht Ihnen das Auswahlmodell die Erstellung spezieller Auswahlmodelle, die den Anforderungen Ihrer eigenen Elementmodelle und -ansichten entsprechen.
Informationen zu den in einer Ansicht ausgewählten Elementen werden in einer Instanz der Klasse „ QItemSelectionModel “ gespeichert. Diese verwaltet Modellindizes für Elemente in einem einzelnen Modell und ist unabhängig von jeglichen Ansichten. Da es viele Ansichten auf ein Modell geben kann, ist es möglich, Auswahlen zwischen Ansichten gemeinsam zu nutzen, wodurch Anwendungen mehrere Ansichten auf konsistente Weise anzeigen können.
Auswahlen bestehen aus Auswahlbereichen. Diese verwalten Informationen über große Auswahlen von Elementen effizient, indem sie für jeden Bereich ausgewählter Elemente nur die Start- und End-Modellindizes aufzeichnen. Nicht zusammenhängende Auswahlen von Elementen werden mithilfe mehrerer Auswahlbereiche gebildet, um die Auswahl zu beschreiben.
Auswahlen werden auf eine Sammlung von Modellindizes angewendet, die von einem Auswahlmodell verwaltet werden. Die zuletzt angewendete Auswahl von Elementen wird als aktuelle Auswahl bezeichnet. Die Auswirkungen dieser Auswahl können auch nach ihrer Anwendung durch die Verwendung bestimmter Arten von Auswahlbefehlen geändert werden. Diese werden später in diesem Abschnitt behandelt.
Aktuelles Element und ausgewählte Elemente
In einer Ansicht gibt es immer ein aktuelles Element und ein ausgewähltes Element – zwei voneinander unabhängige Zustände. Ein Element kann gleichzeitig das aktuelle Element und ein ausgewähltes Element sein. Die Ansicht muss sicherstellen, dass immer ein aktuelles Element vorhanden ist, da beispielsweise die Tastaturnavigation ein aktuelles Element erfordert.
Die folgende Tabelle verdeutlicht die Unterschiede zwischen dem aktuellen Element und ausgewählten Elementen.
| Aktuelles Element | Ausgewählte Elemente |
|---|---|
| Es kann nur ein aktuelles Element geben. | Es können mehrere ausgewählte Elemente vorhanden sein. |
| Das aktuell ausgewählte Element wird durch Tastenbedienung oder Mausklicks geändert. | Der Auswahlstatus der Elemente wird je nach vordefiniertem Modus – z. B. Einzelauswahl, Mehrfachauswahl usw. – gesetzt oder aufgehoben, wenn der Benutzer mit den Elementen interagiert. |
| Das aktuelle Element wird bearbeitet, wenn die Bearbeitungstaste „ F2 “ gedrückt oder ein Doppelklick auf das Element ausgeführt wird (vorausgesetzt, die Bearbeitung ist aktiviert). | Das aktuelle Element kann zusammen mit einem Anker verwendet werden, um einen Bereich anzugeben, der ausgewählt oder abgewählt werden soll (oder eine Kombination aus beidem). |
| Das aktuelle Element wird durch das Fokusrechteck gekennzeichnet. | Die ausgewählten Elemente werden durch das Auswahlrechteck gekennzeichnet. |
Bei der Bearbeitung von Auswahlen ist es oft hilfreich, sich „ QItemSelectionModel “ als eine Aufzeichnung des Auswahlzustands aller Elemente in einem Elementmodell vorzustellen. Sobald ein Auswahlmodell eingerichtet ist, können Sammlungen von Elementen ausgewählt, abgewählt oder ihr Auswahlzustand umgeschaltet werden, ohne dass man wissen muss, welche Elemente bereits ausgewählt sind. Die Indizes aller ausgewählten Elemente können jederzeit abgerufen werden, und andere Komponenten können über den Signal- und-Slot-Mechanismus über Änderungen am Auswahlmodell informiert werden.
Verwendung eines Auswahlmodells
Die Standard-Ansichtsklassen stellen Standard-Auswahlmodelle bereit, die in den meisten Anwendungen verwendet werden können. Ein zu einer Ansicht gehörendes Auswahlmodell kann über die Funktion ` selectionModel()` der Ansicht abgerufen und mit ` setSelectionModel()` zwischen mehreren Ansichten gemeinsam genutzt werden, sodass die Erstellung neuer Auswahlmodelle in der Regel nicht erforderlich ist.
Eine Auswahl wird erstellt, indem ein Modell sowie ein Paar von Modellindizes an ` QItemSelection` übergeben werden. Dabei werden die Indizes verwendet, um auf Elemente im angegebenen Modell zu verweisen, und sie werden als das Element oben links und das Element unten rechts in einem Block ausgewählter Elemente interpretiert. Um die Auswahl auf Elemente in einem Modell anzuwenden, muss die Auswahl an ein Auswahlmodell übergeben werden; dies kann auf verschiedene Arten erfolgen, die jeweils unterschiedliche Auswirkungen auf die bereits im Auswahlmodell vorhandenen Auswahlen haben.
Elemente auswählen
Um einige der wichtigsten Funktionen von Auswahlen zu veranschaulichen, erstellen wir eine Instanz eines benutzerdefinierten Tabellenmodells mit insgesamt 32 Elementen und öffnen eine Tabellenansicht für dessen Daten:
TableModel *model = new TableModel(8, 4, &app);
QTableView *table = new QTableView(0);
table->setModel(model);
QItemSelectionModel *selectionModel = table->selectionModel();Das Standardauswahlmodell der Tabellenansicht wird zur späteren Verwendung abgerufen. Wir nehmen keine Änderungen an den Elementen im Modell vor, sondern wählen stattdessen einige Elemente aus, die die Ansicht oben links in der Tabelle anzeigen soll. Dazu müssen wir die Modellindizes abrufen, die den Elementen oben links und unten rechts in dem auszuwählenden Bereich entsprechen:
QModelIndex topLeft;
QModelIndex bottomRight;
topLeft = model->index(0, 0, QModelIndex());
bottomRight = model->index(5, 2, QModelIndex());Um diese Elemente im Modell auszuwählen und die entsprechende Änderung in der Tabellenansicht anzuzeigen, müssen wir ein Auswahlobjekt erstellen und es anschließend auf das Auswahlmodell anwenden:
QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Select);Die Auswahl wird mithilfe eines Befehls auf das Auswahlmodell angewendet, der durch eine Kombination aus „ selection flags “ definiert ist. In diesem Fall bewirken die verwendeten Flags, dass die im Auswahlobjekt erfassten Elemente unabhängig von ihrem vorherigen Status in das Auswahlmodell aufgenommen werden. Die resultierende Auswahl wird in der Ansicht angezeigt.

Die Auswahl von Elementen kann mithilfe verschiedener Operationen geändert werden, die durch die Auswahlflags definiert sind. Die aus diesen Operationen resultierende Auswahl kann eine komplexe Struktur aufweisen, wird jedoch durch das Auswahlmodell effizient dargestellt. Die Verwendung verschiedener Auswahlflags zur Bearbeitung der ausgewählten Elemente wird beschrieben, wenn wir uns mit der Aktualisierung einer Auswahl befassen.
Auslesen des Auswahlzustands
Die im Auswahlmodell gespeicherten Modellindizes können mit der Funktion ` selectedIndexes()` ausgelesen werden. Diese gibt eine unsortierte Liste von Modellindizes zurück, die wir durchlaufen können, sofern wir wissen, zu welchem Modell sie gehören:
const QModelIndexList indexes = selectionModel->selectedIndexes();
for (const QModelIndex &index : indexes) {
QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
model->setData(index, text);
}Der obige Code verwendet eine bereichsbasierte for-Schleife, um die Elemente, die den vom Auswahlmodell zurückgegebenen Indizes entsprechen, zu durchlaufen und zu ändern.
Das Auswahlmodell sendet Signale aus, um Änderungen in der Auswahl anzuzeigen. Diese benachrichtigen andere Komponenten über Änderungen sowohl an der Auswahl als Ganzes als auch am aktuell fokussierten Element im Elementmodell. Wir können das Signal „ selectionChanged()“ mit einem Slot verbinden und so die Elemente im Modell untersuchen, die bei einer Änderung der Auswahl ausgewählt oder abgewählt werden. Der Slot wird mit zwei „ QItemSelection “-Objekten aufgerufen: Das eine enthält eine Liste von Indizes, die den neu ausgewählten Elementen entsprechen; das andere enthält Indizes, die den neu abgewählten Elementen entsprechen.
Im folgenden Code definieren wir einen Slot, der das Signal „ selectionChanged()“ empfängt, die ausgewählten Elemente mit einer Zeichenkette füllt und den Inhalt der abgewählten Elemente löscht.
void MainWindow::updateSelection(const QItemSelection &selected,
const QItemSelection &deselected)
{
QModelIndexList items = selected.indexes();
for (const QModelIndex &index : std::as_const(items)) {
QString text = QString("(%1,%2)").arg(index.row()).arg(index.column());
model->setData(index, text);
}
items = deselected.indexes();
for (const QModelIndex &index : std::as_const(items)) {
model->setData(index, QString());
}
}Wir können das aktuell fokussierte Element verfolgen, indem wir das Signal „ currentChanged()“ mit einem Slot verbinden, der mit zwei Modellindizes aufgerufen wird. Diese entsprechen dem zuvor fokussierten Element und dem aktuell fokussierten Element.
Im folgenden Code definieren wir einen Slot, der das Signal „ currentChanged()“ empfängt und die bereitgestellten Informationen nutzt, um die Statusleiste eines „ QMainWindow “ zu aktualisieren:
void MainWindow::changeCurrent(const QModelIndex ¤t,
const QModelIndex &previous)
{
statusBar()->showMessage(
tr("Moved from (%1,%2) to (%3,%4)")
.arg(previous.row()).arg(previous.column())
.arg(current.row()).arg(current.column()));
}Die Überwachung der vom Benutzer getroffenen Auswahlen ist mit diesen Signalen unkompliziert, wir können das Auswahlmodell jedoch auch direkt aktualisieren.
Aktualisierung einer Auswahl
Auswahlbefehle werden durch eine Kombination von Auswahlflags bereitgestellt, die in „ QItemSelectionModel::SelectionFlag “ definiert sind. Jedes Auswahlflag teilt dem Auswahlmodell mit, wie es seinen internen Datensatz der ausgewählten Elemente aktualisieren soll, wenn eine der Funktionen „ select()“ aufgerufen wird. Das am häufigsten verwendete Flag ist das Flag „ Select “, das das Auswahlmodell anweist, die angegebenen Elemente als ausgewählt zu vermerken. Das Flag „ Toggle “ bewirkt, dass das Auswahlmodell den Status der angegebenen Elemente umkehrt, d. h. alle nicht ausgewählten Elemente auswählt und alle derzeit ausgewählten Elemente abwählt. Das Flag „ Deselect “ hebt die Auswahl aller angegebenen Elemente auf.
Einzelne Elemente im Auswahlmodell werden aktualisiert, indem eine Auswahl von Elementen erstellt und auf das Auswahlmodell angewendet wird. Im folgenden Code wenden wir eine zweite Auswahl von Elementen auf das oben gezeigte Tabellenmodell an und verwenden dabei den Befehl „ Toggle “, um den Auswahlstatus der angegebenen Elemente umzukehren.
QItemSelection toggleSelection;
topLeft = model->index(2, 1, QModelIndex());
bottomRight = model->index(7, 3, QModelIndex());
toggleSelection.select(topLeft, bottomRight);
selectionModel->select(toggleSelection, QItemSelectionModel::Toggle);Die Ergebnisse dieser Operation werden in der Tabellenansicht angezeigt und bieten eine bequeme Möglichkeit, das Erreichte zu visualisieren:

Standardmäßig wirken sich die Auswahlbefehle nur auf die einzelnen Elemente aus, die durch die Modellindizes angegeben werden. Das zur Beschreibung des Auswahlbefehls verwendete Flag kann jedoch mit zusätzlichen Flags kombiniert werden, um ganze Zeilen und Spalten zu ändern. Wenn Sie beispielsweise ` select()` mit nur einem Index aufrufen, der Befehl jedoch eine Kombination aus ` Select ` und ` Rows` ist, wird die gesamte Zeile ausgewählt, die das betreffende Element enthält. Der folgende Code veranschaulicht die Verwendung der Flags ` Rows ` und ` Columns `:
QItemSelection columnSelection;
topLeft = model->index(0, 1, QModelIndex());
bottomRight = model->index(0, 2, QModelIndex());
columnSelection.select(topLeft, bottomRight);
selectionModel->select(columnSelection,
QItemSelectionModel::Select | QItemSelectionModel::Columns);
QItemSelection rowSelection;
topLeft = model->index(0, 0, QModelIndex());
bottomRight = model->index(1, 0, QModelIndex());
rowSelection.select(topLeft, bottomRight);
selectionModel->select(rowSelection,
QItemSelectionModel::Select | QItemSelectionModel::Rows);Obwohl dem Auswahlmodell nur vier Indizes übergeben werden, führt die Verwendung der Auswahlflags „ Columns “ und „ Rows “ dazu, dass zwei Spalten und zwei Zeilen ausgewählt werden. Die folgende Abbildung zeigt das Ergebnis dieser beiden Auswahlen:

Bei allen Befehlen, die am Beispielmodell ausgeführt wurden, ging es darum, eine Auswahl von Elementen im Modell zusammenzustellen. Es ist auch möglich, die Auswahl zu löschen oder die aktuelle Auswahl durch eine neue zu ersetzen.
Um die aktuelle Auswahl durch eine neue Auswahl zu ersetzen, kombinieren Sie die anderen Auswahlflags mit dem Flag „ Current “. Ein Befehl mit diesem Flag weist das Auswahlmodell an, seine aktuelle Sammlung von Modellindizes durch die in einem Aufruf von ` select()` angegebenen zu ersetzen. Um alle Auswahlen zu löschen, bevor Sie neue hinzufügen, kombinieren Sie die anderen Auswahlflags mit dem Flag ` Clear `. Dies hat zur Folge, dass die Sammlung von Modellindizes des Auswahlmodells zurückgesetzt wird.
Alle Elemente in einem Modell auswählen
Um alle Elemente in einem Modell auszuwählen, muss für jede Ebene des Modells eine Auswahl erstellt werden, die alle Elemente dieser Ebene abdeckt. Dazu rufen wir die Indizes ab, die den Elementen oben links und unten rechts mit einem bestimmten übergeordneten Index entsprechen:
QModelIndex topLeft = model->index(0, 0, parent);
QModelIndex bottomRight = model->index(model->rowCount(parent)-1,
model->columnCount(parent)-1, parent);Mit diesen Indizes und dem Modell wird eine Auswahl erstellt. Die entsprechenden Elemente werden dann im Auswahlmodell ausgewählt:
QItemSelection selection(topLeft, bottomRight);
selectionModel->select(selection, QItemSelectionModel::Select);Dies muss für alle Ebenen im Modell durchgeführt werden. Für Elemente der obersten Ebene würden wir den übergeordneten Index wie gewohnt definieren:
QModelIndex parent = QModelIndex();Bei hierarchischen Modellen wird die Funktion ` hasChildren()` verwendet, um festzustellen, ob ein bestimmtes Element das übergeordnete Element einer anderen Ebene von Elementen ist.
Erstellen neuer Modelle
Durch die Trennung der Funktionen zwischen den Modell- und Ansichtskomponenten lassen sich Modelle erstellen, die bereits vorhandene Ansichten nutzen können. Dieser Ansatz ermöglicht es uns, Daten aus einer Vielzahl von Quellen mithilfe von Standardkomponenten der grafischen Benutzeroberfläche darzustellen, wie beispielsweise QListView, QTableView und QTreeView.
Die Klasse ` QAbstractItemModel ` bietet eine Schnittstelle, die flexibel genug ist, um Datenquellen zu unterstützen, die Informationen in hierarchischen Strukturen anordnen, und dabei die Möglichkeit vorsieht, dass Daten eingefügt, entfernt, geändert oder auf irgendeine Weise sortiert werden können. Sie bietet außerdem Unterstützung für Drag-and-Drop-Operationen.
Die Klassen „ QAbstractListModel “ und „ QAbstractTableModel “ bieten Unterstützung für Schnittstellen zu einfacheren, nicht-hierarchischen Datenstrukturen und eignen sich besser als Ausgangspunkt für einfache Listen- und Tabellenmodelle.
In diesem Abschnitt erstellen wir ein einfaches, schreibgeschütztes Modell, um die Grundprinzipien der Model-View-Architektur zu untersuchen. Im weiteren Verlauf dieses Abschnitts passen wir dieses einfache Modell so an, dass Elemente vom Benutzer geändert werden können.
Ein Beispiel für ein komplexeres Modell finden Sie im Beispiel „Einfaches Baummodell “.
Die Anforderungen an Unterklassen von „ QAbstractItemModel “ werden im Dokument „Model Subclassing Reference“ ausführlicher beschrieben.
Entwerfen eines Modells
Beim Erstellen eines neuen Modells für eine bestehende Datenstruktur ist es wichtig zu überlegen, welcher Modelltyp verwendet werden sollte, um eine Schnittstelle zu den Daten bereitzustellen. Wenn die Daten bereits in einem C++-Container oder in einem beliebigen Typ gespeichert sind, der einen iterierbaren C++-Bereich modelliert, kann „ QRangeModel “ möglicherweise ohne jegliche Unterklassenbildung ein geeignetes Modell bereitstellen, vorausgesetzt, der Eingabebereich und sein Elementtyp werden unterstützt.
Wenn kein vorgefertigtes Modell anwendbar ist und die Datenstruktur als Liste oder Tabelle von Elementen dargestellt werden kann, können Sie Unterklassen von ` QAbstractListModel ` oder ` QAbstractTableModel ` erstellen, da diese Klassen für viele Funktionen geeignete Standardimplementierungen bereitstellen.
Wenn die zugrunde liegende Datenstruktur jedoch nur durch eine hierarchische Baumstruktur dargestellt werden kann, ist es notwendig, eine Unterklasse von ` QAbstractItemModel` zu erstellen. Dieser Ansatz wird im Beispiel „Simple Tree Model“ verfolgt.
In diesem Abschnitt implementieren wir ein einfaches Modell auf der Grundlage einer Liste von Zeichenfolgen, sodass ` QAbstractListModel ` eine ideale Basisklasse für die weitere Entwicklung darstellt.
Unabhängig davon, welche Form die zugrunde liegende Datenstruktur annimmt, ist es in der Regel sinnvoll, die Standard-API von ` QAbstractItemModel ` in spezialisierten Modellen durch eine API zu ergänzen, die einen natürlicheren Zugriff auf die zugrunde liegende Datenstruktur ermöglicht. Dies erleichtert das Befüllen des Modells mit Daten und ermöglicht es dennoch anderen allgemeinen Modell-/Ansichtskomponenten, über die Standard-API mit dem Modell zu interagieren. Das im Folgenden beschriebene Modell stellt genau zu diesem Zweck einen benutzerdefinierten Konstruktor bereit.
Ein schreibgeschütztes Beispielmodell
Das hier implementierte Modell ist ein einfaches, nicht-hierarchisches, schreibgeschütztes Datenmodell, das auf der Standardklasse ` QStringListModel ` basiert. Es verfügt über ein ` QStringList ` als interne Datenquelle und implementiert nur das, was für ein funktionsfähiges Modell erforderlich ist. Um die Implementierung zu vereinfachen, erstellen wir eine Unterklasse von ` QAbstractListModel `, da diese ein sinnvolles Standardverhalten für Listenmodelle definiert und eine einfachere Schnittstelle bereitstellt als die Klasse ` QAbstractItemModel `.
Bei der Implementierung eines Modells ist zu beachten, dass „ QAbstractItemModel “ selbst keine Daten speichert, sondern lediglich eine Schnittstelle bereitstellt, über die die Ansichten auf die Daten zugreifen. Für ein minimales, schreibgeschütztes Modell müssen nur wenige Funktionen implementiert werden, da für den Großteil der Schnittstelle Standardimplementierungen vorhanden sind. Die Klassendeklaration lautet wie folgt:
class StringListModel : public QAbstractListModel
{
Q_OBJECT
public:
StringListModel(const QStringList &strings, QObject *parent = nullptr)
: QAbstractListModel(parent), stringList(strings) {}
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role) const override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
private:
QStringList stringList;
};Abgesehen vom Konstruktor des Modells müssen wir nur zwei Funktionen implementieren: ` rowCount()` gibt die Anzahl der Zeilen im Modell zurück und ` data()` gibt ein Datenelement zurück, das einem bestimmten Modellindex entspricht.
Gut strukturierte Modelle implementieren außerdem ` headerData()` (Anzahl der Zeilen), um Baum- und Tabellenansichten Inhalte für ihre Kopfzeilen bereitzustellen.
Beachten Sie, dass es sich hierbei um ein nicht-hierarchisches Modell handelt, sodass wir uns keine Gedanken über Eltern-Kind-Beziehungen machen müssen. Wäre unser Modell hierarchisch, müssten wir zusätzlich die Funktionen ` index()` und ` parent()` implementieren.
Die Liste der Zeichenfolgen wird intern in der privaten Membervariablen ` stringList ` gespeichert.
Dimensionen des Modells
Wir möchten, dass die Anzahl der Zeilen im Modell der Anzahl der Zeichenfolgen in der Zeichenfolgenliste entspricht. Unter Berücksichtigung dieser Vorgabe implementieren wir die Funktion ` rowCount()`:
int StringListModel::rowCount(const QModelIndex &parent) const
{
return stringList.count();
}Da das Modell nicht hierarchisch ist, können wir den Modellindex, der dem übergeordneten Element entspricht, getrost ignorieren. Standardmäßig enthalten von „ QAbstractListModel “ abgeleitete Modelle nur eine Spalte, sodass wir die Funktion „ columnCount()“ nicht neu implementieren müssen.
Modell-Header und Daten
Für Elemente in der Ansicht möchten wir die Zeichenfolgen aus der Zeichenfolgenliste zurückgeben. Die Funktion ` data()` ist dafür zuständig, das Datenelement zurückzugeben, das dem Index-Argument entspricht:
QVariant StringListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() >= stringList.size())
return QVariant();
if (role == Qt::DisplayRole)
return stringList.at(index.row());
else
return QVariant();
}Wir geben nur dann ein gültiges ` QVariant ` zurück, wenn der übergebene Modellindex gültig ist, die Zeilennummer im Bereich der Elemente der Zeichenfolgenliste liegt und die angeforderte Rolle von uns unterstützt wird.
Einige Ansichten, wie beispielsweise „ QTreeView “ und „ QTableView “, können neben den Elementdaten auch Kopfzeilen anzeigen. Wenn unser Modell in einer Ansicht mit Kopfzeilen dargestellt wird, sollen diese die Zeilen- und Spaltennummern anzeigen. Wir können Informationen zu den Kopfzeilen bereitstellen, indem wir die Funktion „ headerData()“ unterklassen:
QVariant StringListModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
if (role != Qt::DisplayRole)
return QVariant();
if (orientation == Qt::Horizontal)
return QStringLiteral("Column %1").arg(section);
else
return QStringLiteral("Row %1").arg(section);
}Auch hier geben wir nur dann ein gültiges ` QVariant ` zurück, wenn es sich um eine von uns unterstützte Rolle handelt. Bei der Entscheidung über die genau zurückzugebenden Daten wird auch die Ausrichtung der Kopfzeile berücksichtigt.
Nicht alle Ansichten zeigen Kopfzeilen mit den Elementdaten an, und diejenigen, die dies tun, können so konfiguriert sein, dass sie diese ausblenden. Dennoch wird empfohlen, die Funktion ` headerData()` zu implementieren, um relevante Informationen zu den vom Modell bereitgestellten Daten bereitzustellen.
Ein Element kann mehrere Rollen haben und je nach angegebener Rolle unterschiedliche Daten bereitstellen. Die Elemente in unserem Modell haben nur eine Rolle, nämlich „ DisplayRole “, daher geben wir die Daten für Elemente unabhängig von der angegebenen Rolle zurück. Wir könnten jedoch die Daten, die wir für die Rolle „ DisplayRole “ bereitstellen, in anderen Rollen wiederverwenden, beispielsweise in der Rolle „ ToolTipRole “, die Ansichten nutzen können, um Informationen zu Elementen in einem Tooltip anzuzeigen.
Ein bearbeitbares Modell
Das schreibgeschützte Modell zeigt, wie einfache Auswahlmöglichkeiten dem Benutzer präsentiert werden könnten, doch für viele Anwendungen ist ein bearbeitbares Listenmodell wesentlich nützlicher. Wir können das schreibgeschützte Modell so anpassen, dass die Elemente bearbeitbar sind, indem wir die für den schreibgeschützten Modus implementierte Funktion `data()` ändern und zwei zusätzliche Funktionen implementieren: ` flags()` und ` setData()`. Die folgenden Funktionsdeklarationen werden der Klassendefinition hinzugefügt:
Qt::ItemFlags flags(const QModelIndex &index) const override;
bool setData(const QModelIndex &index, const QVariant &value,
int role = Qt::EditRole) override;Das Modell bearbeitbar machen
Ein Delegat prüft vor dem Erstellen eines Editors, ob ein Element bearbeitbar ist. Das Modell muss dem Delegaten mitteilen, dass seine Elemente bearbeitbar sind. Dies erreichen wir, indem wir für jedes Element im Modell die richtigen Flags zurückgeben; in diesem Fall aktivieren wir alle Elemente und machen sie sowohl auswählbar als auch bearbeitbar:
Qt::ItemFlags StringListModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::ItemIsEnabled;
return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}Beachten Sie, dass wir nicht wissen müssen, wie der Delegat den eigentlichen Bearbeitungsprozess durchführt. Wir müssen dem Delegaten lediglich eine Möglichkeit bereitstellen, die Daten im Modell festzulegen. Dies wird über die Funktion ` setData()` erreicht:
bool StringListModel::setData(const QModelIndex &index,
const QVariant &value, int role)
{
if (index.isValid() && role == Qt::EditRole) {
stringList.replace(index.row(), value.toString());
emit dataChanged(index, index, {role});
return true;
}
return false;
}In diesem Modell wird das Element in der Zeichenfolgenliste, das dem Modellindex entspricht, durch den bereitgestellten Wert ersetzt. Bevor wir jedoch die Zeichenfolgenliste ändern können, müssen wir sicherstellen, dass der Index gültig ist, das Element den richtigen Typ hat und die Rolle unterstützt wird. Gemäß Konvention bestehen wir darauf, dass die Rolle „ EditRole “ lautet, da dies die vom Standard-Element-Delegaten verwendete Rolle ist. Für boolesche Werte können Sie jedoch „ Qt::CheckStateRole “ verwenden und das Flag „ Qt::ItemIsUserCheckable “ setzen; in diesem Fall wird zur Bearbeitung des Werts ein Kontrollkästchen verwendet. Die zugrunde liegenden Daten in diesem Modell sind für alle Rollen identisch, sodass diese Besonderheit lediglich die Integration des Modells in Standardkomponenten erleichtert.
Nachdem die Daten festgelegt wurden, muss das Modell den Ansichten mitteilen, dass sich einige Daten geändert haben. Dies geschieht durch das Aussenden des Signals „ dataChanged()“. Da sich nur ein Datenelement geändert hat, ist der im Signal angegebene Bereich auf nur einen Modellindex beschränkt.
Außerdem muss die Funktion `data()` geändert werden, um die Prüfung ` Qt::EditRole ` hinzuzufügen:
QVariant StringListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (index.row() >= stringList.size())
return QVariant();
if (role == Qt::DisplayRole || role == Qt::EditRole)
return stringList.at(index.row());
else
return QVariant();
}Einfügen und Entfernen von Zeilen
Es ist möglich, die Anzahl der Zeilen und Spalten in einem Modell zu ändern. Im String-Listen-Modell ist es nur sinnvoll, die Anzahl der Zeilen zu ändern, daher implementieren wir lediglich die Funktionen zum Einfügen und Entfernen von Zeilen neu. Diese werden in der Klassendefinition deklariert:
bool insertRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;
bool removeRows(int position, int rows, const QModelIndex &index = QModelIndex()) override;Da Zeilen in diesem Modell Zeichenfolgen in einer Liste entsprechen, fügt die Funktion ` insertRows() ` eine Anzahl leerer Zeichenfolgen vor der angegebenen Position in die Zeichenfolgenliste ein. Die Anzahl der eingefügten Zeichenfolgen entspricht der angegebenen Anzahl von Zeilen.
Normalerweise wird der übergeordnete Index verwendet, um zu bestimmen, an welcher Stelle im Modell die Zeilen hinzugefügt werden sollen. In diesem Fall haben wir jedoch nur eine einzige String-Liste auf oberster Ebene, sodass wir einfach leere Strings in diese Liste einfügen.
bool StringListModel::insertRows(int position, int rows, const QModelIndex &parent)
{
beginInsertRows(QModelIndex(), position, position+rows-1);
for (int row = 0; row < rows; ++row) {
stringList.insert(position, "");
}
endInsertRows();
return true;
}Das Modell ruft zunächst die Funktion ` beginInsertRows()` auf, um anderen Komponenten mitzuteilen, dass sich die Anzahl der Zeilen ändern wird. Die Funktion gibt die Zeilennummern der ersten und letzten einzufügenden neuen Zeilen sowie den Modellindex ihres übergeordneten Elements an. Nach der Änderung der Zeichenfolgenliste ruft sie ` endInsertRows()` auf, um den Vorgang abzuschließen und anderen Komponenten mitzuteilen, dass sich die Dimensionen des Modells geändert haben, wobei sie „true“ zurückgibt, um den Erfolg anzuzeigen.
Die Funktion zum Entfernen von Zeilen aus dem Modell ist ebenfalls einfach zu schreiben. Die aus dem Modell zu entfernenden Zeilen werden durch die angegebene Position und die Anzahl der Zeilen festgelegt. Wir ignorieren den übergeordneten Index, um unsere Implementierung zu vereinfachen, und entfernen lediglich die entsprechenden Elemente aus der Zeichenfolgenliste.
bool StringListModel::removeRows(int position, int rows, const QModelIndex &parent)
{
beginRemoveRows(QModelIndex(), position, position+rows-1);
for (int row = 0; row < rows; ++row) {
stringList.removeAt(position);
}
endRemoveRows();
return true;
}Die Funktion ` beginRemoveRows()` wird immer aufgerufen, bevor zugrunde liegende Daten entfernt werden, und gibt die erste und letzte zu entfernende Zeile an. Dies ermöglicht es anderen Komponenten, auf die Daten zuzugreifen, bevor sie nicht mehr verfügbar sind. Nachdem die Zeilen entfernt wurden, sendet das Modell ` endRemoveRows()`, um den Vorgang abzuschließen und anderen Komponenten mitzuteilen, dass sich die Dimensionen des Modells geändert haben.
Nächste Schritte
Wir können die von diesem oder jedem anderen Modell bereitgestellten Daten mithilfe der Klasse ` QListView ` anzeigen, um die Elemente des Modells in Form einer vertikalen Liste darzustellen. Für das String-Listen-Modell stellt diese Ansicht zudem einen Standard-Editor bereit, sodass die Elemente bearbeitet werden können. Die Möglichkeiten, die die Standard-Ansichtsklassen bieten, werden im Abschnitt „Ansichtsklassen“ näher beleuchtet.
Das Dokument „Model Subclassing Reference“ behandelt die Anforderungen an Unterklassen von ` QAbstractItemModel ` ausführlicher und bietet einen Leitfaden zu den virtuellen Funktionen, die implementiert werden müssen, um verschiedene Funktionen in unterschiedlichen Modelltypen zu ermöglichen.
Hilfsklassen für Elementansichten
Die elementbasierten Widgets haben Namen, die ihre Verwendung widerspiegeln: „ QListWidget “ stellt eine Liste von Elementen bereit, „ QTreeWidget “ zeigt eine mehrstufige Baumstruktur an und „ QTableWidget “ stellt eine Tabelle mit Zellenelementen bereit. Jede Klasse erbt das Verhalten der Klasse „ QAbstractItemView “, die gemeinsames Verhalten für die Elementauswahl und die Kopfzeilenverwaltung implementiert.
Listen-Widgets
Einstufige Listen von Elementen werden in der Regel mithilfe eines „ QListWidget “ und mehrerer „ QListWidgetItems“ angezeigt. Ein Listen-Widget wird auf dieselbe Weise wie jedes andere Widget erstellt:
QListWidget *listWidget = new QListWidget(this);Listenelemente können beim Erstellen direkt zum Listen-Widget hinzugefügt werden:
new QListWidgetItem(tr("Sycamore"), listWidget);
new QListWidgetItem(tr("Chestnut"), listWidget);
new QListWidgetItem(tr("Mahogany"), listWidget);Sie können auch ohne übergeordnetes Listen-Widget erstellt und zu einem späteren Zeitpunkt einer Liste hinzugefügt werden:
QListWidgetItem *newItem = new QListWidgetItem;
newItem->setText(itemText);
listWidget->insertItem(row, newItem);Jedes Element in einer Liste kann eine Textbeschriftung und ein Symbol anzeigen. Die Farben und die Schriftart, die zum Darstellen des Textes verwendet werden, können geändert werden, um den Elementen ein individuelles Erscheinungsbild zu verleihen. Tooltips, Statustipps und die „Was ist das?“-Hilfe lassen sich ganz einfach konfigurieren, um sicherzustellen, dass die Liste nahtlos in die Anwendung integriert ist.
newItem->setToolTip(toolTipText);
newItem->setStatusTip(toolTipText);
newItem->setWhatsThis(whatsThisText);Standardmäßig werden Elemente in einer Liste in der Reihenfolge ihrer Erstellung angezeigt. Listen mit Elementen können gemäß den in „ Qt::SortOrder “ angegebenen Kriterien sortiert werden, um eine Liste zu erstellen, die in vorwärts oder rückwärts alphabetischer Reihenfolge sortiert ist:
Baum-Widgets
Bäume oder hierarchische Listen von Elementen werden von den Klassen ` QTreeWidget ` und ` QTreeWidgetItem ` bereitgestellt. Jedes Element im Baum-Widget kann eigene untergeordnete Elemente haben und eine beliebige Anzahl von Spalten mit Informationen anzeigen. Baum-Widgets werden wie jedes andere Widget erstellt:
QTreeWidget *treeWidget = new QTreeWidget(this);Bevor Elemente zum Baum-Widget hinzugefügt werden können, muss die Anzahl der Spalten festgelegt werden. Wir könnten beispielsweise zwei Spalten definieren und eine Kopfzeile erstellen, um oben in jeder Spalte Beschriftungen anzuzeigen:
treeWidget->setColumnCount(2);
QStringList headers;
headers << tr("Subject") << tr("Default");
treeWidget->setHeaderLabels(headers);Am einfachsten lassen sich die Beschriftungen für die einzelnen Abschnitte über eine Zeichenfolgenliste festlegen. Für komplexere Kopfzeilen können Sie ein Baumelement erstellen, es nach Belieben gestalten und als Kopfzeile des Baum-Widgets verwenden.
Elemente der obersten Ebene im Baum-Widget werden mit dem Baum-Widget als übergeordnetem Widget erstellt. Sie können in beliebiger Reihenfolge eingefügt werden, oder Sie können sicherstellen, dass sie in einer bestimmten Reihenfolge aufgelistet werden, indem Sie beim Erstellen jedes Elements das vorhergehende Element angeben:
QTreeWidgetItem *cities = new QTreeWidgetItem(treeWidget);
cities->setText(0, tr("Cities"));
QTreeWidgetItem *osloItem = new QTreeWidgetItem(cities);
osloItem->setText(0, tr("Oslo"));
osloItem->setText(1, tr("Yes"));
QTreeWidgetItem *planets = new QTreeWidgetItem(treeWidget, cities);Baum-Widgets behandeln Elemente der obersten Ebene etwas anders als andere Elemente aus tieferen Ebenen des Baums. Elemente können aus der obersten Ebene des Baums entfernt werden, indem die Funktion ` takeTopLevelItem()` des Baum-Widgets aufgerufen wird; Elemente aus tieferen Ebenen werden jedoch durch Aufruf der Funktion ` takeChild()` ihres übergeordneten Elements entfernt. Elemente werden mit der Funktion ` insertTopLevelItem()` in die oberste Ebene des Baums eingefügt. Auf tieferen Ebenen des Baums wird die Funktion ` insertChild()` des übergeordneten Elements verwendet.
Es ist einfach, Elemente zwischen der obersten Ebene und den unteren Ebenen des Baums zu verschieben. Wir müssen lediglich prüfen, ob es sich bei den Elementen um Elemente der obersten Ebene handelt oder nicht; diese Information wird von der Funktion ` parent() ` des jeweiligen Elements bereitgestellt. So können wir beispielsweise das aktuelle Element im Baum-Widget unabhängig von seiner Position entfernen:
QTreeWidgetItem *parent = currentItem->parent();
int index;
if (parent) {
index = parent->indexOfChild(treeWidget->currentItem());
delete parent->takeChild(index);
} else {
index = treeWidget->indexOfTopLevelItem(treeWidget->currentItem());
delete treeWidget->takeTopLevelItem(index);
}Das Einfügen des Elements an einer anderen Stelle im Baum-Widget folgt dem gleichen Muster:
QTreeWidgetItem *parent = currentItem->parent();
QTreeWidgetItem *newItem;
if (parent)
newItem = new QTreeWidgetItem(parent, treeWidget->currentItem());
else
newItem = new QTreeWidgetItem(treeWidget, treeWidget->currentItem());Tabellen-Widgets
Tabellen mit Elementen, ähnlich wie in Tabellenkalkulationsanwendungen, werden mit den Methoden ` QTableWidget ` und ` QTableWidgetItem` erstellt. Diese stellen ein scrollbares Tabellen-Widget mit Kopfzeilen und darin zu verwendenden Elementen bereit.
Tabellen können mit einer festgelegten Anzahl von Zeilen und Spalten erstellt werden, oder diese können bei Bedarf zu einer Tabelle ohne festgelegte Größe hinzugefügt werden.
QTableWidget *tableWidget;
tableWidget = new QTableWidget(12, 3, this);Elemente werden außerhalb der Tabelle erstellt, bevor sie an der gewünschten Stelle in die Tabelle eingefügt werden:
QTableWidgetItem *newItem = new QTableWidgetItem(tr("%1").arg(
pow(row, column+1)));
tableWidget->setItem(row, column, newItem);Horizontale und vertikale Kopfzeilen können der Tabelle hinzugefügt werden, indem Elemente außerhalb der Tabelle erstellt und als Kopfzeilen verwendet werden:
QTableWidgetItem *valuesHeaderItem = new QTableWidgetItem(tr("Values"));
tableWidget->setHorizontalHeaderItem(0, valuesHeaderItem);Beachten Sie, dass die Zeilen und Spalten in der Tabelle bei Null beginnen.
Gemeinsame Funktionen
Es gibt eine Reihe von elementbasierten Funktionen, die allen Convenience-Klassen gemeinsam sind und über dieselben Schnittstellen in jeder Klasse verfügbar sind. Wir stellen diese in den folgenden Abschnitten anhand einiger Beispiele für verschiedene Widgets vor. Weitere Details zur Verwendung der einzelnen Funktionen finden Sie in der Liste der Modell-/Ansichtsklassen für die jeweiligen Widgets.
Ausgeblendete Elemente
Manchmal ist es sinnvoll, Elemente in einem Elementansichts-Widget auszublenden, anstatt sie zu entfernen. Elemente für alle oben genannten Widgets können ausgeblendet und später wieder eingeblendet werden. Ob ein Element ausgeblendet ist, lässt sich mit der Funktion `isItemHidden()` feststellen; das Ausblenden erfolgt über ` setItemHidden()`.
Da dieser Vorgang elementbasiert ist, steht dieselbe Funktion für alle drei Hilfs-Klassen zur Verfügung.
Auswahlen
Die Art und Weise, wie Elemente ausgewählt werden, wird durch den Auswahlmodus des Widgets (QAbstractItemView::SelectionMode) gesteuert. Diese Eigenschaft legt fest, ob der Benutzer ein oder mehrere Elemente auswählen kann und, bei der Auswahl mehrerer Elemente, ob die Auswahl einen zusammenhängenden Bereich von Elementen umfassen muss. Der Auswahlmodus funktioniert bei allen oben genannten Widgets auf dieselbe Weise.
| Auswahl einzelner Elemente: Wenn der Benutzer ein einzelnes Element aus einem Widget auswählen muss, ist der Standardmodus „ SingleSelection “ am besten geeignet. In diesem Modus sind das aktuelle Element und das ausgewählte Element identisch. |
| Auswahl mehrerer Elemente: In diesem Modus kann der Benutzer den Auswahlstatus jedes beliebigen Elements im Widget umschalten, ohne die bestehende Auswahl zu ändern – ähnlich wie bei nicht-exklusiven Kontrollkästchen, die unabhängig voneinander umgeschaltet werden können. |
| Erweiterte Auswahl: Widgets, bei denen häufig viele benachbarte Elemente ausgewählt werden müssen, wie sie beispielsweise in Tabellenkalkulationen vorkommen, erfordern den Modus „ ExtendedSelection “. In diesem Modus können zusammenhängende Bereiche von Elementen im Widget sowohl mit der Maus als auch mit der Tastatur ausgewählt werden. Komplexe Auswahlen, die viele Elemente umfassen, die im Widget nicht an andere ausgewählte Elemente angrenzen, können ebenfalls erstellt werden, wenn Modifikatortasten verwendet werden.Wählt der Benutzer ein Element ohne Verwendung einer Modifikatortaste aus, wird die bestehende Auswahl aufgehoben. |
Die ausgewählten Elemente in einem Widget werden mithilfe der Funktion „ selectedItems() “ ausgelesen, die eine Liste der relevanten Elemente liefert, über die iteriert werden kann. Beispielsweise können wir mit dem folgenden Code die Summe aller numerischen Werte innerhalb einer Liste ausgewählter Elemente ermitteln:
const QList<QTableWidgetItem *> selected = tableWidget->selectedItems();
int number = 0;
double total = 0;
for (QTableWidgetItem *item : selected) {
bool ok;
double value = item->text().toDouble(&ok);
if (ok && !item->text().isEmpty()) {
total += value;
number++;
}
}Beachten Sie, dass im Einzelauswahlmodus das aktuelle Element Teil der Auswahl ist. Im Mehrfachauswahl- und im erweiterten Auswahlmodus liegt das aktuelle Element möglicherweise nicht innerhalb der Auswahl, je nachdem, wie der Benutzer die Auswahl gebildet hat.
Suche
Häufig ist es nützlich, Elemente innerhalb eines Elementansichts-Widgets finden zu können – sei es als Entwickler oder als Dienst, der den Benutzern zur Verfügung gestellt wird. Alle drei Komfortklassen für Elementansichten bieten eine gemeinsame Funktion „ findItems() “, um dies so einheitlich und einfach wie möglich zu gestalten.
Die Suche nach Elementen erfolgt anhand des darin enthaltenen Textes gemäß den Kriterien, die durch eine Auswahl von Werten aus Qt::MatchFlags festgelegt werden. Mit der Funktion findItems() können wir eine Liste der übereinstimmenden Elemente abrufen:
const QList<QTreeWidgetItem *> found = treeWidget->findItems(
itemText, Qt::MatchWildcard);
for (QTreeWidgetItem *item : found) {
item->setSelected(true);
// Show the item->text(0) for each item.
}Der obige Code bewirkt, dass Elemente in einem Baum-Widget ausgewählt werden, wenn sie den im Suchstring angegebenen Text enthalten. Dieses Muster kann auch in den Listen- und Tabellen-Widgets verwendet werden.
Verwendung von Drag & Drop mit Elementansichten
Die Drag-and-Drop-Infrastruktur von Qt wird vom Model-View-Framework vollständig unterstützt. Elemente in Listen, Tabellen und Bäumen können innerhalb der Ansichten verschoben werden, und Daten können als MIME-kodierte Daten importiert und exportiert werden.
Die Standardansichten unterstützen automatisch internes Drag-and-Drop, bei dem Elemente verschoben werden, um die Reihenfolge ihrer Anzeige zu ändern. Standardmäßig ist Drag-and-Drop für diese Ansichten nicht aktiviert, da sie für die einfachsten und gängigsten Anwendungsfälle konfiguriert sind. Damit Elemente verschoben werden können, müssen bestimmte Eigenschaften der Ansicht aktiviert werden, und auch die Elemente selbst müssen das Verschieben zulassen.
Die Anforderungen an ein Modell, das nur den Export von Elementen aus einer Ansicht zulässt und in das keine Daten hineingezogen werden können, sind geringer als die für ein Modell mit vollständig aktiviertem Drag-and-Drop.
Weitere Informationen zum Aktivieren der Drag-and-Drop-Unterstützung in neuen Modellen finden Sie in der Referenz zur Modellunterklassifizierung.
Verwendung von Komfortansichten
Jeder der mit „ QListWidget “, „ QTableWidget “ und „ QTreeWidget “ verwendeten Elementtypen ist standardmäßig so konfiguriert, dass er einen anderen Satz von Flags verwendet. Beispielsweise ist jedes „ QListWidgetItem “ oder „ QTreeWidgetItem “ zunächst aktiviert, ankreuzbar, auswählbar und kann als Quelle für einen Drag-and-Drop-Vorgang verwendet werden; jedes „ QTableWidgetItem “ kann zudem bearbeitet und als Ziel eines Drag-and-Drop-Vorgangs verwendet werden.
Obwohl bei allen Standardelementen ein oder beide Flags für Drag & Drop gesetzt sind, müssen Sie in der Regel verschiedene Eigenschaften in der Ansicht selbst festlegen, um die integrierte Unterstützung für Drag & Drop nutzen zu können:
- Um das Ziehen von Elementen zu aktivieren, setzen Sie die Eigenschaft „ dragEnabled “ der Ansicht auf „
true“. - Damit der Benutzer sowohl interne als auch externe Elemente in der Ansicht ablegen kann, setzen Sie die Eigenschaft „ viewport()“ der Ansicht auf „ acceptDrops “ und die Eigenschaft „ “ auf „
true“. - Um dem Benutzer anzuzeigen, wo das gerade gezogene Element beim Ablegen platziert wird, legen Sie die Eigenschaft „ showDropIndicator “ der Ansicht fest. Dadurch erhält der Benutzer kontinuierlich aktualisierte Informationen über die Platzierung des Elements innerhalb der Ansicht.
Beispielsweise können wir Drag & Drop in einem Listen-Widget mit den folgenden Codezeilen aktivieren:
QListWidget *listWidget = new QListWidget(this);
listWidget->setSelectionMode(QAbstractItemView::SingleSelection);
listWidget->setDragEnabled(true);
listWidget->viewport()->setAcceptDrops(true);
listWidget->setDropIndicatorShown(true);Das Ergebnis ist ein Listen-Widget, in dem die Elemente innerhalb der Ansicht verschoben und kopiert werden können und das es dem Benutzer sogar ermöglicht, Elemente zwischen Ansichten mit demselben Datentyp zu verschieben. In beiden Fällen werden die Elemente kopiert und nicht verschoben.
Damit der Benutzer die Elemente innerhalb der Ansicht verschieben kann, müssen wir die Eigenschaft ` dragDropMode` des Listen-Widgets festlegen:
listWidget->setDragDropMode(QAbstractItemView::InternalMove);Verwendung von Modell-/Ansichtsklassen
Das Einrichten einer Ansicht für Drag-and-Drop folgt demselben Muster wie bei den Convenience-Ansichten. So kann beispielsweise ein ` QListView ` auf dieselbe Weise eingerichtet werden wie ein ` QListWidget`:
QListView *listView = new QListView(this);
listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
listView->setDragEnabled(true);
listView->setAcceptDrops(true);
listView->setDropIndicatorShown(true);Da der Zugriff auf die von der Ansicht angezeigten Daten durch ein Modell gesteuert wird, muss das verwendete Modell auch Drag-and-Drop-Operationen unterstützen. Die von einem Modell unterstützten Aktionen lassen sich durch eine Neuimplementierung der Funktion ` QAbstractItemModel::supportedDropActions()` festlegen. So werden beispielsweise Kopier- und Verschiebeoperationen mit dem folgenden Code aktiviert:
Qt::DropActions DragDropListModel::supportedDropActions() const
{
return Qt::CopyAction | Qt::MoveAction;
}Obwohl beliebige Kombinationen von Werten aus ` Qt::DropActions ` angegeben werden können, muss das Modell so geschrieben sein, dass es diese unterstützt. Um beispielsweise die ordnungsgemäße Verwendung von ` Qt::MoveAction ` mit einem Listenmodell zu ermöglichen, muss das Modell eine Implementierung von ` QAbstractItemModel::removeRows()` bereitstellen, entweder direkt oder durch Vererbung der Implementierung von seiner Basisklasse.
Drag-and-Drop für Elemente aktivieren
Modelle teilen den Ansichten mit, welche Elemente gezogen werden können und welche das Ablegen zulassen, indem sie die Funktion ` QAbstractItemModel::flags()` neu implementieren, um geeignete Flags bereitzustellen.
Beispielsweise kann ein Modell, das eine einfache Liste auf Basis von ` QAbstractListModel ` bereitstellt, Drag-and-Drop für jedes der Elemente aktivieren, indem es sicherstellt, dass die zurückgegebenen Flags die Werte ` Qt::ItemIsDragEnabled ` und ` Qt::ItemIsDropEnabled ` enthalten:
Qt::ItemFlags DragDropListModel::flags(const QModelIndex &index) const
{
Qt::ItemFlags defaultFlags = QStringListModel::flags(index);
if (index.isValid())
return Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled | defaultFlags;
else
return Qt::ItemIsDropEnabled | defaultFlags;
}Beachten Sie, dass Elemente auf die oberste Ebene des Modells abgelegt werden können, das Ziehen jedoch nur für gültige Elemente aktiviert ist.
Da das Modell im obigen Code von ` QStringListModel` abgeleitet ist, erhalten wir einen Standardsatz an Flags, indem wir dessen Implementierung der Funktion `flags()` aufrufen.
Kodierung exportierter Daten
Wenn Datenelemente im Rahmen eines Drag-and-Drop-Vorgangs aus einem Modell exportiert werden, werden sie in ein geeignetes Format kodiert, das einem oder mehreren MIME-Typen entspricht. Modelle deklarieren die MIME-Typen, die sie zur Bereitstellung von Elementen verwenden können, indem sie die Funktion ` QAbstractItemModel::mimeTypes()` neu implementieren und eine Liste von Standard-MIME-Typen zurückgeben.
Ein Modell, das beispielsweise nur Klartext bereitstellt, würde die folgende Implementierung liefern:
QStringList DragDropListModel::mimeTypes() const
{
QStringList types;
types << "application/vnd.text.list";
return types;
}Das Modell muss außerdem Code bereitstellen, um Daten in das angekündigte Format zu kodieren. Dies wird durch die Neuimplementierung der Funktion ` QAbstractItemModel::mimeData()` erreicht, um ein ` QMimeData `-Objekt bereitzustellen, genau wie bei jedem anderen Drag-and-Drop-Vorgang.
Der folgende Code zeigt, wie jedes Datenelement, das einer bestimmten Liste von Indizes entspricht, als Klartext kodiert und in einem ` QMimeData `-Objekt gespeichert wird.
QMimeData *DragDropListModel::mimeData(const QModelIndexList &indexes) const
{
QMimeData *mimeData = new QMimeData;
QByteArray encodedData;
QDataStream stream(&encodedData, QIODevice::WriteOnly);
for (const QModelIndex &index : indexes) {
if (index.isValid()) {
QString text = data(index, Qt::DisplayRole).toString();
stream << text;
}
}
mimeData->setData("application/vnd.text.list", encodedData);
return mimeData;
}Da der Funktion eine Liste von Modellindizes übergeben wird, ist dieser Ansatz allgemein genug, um sowohl in hierarchischen als auch in nicht-hierarchischen Modellen verwendet zu werden.
Beachten Sie, dass benutzerdefinierte Datentypen als „ meta objects “ deklariert werden müssen und dass dafür Stream-Operatoren implementiert werden müssen. Weitere Informationen finden Sie in der Klassenbeschreibung von „ QMetaObject “.
Einfügen von ausgelassenen Daten in ein Modell
Die Art und Weise, wie ein bestimmtes Modell gelöschte Daten behandelt, hängt sowohl von seinem Typ (Liste, Tabelle oder Baum) als auch davon ab, wie sein Inhalt dem Benutzer voraussichtlich präsentiert wird. Im Allgemeinen sollte der Ansatz zur Behandlung gelöschter Daten derjenige sein, der am besten zum zugrunde liegenden Datenspeicher des Modells passt.
Verschiedene Modelltypen gehen in der Regel unterschiedlich mit abgelegten Daten um. Listen- und Tabellenmodelle bieten lediglich eine flache Struktur, in der Datenelemente gespeichert werden. Daher fügen sie möglicherweise neue Zeilen (und Spalten) ein, wenn Daten auf ein bestehendes Element in einer Ansicht gezogen werden, oder sie überschreiben den Inhalt des Elements im Modell unter Verwendung eines Teils der bereitgestellten Daten. Baummodelle sind oft in der Lage, untergeordnete Elemente mit neuen Daten in ihren zugrunde liegenden Datenspeichern hinzuzufügen und verhalten sich daher aus Sicht des Benutzers vorhersehbarer.
Das Handling von abgelegten Daten erfolgt über die Neuimplementierung der Methode ` QAbstractItemModel::dropMimeData()` durch ein Modell. Ein Modell, das beispielsweise eine einfache Liste von Zeichenketten verarbeitet, kann eine Implementierung bereitstellen, die Daten, die auf bereits vorhandene Elemente abgelegt werden, getrennt von Daten behandelt, die auf der obersten Ebene des Modells (d. h. auf einem ungültigen Element) abgelegt werden.
Modelle können das Ablegen auf bestimmten Elementen oder in Abhängigkeit von den abgelegten Daten untersagen, indem sie ` QAbstractItemModel::canDropMimeData()` neu implementieren.
Das Modell muss zunächst sicherstellen, dass die Operation ausgeführt werden soll, dass die bereitgestellten Daten in einem verwertbaren Format vorliegen und dass ihr Zielort innerhalb des Modells gültig ist:
bool DragDropListModel::canDropMimeData(const QMimeData *data,
Qt::DropAction action, int row, int column, const QModelIndex &parent) const
{
Q_UNUSED(action);
Q_UNUSED(row);
Q_UNUSED(parent);
if (!data->hasFormat("application/vnd.text.list"))
return false;
if (column > 0)
return false;
return true;
}
bool DragDropListModel::dropMimeData(const QMimeData *data,
Qt::DropAction action, int row, int column, const QModelIndex &parent)
{
if (!canDropMimeData(data, action, row, column, parent))
return false;
if (action == Qt::IgnoreAction)
return true;Ein einfaches, einspaltiges Zeichenfolgenlistenmodell kann einen Fehler melden, wenn die bereitgestellten Daten kein reiner Text sind oder wenn die für das Ablegen angegebene Spaltennummer ungültig ist.
Die in das Modell einzufügenden Daten werden unterschiedlich behandelt, je nachdem, ob sie auf ein vorhandenes Element abgelegt werden oder nicht. In diesem einfachen Beispiel möchten wir das Ablegen zwischen vorhandenen Elementen, vor dem ersten Element in der Liste und nach dem letzten Element zulassen.
Wenn ein Ablegen erfolgt, ist der dem übergeordneten Element entsprechende Modellindex entweder gültig – was bedeutet, dass das Ablegen auf einem Element erfolgte – oder ungültig – was bedeutet, dass das Ablegen an einer Stelle in der Ansicht erfolgte, die der obersten Ebene des Modells entspricht.
int beginRow;
if (row != -1)
beginRow = row;Zunächst prüfen wir die übergebene Zeilennummer, um festzustellen, ob wir sie zum Einfügen von Elementen in das Modell verwenden können, unabhängig davon, ob der übergeordnete Index gültig ist oder nicht.
else if (parent.isValid())
beginRow = parent.row();Ist der übergeordnete Modellindex gültig, erfolgte das Ablegen auf einem Element. In diesem einfachen Listenmodell ermitteln wir die Zeilennummer des Elements und verwenden diesen Wert, um abgelegte Elemente in die oberste Ebene des Modells einzufügen.
else
beginRow = rowCount(QModelIndex());Wenn ein Drop an einer anderen Stelle in der Ansicht erfolgt und die Zeilennummer nicht verwendet werden kann, fügen wir Elemente an die oberste Ebene des Modells an.
In hierarchischen Modellen ist es bei einem Drop eines Elements besser, neue Elemente als untergeordnete Elemente dieses Elements in das Modell einzufügen. In dem hier gezeigten einfachen Beispiel hat das Modell nur eine Ebene, sodass dieser Ansatz nicht geeignet ist.
Dekodierung importierter Daten
Jede Implementierung von ` dropMimeData()` muss außerdem die Daten dekodieren und in die zugrunde liegende Datenstruktur des Modells einfügen.
Bei einem einfachen String-Listen-Modell können die kodierten Elemente dekodiert und in eine ` QStringList` gestreamt werden:
QByteArray encodedData = data->data("application/vnd.text.list");
QDataStream stream(&encodedData, QIODevice::ReadOnly);
QStringList newItems;
int rows = 0;
while (!stream.atEnd()) {
QString text;
stream >> text;
newItems << text;
++rows;
}Die Zeichenfolgen können dann in den zugrunde liegenden Datenspeicher eingefügt werden. Aus Gründen der Konsistenz kann dies über die modellspezifische Schnittstelle erfolgen:
insertRows(beginRow, rows, QModelIndex());
for (const QString &text : std::as_const(newItems)) {
QModelIndex idx = index(beginRow, 0, QModelIndex());
setData(idx, text);
beginRow++;
}
return true;
}Beachten Sie, dass das Modell in der Regel Implementierungen der Funktionen ` QAbstractItemModel::insertRows()` und ` QAbstractItemModel::setData()` bereitstellen muss.
Proxy-Modelle
Im Model-View-Framework können Datenelemente, die von einem einzelnen Modell bereitgestellt werden, von einer beliebigen Anzahl von Ansichten gemeinsam genutzt werden, wobei jede dieser Ansichten dieselben Informationen auf völlig unterschiedliche Weise darstellen kann. Benutzerdefinierte Ansichten und Delegaten sind effektive Mittel, um völlig unterschiedliche Darstellungen derselben Daten bereitzustellen. Anwendungen müssen jedoch häufig konventionelle Ansichten auf verarbeitete Versionen derselben Daten bereitstellen, wie beispielsweise unterschiedlich sortierte Ansichten einer Liste von Elementen.
Obwohl es naheliegend erscheint, Sortier- und Filtervorgänge als interne Funktionen der Views durchzuführen, erlaubt dieser Ansatz nicht, dass mehrere Views die Ergebnisse solcher potenziell aufwendigen Vorgänge gemeinsam nutzen. Der alternative Ansatz, bei dem die Sortierung innerhalb des Modells selbst erfolgt, führt zu einem ähnlichen Problem: Jede View muss Datenelemente anzeigen, die entsprechend dem zuletzt durchgeführten Verarbeitungsvorgang organisiert sind.
Um dieses Problem zu lösen, nutzt das Modell-Ansicht-Framework Proxy-Modelle zur Verwaltung der Informationen, die zwischen einzelnen Modellen und Ansichten ausgetauscht werden. Proxy-Modelle sind Komponenten, die sich aus der Perspektive einer Ansicht wie gewöhnliche Modelle verhalten und im Auftrag dieser Ansicht auf Daten aus Quellmodellen zugreifen. Die vom Modell-Ansicht-Framework verwendeten Signale und Slots stellen sicher, dass jede Ansicht ordnungsgemäß aktualisiert wird, unabhängig davon, wie viele Proxy-Modelle zwischen ihr und dem Quellmodell liegen.
Verwendung von Proxy-Modellen
Proxy-Modelle können zwischen einem bestehenden Modell und einer beliebigen Anzahl von Ansichten eingefügt werden. Qt wird mit einem Standard-Proxy-Modell, ` QSortFilterProxyModel`, ausgeliefert, das in der Regel direkt instanziiert und verwendet wird, aber auch unterklassifiziert werden kann, um benutzerdefinierte Filter- und Sortierfunktionen bereitzustellen. Die Klasse ` QSortFilterProxyModel ` kann wie folgt verwendet werden:
QSortFilterProxyModel *filterModel = new QSortFilterProxyModel(parent);
filterModel->setSourceModel(stringListModel);
QListView *filteredView = new QListView;
filteredView->setModel(filterModel);Da Proxy-Modelle von ` QAbstractItemModel` erben, können sie mit jeder Art von Ansicht verbunden und von mehreren Ansichten gemeinsam genutzt werden. Sie können auch dazu verwendet werden, die von anderen Proxy-Modellen erhaltenen Informationen in einer Pipeline-Anordnung zu verarbeiten.
Die Klasse ` QSortFilterProxyModel ` ist so konzipiert, dass sie direkt in Anwendungen instanziiert und verwendet werden kann. Speziellere Proxy-Modelle lassen sich erstellen, indem man diese Klasse untererbt und die erforderlichen Vergleichsoperationen implementiert.
Anpassung von Proxy-Modellen
Im Allgemeinen besteht die in einem Proxy-Modell verwendete Verarbeitungsart darin, jedes Datenelement von seinem ursprünglichen Speicherort im Quellmodell auf einen anderen Speicherort im Proxy-Modell abzubilden. In manchen Modellen haben einige Elemente möglicherweise keinen entsprechenden Speicherort im Proxy-Modell; diese Modelle sind Filter-Proxy-Modelle. Ansichten greifen über vom Proxy-Modell bereitgestellte Modellindizes auf Elemente zu; diese enthalten keine Informationen über das Quellmodell oder die Speicherorte der ursprünglichen Elemente in diesem Modell.
QSortFilterProxyModel ermöglicht es, Daten aus einem Quellmodell zu filtern, bevor sie an Ansichten übergeben werden, und erlaubt zudem, den Inhalt eines Quellmodells als vorsortierte Daten an Ansichten zu übergeben.
Benutzerdefinierte Filtermodelle
Die Klasse „ QSortFilterProxyModel “ stellt ein Filtermodell bereit, das recht vielseitig ist und in einer Vielzahl gängiger Situationen eingesetzt werden kann. Für fortgeschrittene Benutzer bietet „ QSortFilterProxyModel “ die Möglichkeit, Unterklassen zu erstellen, wodurch ein Mechanismus zur Implementierung benutzerdefinierter Filter bereitgestellt wird.
Unterklassen von ` QSortFilterProxyModel ` können zwei virtuelle Funktionen neu implementieren, die immer dann aufgerufen werden, wenn ein Modellindex aus dem Proxy-Modell angefordert oder verwendet wird:
- filterAcceptsColumn() dient dazu, bestimmte Spalten aus einem Teil des Quellmodells herauszufiltern.
- filterAcceptsRow() dient dazu, bestimmte Zeilen aus einem Teil des Quellmodells herauszufiltern.
Die Standardimplementierungen der oben genannten Funktionen in ` QSortFilterProxyModel ` geben `true` zurück, um sicherzustellen, dass alle Elemente an die Ansichten weitergeleitet werden; Neuimplementierungen dieser Funktionen sollten `false` zurückgeben, um einzelne Zeilen und Spalten herauszufiltern.
Benutzerdefinierte Sortiermodelle
QSortFilterProxyModel verwenden die Funktion `std::stable_sort()`, um Zuordnungen zwischen Elementen im Quellmodell und denen im Proxy-Modell einzurichten, wodurch eine sortierte Hierarchie von Elementen den Ansichten zur Verfügung gestellt werden kann, ohne die Struktur des Quellmodells zu verändern. Um ein benutzerdefiniertes Sortierverhalten bereitzustellen, implementieren Sie die Funktion ` lessThan()` neu, um benutzerdefinierte Vergleiche durchzuführen.
Referenz zur Modellunterklassenbildung
Modellunterklassen müssen Implementierungen für viele der in der Basisklasse ` QAbstractItemModel ` definierten virtuellen Funktionen bereitstellen. Die Anzahl dieser Funktionen, die implementiert werden müssen, hängt vom Modelltyp ab – je nachdem, ob das Modell Ansichten mit einer einfachen Liste, einer Tabelle oder einer komplexen Hierarchie von Elementen versorgt. Modelle, die von ` QAbstractListModel ` und ` QAbstractTableModel ` erben, können die Standardimplementierungen der von diesen Klassen bereitgestellten Funktionen nutzen. Modelle, die Datenelemente in baumartigen Strukturen bereitstellen, müssen Implementierungen für viele der virtuellen Funktionen in ` QAbstractItemModel` bereitstellen.
Die Funktionen, die in einer Modellunterklasse implementiert werden müssen, lassen sich in drei Gruppen unterteilen:
- Verarbeitung von Elementdaten: Alle Modelle müssen Funktionen implementieren, damit Ansichten und Delegaten die Dimensionen des Modells abfragen, Elemente untersuchen und Daten abrufen können.
- Navigation und Indexerstellung: Hierarchische Modelle müssen Funktionen bereitstellen, die Ansichten aufrufen können, um durch die von ihnen bereitgestellten baumartigen Strukturen zu navigieren und Modellindizes für Elemente abzurufen.
- Unterstützung von Drag & Drop und MIME-Typ-Verarbeitung: Modelle erben Funktionen, die die Art und Weise steuern, wie interne und externe Drag-&-Drop-Vorgänge durchgeführt werden. Diese Funktionen ermöglichen es, Datenelemente anhand von MIME-Typen zu beschreiben, die andere Komponenten und Anwendungen verstehen können.
Verarbeitung von Elementdaten
Modelle können unterschiedliche Zugriffsebenen auf die von ihnen bereitgestellten Daten bieten: Sie können einfache, schreibgeschützte Komponenten sein, manche Modelle unterstützen möglicherweise Größenänderungsvorgänge, und andere lassen möglicherweise die Bearbeitung von Elementen zu.
Schreibgeschützter Zugriff
Um einen schreibgeschützten Zugriff auf die von einem Modell bereitgestellten Daten zu ermöglichen, müssen die folgenden Funktionen in der Unterklasse des Modells implementiert werden:
| flags() | Wird von anderen Komponenten verwendet, um Informationen über jedes vom Modell bereitgestellte Element abzurufen. In vielen Modellen sollte die Kombination der Flags „ Qt::ItemIsEnabled “ und „ Qt::ItemIsSelectable “ enthalten. |
| data() | Wird verwendet, um Elementdaten an Ansichten und Delegaten zu übergeben. Im Allgemeinen müssen Modelle nur Daten für „ Qt::DisplayRole “ und etwaige anwendungsspezifische Benutzerrollen bereitstellen; es empfiehlt sich jedoch, auch Daten für „ Qt::ToolTipRole “, „ Qt::AccessibleTextRole “ und „ Qt::AccessibleDescriptionRole “ bereitzustellen. Informationen zu den mit den einzelnen Rollen verbundenen Typen finden Sie in der Dokumentation zur Enumeration „ Qt::ItemDataRole “. |
| headerData() | Stellt Ansichten Informationen zur Verfügung, die in ihren Kopfzeilen angezeigt werden sollen. Die Informationen werden nur von Ansichten abgerufen, die Kopfzeileninformationen anzeigen können. |
| rowCount() | Liefert die Anzahl der vom Modell bereitgestellten Datenzeilen. |
Diese vier Funktionen müssen in allen Modelltypen implementiert werden, einschließlich Listenmodellen (Unterklassen von `QAbstractListModel `) und Tabellenmodellen (Unterklassen von `QAbstractTableModel `).
Darüber hinaus müssen die folgenden Funktionen in direkten Unterklassen von ` QAbstractTableModel ` und ` QAbstractItemModel` implementiert werden:
| columnCount() | Gibt die Anzahl der vom Modell bereitgestellten Datenspalten zurück. Listenmodelle stellen diese Funktion nicht bereit, da sie bereits in ` QAbstractListModel` implementiert ist. |
Bearbeitbare Elemente
Bearbeitbare Modelle ermöglichen die Änderung von Datenelementen und können zudem Funktionen zum Einfügen und Entfernen von Zeilen und Spalten bereitstellen. Um die Bearbeitung zu ermöglichen, müssen die folgenden Funktionen korrekt implementiert werden:
| flags() | Muss für jedes Element eine geeignete Kombination von Flags zurückgeben. Insbesondere muss der von dieser Funktion zurückgegebene Wert zusätzlich zu den Werten, die auf Elemente in einem schreibgeschützten Modell angewendet werden, auch „ Qt::ItemIsEditable “ enthalten. |
| setData() | Wird verwendet, um das mit einem bestimmten Modellindex verknüpfte Datenelement zu ändern. Um Benutzereingaben über Elemente der Benutzeroberfläche akzeptieren zu können, muss diese Funktion Daten verarbeiten, die mit „ Qt::EditRole “ verknüpft sind. Die Implementierung kann auch Daten akzeptieren, die mit vielen verschiedenen Arten von Rollen verknüpft sind, die durch „ Qt::ItemDataRole “ spezifiziert werden. Nach der Änderung des Datenelements müssen Modelle das Signal „ dataChanged()“ ausgeben, um andere Komponenten über die Änderung zu informieren. |
| setHeaderData() | Wird verwendet, um horizontale und vertikale Kopfzeileninformationen zu ändern. Nach der Änderung des Datenelements müssen Modelle das Signal „ headerDataChanged()“ ausgeben, um andere Komponenten über die Änderung zu informieren. |
Skalierbare Modelle
Alle Modelltypen können das Einfügen und Entfernen von Zeilen unterstützen. Tabellenmodelle und hierarchische Modelle können zudem das Einfügen und Entfernen von Spalten unterstützen. Es ist wichtig, andere Komponenten sowohl vor als auch nach dem Auftreten von Änderungen an den Dimensionen des Modells darüber zu informieren. Daher können die folgenden Funktionen implementiert werden, um die Größenanpassung des Modells zu ermöglichen; die Implementierungen müssen jedoch sicherstellen, dass die entsprechenden Funktionen aufgerufen werden, um verbundene Ansichten und Delegaten zu benachrichtigen:
| insertRows() | Wird verwendet, um neue Zeilen und Datenelemente zu allen Modelltypen hinzuzufügen. Implementierungen müssen vor dem Einfügen neuer Zeilen in zugrunde liegende Datenstrukturen die Funktion ` beginInsertRows()` aufrufen und unmittelbar danach die Funktion ` endInsertRows()` aufrufen. |
| removeRows() | Wird verwendet, um Zeilen und die darin enthaltenen Datenelemente aus allen Modelltypen zu entfernen. Implementierungen müssen beginRemoveRows() aufrufen, bevor Zeilen aus den zugrunde liegenden Datenstrukturen entfernt werden, und unmittelbar danach endRemoveRows() aufrufen. |
| insertColumns() | Wird verwendet, um neue Spalten und Datenelemente zu Tabellenmodellen und hierarchischen Modellen hinzuzufügen. Implementierungen müssen beginInsertColumns() aufrufen, bevor neue Spalten in zugrunde liegende Datenstrukturen eingefügt werden, und unmittelbar danach endInsertColumns() aufrufen. |
| removeColumns() | Wird verwendet, um Spalten und die darin enthaltenen Datenelemente aus Tabellenmodellen und hierarchischen Modellen zu entfernen. Implementierungen müssen beginRemoveColumns() aufrufen, bevor Spalten aus zugrunde liegenden Datenstrukturen entfernt werden, und unmittelbar danach endRemoveColumns() aufrufen. |
Im Allgemeinen sollten diese Funktionen „true“ zurückgeben, wenn der Vorgang erfolgreich war. Es kann jedoch Fälle geben, in denen der Vorgang nur teilweise erfolgreich war; beispielsweise, wenn weniger als die angegebene Anzahl von Zeilen eingefügt werden konnte. In solchen Fällen sollte das Modell „false“ zurückgeben, um einen Fehler anzuzeigen, damit angehängte Komponenten die Situation bewältigen können.
Die von den in Implementierungen der Resizing-API aufgerufenen Funktionen ausgesendeten Signale geben angeschlossene Komponenten die Möglichkeit, Maßnahmen zu ergreifen, bevor Daten nicht mehr verfügbar sind. Die Kapselung von Einfüge- und Entfernungsoperationen durch „begin“- und „end“-Funktionen ermöglicht es dem Modell zudem, „ persistent model indexes “ korrekt zu verwalten.
Normalerweise sind die „begin“- und „end“-Funktionen in der Lage, andere Komponenten über Änderungen an der zugrunde liegenden Struktur des Modells zu informieren. Bei komplexeren Änderungen an der Struktur des Modells, die möglicherweise eine interne Neuordnung, das Sortieren von Daten oder andere strukturelle Änderungen beinhalten, muss die folgende Abfolge ausgeführt werden:
- Das Signal „ layoutAboutToBeChanged()“ ausgeben
- Aktualisieren Sie die internen Daten, die die Struktur des Modells darstellen.
- Aktualisieren Sie persistente Indizes mit „ changePersistentIndexList()“.
- Das Signal ` layoutChanged()` ausgeben.
Diese Abfolge kann für jede strukturelle Aktualisierung anstelle der übergeordneten und bequemeren geschützten Methoden verwendet werden. Wenn beispielsweise aus einem Modell mit zwei Millionen Zeilen alle ungeraden Zeilen entfernt werden sollen, entspricht dies 1 Million nicht zusammenhängenden Bereichen mit jeweils einem Element. Man könnte zwar „beginRemoveRows“ und „endRemoveRows“ 1 Million Mal aufrufen, doch das wäre offensichtlich ineffizient. Stattdessen kann dies als eine einzige Layoutänderung signalisiert werden, die alle erforderlichen persistenten Indizes auf einmal aktualisiert.
Lazy-Bevölkerung von Modelldaten
Die verzögerte Befüllung von Modelldaten ermöglicht es, Anfragen nach Informationen über das Modell so lange aufzuschieben, bis sie von den Ansichten tatsächlich benötigt werden.
Manche Modelle müssen Daten aus entfernten Quellen abrufen oder zeitaufwändige Operationen durchführen, um Informationen über die Organisation der Daten zu erhalten. Da Ansichten in der Regel so viele Informationen wie möglich anfordern, um Modelldaten korrekt darzustellen, kann es sinnvoll sein, die Menge der an sie zurückgegebenen Informationen zu begrenzen, um unnötige Folgeanfragen nach Daten zu reduzieren.
In hierarchischen Modellen, in denen das Ermitteln der Anzahl der untergeordneten Elemente eines bestimmten Elements eine ressourcenintensive Operation ist, ist es sinnvoll sicherzustellen, dass die Implementierung von ` rowCount()` des Modells nur bei Bedarf aufgerufen wird. In solchen Fällen kann die Funktion ` hasChildren()` neu implementiert werden, um Ansichten eine ressourcenschonende Möglichkeit zu bieten, das Vorhandensein von untergeordneten Elementen zu prüfen und im Fall von ` QTreeView` die entsprechende Dekoration für das übergeordnete Element zu zeichnen.
Unabhängig davon, ob die Neuimplementierung von ` hasChildren()` ` true ` oder ` false` zurückgibt, ist es für die Ansicht möglicherweise nicht erforderlich, ` rowCount()` aufzurufen, um herauszufinden, wie viele untergeordnete Elemente vorhanden sind. Beispielsweise muss ` QTreeView ` nicht wissen, wie viele untergeordnete Elemente vorhanden sind, wenn das übergeordnete Element nicht erweitert wurde, um diese anzuzeigen.
Wenn bekannt ist, dass viele Elemente untergeordnete Elemente haben werden, ist es manchmal sinnvoll, ` hasChildren()` neu zu implementieren, sodass bedingungslos ` true ` zurückgegeben wird. Dies stellt sicher, dass jedes Element später auf untergeordnete Elemente überprüft werden kann, während die anfängliche Befüllung der Modelldaten so schnell wie möglich erfolgt. Der einzige Nachteil besteht darin, dass Elemente ohne untergeordnete Elemente in einigen Ansichten möglicherweise falsch dargestellt werden, bis der Benutzer versucht, die nicht vorhandenen untergeordneten Elemente anzuzeigen.
Navigation und Erstellung von Modellindizes
Hierarchische Modelle müssen Funktionen bereitstellen, die Ansichten aufrufen können, um durch die von ihnen bereitgestellten baumartigen Strukturen zu navigieren und Modellindizes für Elemente abzurufen.
Eltern und untergeordnete Elemente
Da die den Ansichten zur Verfügung gestellte Struktur durch die zugrunde liegende Datenstruktur bestimmt wird, obliegt es jeder Modellunterklasse, eigene Modellindizes zu erstellen, indem sie Implementierungen der folgenden Funktionen bereitstellt:
| index() | Bei Vorliegen eines Modellindexes für ein übergeordnetes Element ermöglicht diese Funktion Ansichten und Delegaten den Zugriff auf die untergeordneten Elemente dieses Elements. Kann kein gültiges untergeordnetes Element – entsprechend der angegebenen Zeile, Spalte und dem übergeordneten Modellindex – gefunden werden, muss die Funktion `QModelIndex()` zurückgeben, was einen ungültigen Modellindex darstellt. |
| parent() | Liefert einen Modellindex, der dem übergeordneten Element eines beliebigen untergeordneten Elements entspricht. Entspricht der angegebene Modellindex einem Element der obersten Ebene im Modell oder gibt es kein gültiges übergeordnetes Element im Modell, muss die Funktion einen ungültigen Modellindex zurückgeben, der mit dem leeren Konstruktor QModelIndex() erstellt wurde. |
Beide oben genannten Funktionen nutzen die Factory-Funktion ` createIndex()` zur Generierung von Indizes, die von anderen Komponenten verwendet werden können. Es ist üblich, dass Modelle dieser Funktion eine eindeutige Kennung übergeben, um sicherzustellen, dass der Modellindex später wieder mit dem entsprechenden Element verknüpft werden kann.
Unterstützung für Drag & Drop und MIME-Typ-Verarbeitung
Die Modell-/Ansichtsklassen unterstützen Drag-and-Drop-Vorgänge und bieten ein Standardverhalten, das für viele Anwendungen ausreichend ist. Es ist jedoch auch möglich, die Art und Weise anzupassen, wie Elemente während Drag-and-Drop-Vorgängen kodiert werden, ob sie standardmäßig kopiert oder verschoben werden und wie sie in bestehende Modelle eingefügt werden.
Darüber hinaus implementieren die Convenience-View-Klassen ein spezielles Verhalten, das den Erwartungen bestehender Entwickler weitgehend entsprechen sollte. Der Abschnitt „Convenience-Views“ bietet einen Überblick über dieses Verhalten.
MIME-Daten
Standardmäßig verwenden die integrierten Modelle und Ansichten einen internen MIME-Typ (application/x-qabstractitemmodeldatalist), um Informationen über Modellindizes weiterzugeben. Dieser spezifiziert Daten für eine Liste von Elementen, die die Zeilen- und Spaltennummern jedes Elements sowie Informationen über die Rollen enthalten, die jedes Element unterstützt.
Daten, die mit diesem MIME-Typ kodiert sind, können durch Aufruf von ` QAbstractItemModel::mimeData()` mit einem ` QModelIndexList ` abgerufen werden, der die zu serialisierenden Elemente enthält.
Bei der Implementierung der Drag-and-Drop-Unterstützung in einem benutzerdefinierten Modell ist es möglich, Datenelemente in speziellen Formaten zu exportieren, indem die folgende Funktion neu implementiert wird:
| mimeData() | Diese Funktion kann neu implementiert werden, um Daten in anderen Formaten als dem standardmäßigen internen MIME-Typ von ` application/x-qabstractitemmodeldatalist ` zurückzugeben.Unterklassen können das Standardobjekt ` QMimeData ` von der Basisklasse abrufen und diesem Daten in zusätzlichen Formaten hinzufügen. |
Für viele Modelle ist es sinnvoll, den Inhalt von Elementen in gängigen Formaten bereitzustellen, die durch MIME-Typen wie text/plain und image/png dargestellt werden. Beachten Sie, dass Bilder, Farben und HTML-Dokumente mit den Funktionen QMimeData::setImageData(), QMimeData::setColorData() und QMimeData::setHtml() problemlos zu einem QMimeData -Objekt hinzugefügt werden können.
Akzeptieren von abgelegten Daten
Wenn ein Drag-and-Drop-Vorgang über einer Ansicht ausgeführt wird, wird das zugrunde liegende Modell abgefragt, um festzustellen, welche Arten von Vorgängen es unterstützt und welche MIME-Typen es akzeptieren kann. Diese Informationen werden von den Funktionen ` QAbstractItemModel::supportedDropActions()` und ` QAbstractItemModel::mimeTypes()` bereitgestellt. Modelle, die die von ` QAbstractItemModel ` bereitgestellten Implementierungen nicht überschreiben, unterstützen Kopiervorgänge und den standardmäßigen internen MIME-Typ für Elemente.
Wenn serialisierte Elementdaten auf eine Ansicht gezogen werden, werden die Daten mithilfe der Implementierung von ` QAbstractItemModel::dropMimeData()` in das aktuelle Modell eingefügt. Die Standardimplementierung dieser Funktion überschreibt niemals Daten im Modell; stattdessen versucht sie, die Datenelemente entweder als gleichrangige Elemente eines Elements oder als untergeordnete Elemente dieses Elements einzufügen.
Um die Vorteile der Standardimplementierung von ` QAbstractItemModel` für den integrierten MIME-Typ nutzen zu können, müssen neue Modelle Neuimplementierungen der folgenden Funktionen bereitstellen:
| insertRows() | Diese Funktionen ermöglichen es dem Modell, neue Daten automatisch unter Verwendung der bestehenden Implementierung von ` QAbstractItemModel::dropMimeData()` einzufügen. |
| insertColumns() | |
| setData() | Ermöglicht das Befüllen der neuen Zeilen und Spalten mit Elementen. |
| setItemData() | Diese Funktion bietet eine effizientere Unterstützung beim Einfügen neuer Elemente. |
Um andere Datenformate zu akzeptieren, müssen diese Funktionen neu implementiert werden:
| supportedDropActions() | Wird verwendet, um eine Kombination aus ` drop actions` zurückzugeben, die angibt, welche Arten von Drag-and-Drop-Operationen das Modell akzeptiert. |
| mimeTypes() | Dient zur Rückgabe einer Liste von MIME-Typen, die vom Modell dekodiert und verarbeitet werden können. Im Allgemeinen entsprechen die MIME-Typen, die als Eingabe für das Modell unterstützt werden, denen, die es bei der Kodierung von Daten für die Verwendung durch externe Komponenten verwenden kann. |
| dropMimeData() | Führt die eigentliche Dekodierung der durch Drag-and-Drop-Vorgänge übertragenen Daten durch, bestimmt, an welcher Stelle im Modell sie gesetzt werden, und fügt bei Bedarf neue Zeilen und Spalten ein. Wie diese Funktion in Unterklassen implementiert wird, hängt von den Anforderungen der von jedem Modell bereitgestellten Daten ab. |
Wenn die Implementierung der Funktion ` dropMimeData()` die Dimensionen eines Modells durch Einfügen oder Entfernen von Zeilen oder Spalten verändert oder wenn Datenelemente modifiziert werden, muss darauf geachtet werden, dass alle relevanten Signale ausgegeben werden. Es kann sinnvoll sein, einfach Neimplementierungen anderer Funktionen in der Unterklasse aufzurufen, wie beispielsweise ` setData()`, ` insertRows()` und ` insertColumns()`, um sicherzustellen, dass sich das Modell konsistent verhält.
Um sicherzustellen, dass Drag-Operationen ordnungsgemäß funktionieren, ist es wichtig, die folgenden Funktionen neu zu implementieren, die Daten aus dem Modell entfernen:
- removeRows()
- removeRow()
- removeColumns()
- removeColumn()
Weitere Informationen zum Drag & Drop mit Elementansichten finden Sie unter „Drag & Drop mit Elementansichten verwenden“.
Übersichtsansichten
Die Komfortansichten (QListWidget, QTableWidget und QTreeWidget) überschreiben die Standard-Drag-and-Drop-Funktionalität und bieten ein weniger flexibles, dafür aber natürlicheres Verhalten, das für viele Anwendungen geeignet ist. Da es beispielsweise üblicher ist, Daten in Zellen einer „ QTableWidget “-Ansicht abzulegen und dabei den bestehenden Inhalt durch die übertragenen Daten zu ersetzen, setzt das zugrunde liegende Modell die Daten der Zielelemente und fügt keine neuen Zeilen und Spalten in das Modell ein. Weitere Informationen zum Drag-and-Drop in Komfortansichten finden Sie unter „Verwenden von Drag-and-Drop mit Elementansichten“.
Leistungsoptimierung für große Datenmengen
Die Funktion „ canFetchMore()“ prüft, ob das übergeordnete Element über weitere Daten verfügt, und gibt entsprechend „ true “ oder „false“ zurück. Die Funktion „ fetchMore()“ ruft Daten auf der Grundlage des angegebenen übergeordneten Elements ab. Beide Funktionen lassen sich kombinieren, beispielsweise in einer Datenbankabfrage mit inkrementellen Daten, um eine „ QAbstractItemModel “ zu füllen. Wir implementieren „ canFetchMore()“ neu, um anzugeben, ob weitere Daten abgerufen werden müssen, und „ fetchMore()“, um das Modell nach Bedarf zu füllen.
Ein weiteres Beispiel wären dynamisch gefüllte Baummodelle, bei denen wir ` fetchMore()` neu implementieren, wenn ein Zweig im Baummodell erweitert wird.
Wenn Ihre Neuimplementierung von fetchMore() dem Modell Zeilen hinzufügt, müssen Sie beginInsertRows() und endInsertRows() aufrufen. Außerdem müssen sowohl canFetchMore() als auch fetchMore() neu implementiert werden, da deren Standardimplementierung „false“ zurückgibt und nichts tut.
Die Modell-/Ansichtsklassen
Diese Klassen verwenden das Model-View-Entwurfsmuster, bei dem die zugrunde liegenden Daten (im Model) von der Art und Weise getrennt gehalten werden, wie die Daten vom Benutzer dargestellt und bearbeitet werden (in der View).
Dienen zur Anzeige und Bearbeitung von Datenelementen aus einem Modell | |
Die abstrakte Schnittstelle für Element-Modellklassen | |
Die grundlegende Funktionalität für Element-View-Klassen | |
Abstraktes Modell, von dem Unterklassen abgeleitet werden können, um eindimensionale Listenmodelle zu erstellen | |
Basisklasse für Proxy-Elementmodelle, die Sortier-, Filter- oder andere Datenverarbeitungsaufgaben ausführen können | |
Abstraktes Modell, von dem Unterklassen abgeleitet werden können, um Tabellenmodelle zu erstellen | |
Modell-/Ansichts-Implementierung einer Spaltenansicht | |
Verwaltet mehrere Quellmodelle als Proxys und verkettet deren Zeilen | |
Zuordnung zwischen einem Abschnitt eines Datenmodells und Widgets | |
Datenmodell für das lokale Dateisystem | |
Kopfzeile oder Kopfspalte für Elementansichten | |
Vermittelt das Quellmodell unverändert | |
Anzeige- und Bearbeitungsfunktionen für Datenelemente aus einem Modell | |
Ermöglicht die Erstellung von Basisklassen für Element-Editor-Ersteller, ohne QItemEditorCreatorBase zu unterklassen | |
Abstrakte Basisklasse, die bei der Implementierung neuer Element-Editor-Ersteller als Basisklasse verwendet werden muss | |
Widgets zur Bearbeitung von Elementdaten in Ansichten und Delegaten | |
Verwaltet Informationen zu ausgewählten Elementen in einem Modell | |
Verfolgt die ausgewählten Elemente einer Ansicht | |
Verwaltet Informationen über einen Bereich ausgewählter Elemente in einem Modell | |
Listen- oder Symbolansicht eines Modells | |
Elementbasiertes Listen-Widget | |
Element zur Verwendung mit der Elementansichtsklasse „QListWidget“ | |
Wird verwendet, um Daten in einem Datenmodell zu lokalisieren | |
Enthält eine Rolle und die dieser Rolle zugeordneten Daten | |
Umfasst QModelRoleData-Objekte | |
Wird verwendet, um Daten in einem Datenmodell zu lokalisieren | |
Implementiert QAbstractItemModel für jeden C++-Bereich | |
Die Vorlage bietet einen Anpassungspunkt, um zu steuern, wie QRangeModel auf die Rollendaten einzelner Elemente zugreift | |
Die Vorlage bietet einen Anpassungspunkt, um zu steuern, wie QRangeModel Typen darstellt, die als Zeilen verwendet werden | |
QAbstractItemModel-konformer Zugriff auf beliebige C++-Bereiche | |
Stellt einen nicht-konstanten Iterator im STL-Stil über die Spalten einer Modellzeile bereit | |
Stellt einen nicht-konstanten Iterator im STL-Stil über die Spalten einer Modellzeile bereit | |
Stellt einen konstanten Iterator im STL-Stil über die Zeilen eines Modells bereit | |
Referenz-Wrapper um eine konstante Zeile in einem QRangeModel | |
Referenz-Wrapper um ein Element in einem QRangeModel | |
Stellt einen nicht-konstanten Iterator im STL-Stil über die Zeilen eines Modells bereit | |
Referenz-Wrapper um eine Zeile in einem QRangeModel | |
Gemeinsame API für `RowReference` und `ConstRowReference` | |
Unterstützung für das Sortieren und Filtern von Daten, die zwischen einem anderen Modell und einer Ansicht ausgetauscht werden | |
Element zur Verwendung mit der Klasse `QStandardItemModel` | |
Die Möglichkeit, Widgets zu registrieren, ohne eine Unterklasse von `QItemEditorCreatorBase` erstellen zu müssen | |
Generisches Modell zum Speichern benutzerdefinierter Daten | |
Modell, das Zeichenfolgen an Ansichten übergibt | |
Anzeige- und Bearbeitungsfunktionen für Datenelemente aus einem Modell | |
Standardimplementierung von Modell und Ansicht für eine Tabellenansicht | |
Elementbasierte Tabellenansicht mit einem Standardmodell | |
Element zur Verwendung mit der Klasse `QTableWidget` | |
Möglichkeit zur Interaktion mit der Auswahl in einem Modell ohne Verwendung von Modellindizes und eines Auswahlmodells | |
Standard-Modell-/Ansichts-Implementierung einer Baumansicht | |
Baumansicht, die ein vordefiniertes Baummodell verwendet | |
Element zur Verwendung mit der QTreeWidget-Hilfsklasse | |
Möglichkeit, die Elemente einer QTreeWidget-Instanz zu durchlaufen |
Verwandte Beispiele
© 2026 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.



