Modell-/Ansichts-Programmierung

Einführung in die Model/View-Programmierung

Qt enthält eine Reihe von Item-View-Klassen, die eine Model/View-Architektur verwenden, um die Beziehung zwischen Daten und der Art und Weise, wie sie dem Benutzer präsentiert werden, zu verwalten. Die Trennung der Funktionalität, die durch diese Architektur eingeführt wird, gibt Entwicklern eine größere Flexibilität bei der Anpassung der Darstellung von Elementen und bietet eine Standardmodellschnittstelle, die es ermöglicht, eine breite Palette von Datenquellen mit bestehenden Elementansichten zu verwenden. In diesem Dokument geben wir eine kurze Einführung in das Modell/View-Paradigma, skizzieren die beteiligten Konzepte und beschreiben die Architektur des Item-View-Systems. Jede der Komponenten der Architektur wird erläutert und es werden Beispiele gegeben, die zeigen, wie die bereitgestellten Klassen verwendet werden können.

Die Model/View-Architektur

Model-View-Controller (MVC) ist ein aus Smalltalk stammendes Entwurfsmuster, das häufig bei der Erstellung von Benutzeroberflächen verwendet wird. In Design Patterns, Gamma et al. schreiben:

MVC besteht aus drei Arten von Objekten. Das Modell ist das Anwendungsobjekt, die Ansicht ist seine Bildschirmdarstellung, und der Controller definiert die Art und Weise, wie die Benutzeroberfläche auf Benutzereingaben reagiert. Vor MVC neigten die Entwürfe von Benutzeroberflächen dazu, diese Objekte in einen Topf zu werfen. MVC entkoppelt sie, um die Flexibilität und Wiederverwendung zu erhöhen.

Wenn die View- und die Controller-Objekte kombiniert werden, entsteht die Model/View-Architektur. Diese trennt immer noch die Art und Weise, wie Daten gespeichert werden, von der Art und Weise, wie sie dem Benutzer präsentiert werden, bietet aber einen einfacheren Rahmen, der auf denselben Prinzipien beruht. Diese Trennung macht es möglich, dieselben Daten in verschiedenen Ansichten darzustellen und neue Arten von Ansichten zu implementieren, ohne die zugrunde liegenden Datenstrukturen zu ändern. Um eine flexible Handhabung von Benutzereingaben zu ermöglichen, führen wir das Konzept des Delegaten ein. Der Vorteil eines Delegaten in diesem Framework besteht darin, dass die Art und Weise, wie Datenelemente dargestellt und bearbeitet werden, angepasst werden kann.

Die Modell/Ansicht-Architektur

Das Modell kommuniziert mit einer Datenquelle und bietet eine Schnittstelle für die anderen Komponenten der Architektur. Die Art der Kommunikation hängt von der Art der Datenquelle und der Art der Implementierung des Modells ab.

Die Ansicht erhält Modellindizes vom Modell; dies sind Verweise auf Datenelemente. Durch die Übergabe von Modellindizes an das Modell kann die Ansicht Datenelemente aus der Datenquelle abrufen.

In Standardansichten rendert ein Delegat die Datenelemente. Wenn ein Element bearbeitet wird, kommuniziert der Delegat direkt mit dem Modell unter Verwendung von Modellindizes.

Im Allgemeinen können die Model/View-Klassen in die drei oben beschriebenen Gruppen unterteilt werden: Models, Views und Delegates. 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 die gesamte von anderen Komponenten erwartete Funktionalität zur Verfügung zu stellen; dies ermöglicht auch die Entwicklung spezialisierter Komponenten.

Modelle, Ansichten und Delegierte kommunizieren miteinander über Signale und Slots:

  • Signale vom Modell informieren die Ansicht über Änderungen der Daten in der Datenquelle.
  • Signale von der Ansicht liefern Informationen über die Interaktion des Benutzers mit den angezeigten Elementen.
  • Signale vom Delegaten werden während der Bearbeitung verwendet, um das Modell und die Ansicht über den Zustand des Editors zu informieren.

Modelle

Alle Elementmodelle basieren auf der Klasse QAbstractItemModel. Diese Klasse definiert eine Schnittstelle, die von Ansichten und Delegaten verwendet wird, um auf Daten zuzugreifen. Die Daten selbst müssen nicht im Modell gespeichert werden; sie können in einer Datenstruktur oder einem Repository gespeichert werden, das von einer separaten Klasse, einer Datei, einer Datenbank oder einer anderen Anwendungskomponente bereitgestellt wird.

Die grundlegenden Konzepte rund um Modelle werden im Abschnitt über Modellklassen vorgestellt.

QAbstractItemModel bietet eine Schnittstelle zu Daten, die flexibel genug ist, um Ansichten zu handhaben, die Daten in Form von Tabellen, Listen und Bäumen darstellen. Bei der Implementierung neuer Modelle für listen- und tabellenartige Datenstrukturen sind jedoch die Klassen QAbstractListModel und QAbstractTableModel bessere Ausgangspunkte, da sie geeignete Standardimplementierungen gängiger Funktionen bieten. Jede dieser Klassen kann in Unterklassen unterteilt werden, um Modelle zu erstellen, die spezielle Arten von Listen und Tabellen unterstützen.

Der Prozess der Subklassifizierung von Modellen wird im Abschnitt über das Erstellen neuer Modelle besprochen.

Qt bietet einige vorgefertigte Modelle, die für die Verarbeitung von Daten verwendet werden können:

Wenn diese Standardmodelle Ihren Anforderungen nicht genügen, können Sie QAbstractItemModel, QAbstractListModel oder QAbstractTableModel unterklassifizieren, um Ihre eigenen benutzerdefinierten Modelle zu erstellen.

Ansichten

Vollständige Implementierungen werden für verschiedene Arten von Ansichten bereitgestellt: QListView zeigt eine Liste von Elementen an, QTableView zeigt Daten aus einem Modell in einer Tabelle an, und QTreeView zeigt Modellelemente von Daten in einer hierarchischen Liste an. Jede dieser Klassen basiert auf der abstrakten Basisklasse QAbstractItemView. Obwohl es sich bei diesen Klassen um gebrauchsfertige Implementierungen handelt, können sie auch unterklassifiziert werden, um benutzerdefinierte Ansichten bereitzustellen.

Die verfügbaren Ansichten werden im Abschnitt über Ansichtsklassen untersucht.

Delegierte

QAbstractItemDelegate ist die abstrakte Basisklasse für Delegates im Model/View Framework. Die Standard-Implementierung von Delegates wird von QStyledItemDelegate bereitgestellt, und diese wird von den Standardansichten von Qt als Standard-Delegate verwendet. QStyledItemDelegate und QItemDelegate sind jedoch unabhängige Alternativen zum Malen und Bereitstellen von Editoren für Elemente in Ansichten. Der Unterschied zwischen ihnen ist, dass QStyledItemDelegate den aktuellen Stil verwendet, um seine Elemente zu zeichnen. Wir empfehlen daher, QStyledItemDelegate als Basisklasse zu verwenden, wenn Sie benutzerdefinierte Delegates implementieren oder mit Qt-Stylesheets arbeiten.

Delegates werden im Abschnitt über Delegate Classes beschrieben.

Sortieren

Es gibt zwei Möglichkeiten, die Sortierung in der Model/View-Architektur anzugehen; welcher Ansatz gewählt wird, hängt von dem zugrunde liegenden Modell ab.

Wenn Ihr Modell sortierbar ist, d.h. wenn es die Funktion QAbstractItemModel::sort() nachbildet, bieten sowohl QTableView als auch QTreeView eine API, mit der Sie Ihre Modelldaten programmatisch sortieren können. Außerdem können Sie die interaktive Sortierung aktivieren (d. h. den Benutzern die Möglichkeit geben, die Daten durch Anklicken der Kopfzeilen der Ansicht zu sortieren), indem Sie das Signal QHeaderView::sortIndicatorChanged() mit dem Slot QTableView::sortByColumn() bzw. QTreeView::sortByColumn() verbinden.

Wenn Ihr Modell nicht über die erforderliche Schnittstelle verfügt oder Sie eine Listenansicht zur Darstellung Ihrer Daten verwenden möchten, können Sie alternativ ein Proxy-Modell verwenden, um die Struktur Ihres Modells umzuwandeln, bevor Sie die Daten in der Ansicht darstellen. Dies wird im Abschnitt über Proxy-Modelle ausführlich behandelt.

Convenience-Klassen

Eine Reihe von Convenience-Klassen sind von den Standard-View-Klassen abgeleitet, um Anwendungen zu unterstützen, die auf die item-basierten View- und Table-Klassen von Qt angewiesen sind. 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 View-Klassen und können nicht für beliebige Modelle verwendet werden. Wir empfehlen, dass Sie einen Model/View-Ansatz für die Handhabung von Daten in Item-Views verwenden, es sei denn, Sie benötigen unbedingt eine Reihe von Item-basierten Klassen.

Wenn Sie die Vorteile des Model/View-Ansatzes nutzen möchten, aber dennoch eine item-basierte Schnittstelle verwenden wollen, sollten Sie View-Klassen wie QListView, QTableView und QTreeView mit QStandardItemModel verwenden.

Verwendung von Modellen und Ansichten

Die folgenden Abschnitte erklären, wie das Model/View-Muster in Qt verwendet wird. Jeder Abschnitt enthält ein Beispiel und wird gefolgt von einem Abschnitt, der zeigt, wie man neue Komponenten erstellt.

Zwei in Qt enthaltene Modelle

Zwei der von Qt bereitgestellten Standardmodelle sind QStandardItemModel und QFileSystemModel. QStandardItemModel ist ein Mehrzweckmodell, das zur Darstellung verschiedener Datenstrukturen verwendet werden kann, die von Listen-, Tabellen- und Baumansichten benötigt werden. Dieses Modell enthält auch die Datenelemente. QFileSystemModel ist ein Modell, das Informationen über den Inhalt eines Verzeichnisses verwaltet. Folglich enthält es selbst keine Daten, sondern stellt lediglich Dateien und Verzeichnisse im lokalen Ablagesystem dar.

QFileSystemModel bietet ein gebrauchsfertiges Modell, mit dem man experimentieren kann, und kann leicht konfiguriert werden, um vorhandene Daten zu verwenden. Anhand dieses Modells können wir zeigen, wie man ein Modell für die Verwendung mit vorgefertigten Ansichten einrichtet und wie man Daten mithilfe von Modellindizes manipuliert.

Verwendung von Ansichten mit einem vorhandenen Modell

Die Klassen QListView und QTreeView sind die am besten geeigneten Ansichten für die Verwendung mit QFileSystemModel. Das unten stehende Beispiel zeigt den Inhalt eines Verzeichnisses in einer Baumansicht neben denselben Informationen in einer Listenansicht an. Die Ansichten teilen sich die Auswahl des Benutzers, so dass die ausgewählten Elemente in beiden Ansichten hervorgehoben werden.

Wir richten eine QFileSystemModel ein, so dass sie einsatzbereit ist, und erstellen einige Ansichten, um den Inhalt eines Verzeichnisses anzuzeigen. Dies zeigt die einfachste Art, ein Modell zu verwenden. Der Aufbau und die Verwendung 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 wird so eingerichtet, dass es Daten aus einem bestimmten Dateisystem verwendet. Der Aufruf von setRootPath() teilt dem Modell mit, welches Laufwerk im Dateisystem 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 die gleiche Weise wie andere Widgets erstellt. Um eine Ansicht so einzurichten, dass sie die Elemente im Modell anzeigt, müssen Sie lediglich die Funktion setModel() mit dem Verzeichnismodell als Argument aufrufen. Wir filtern die vom Modell gelieferten Daten, indem wir die Funktion setRootIndex() für jede Ansicht aufrufen und einen geeigneten Modellindex aus dem Dateisystemmodell für das aktuelle Verzeichnis übergeben.

Die Funktion index(), die in diesem Fall verwendet wird, ist einzigartig für QFileSystemModel; wir übergeben ihr ein Verzeichnis und sie gibt einen Modellindex zurück. Modell-Indizes werden in Modell-Klassen besprochen.

Der Rest der Funktion zeigt lediglich die Ansichten innerhalb eines Splitter-Widgets 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 vergessen zu erwähnen, wie die Auswahl von Elementen zu behandeln ist. Dieses Thema wird in dem Abschnitt über die Behandlung von Selektionen in Elementansichten ausführlicher behandelt.

Modell-Klassen

Bevor Sie untersuchen, wie Selektionen gehandhabt werden, sollten Sie sich mit den Konzepten befassen, die im Model/View-Framework verwendet werden.

Grundlegende Konzepte

In der Model/View-Architektur bietet das Model eine Standardschnittstelle, ü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 eine hierarchische Struktur dar, die Tabellen von Elementen enthält. Ansichten verwenden diese Konvention, um auf Datenelemente im Modell zuzugreifen, sind aber in der Art und Weise, wie sie diese Informationen dem Benutzer präsentieren, nicht eingeschränkt.

Modelle benachrichtigen auch alle angehängten Ansichten über Änderungen an Daten durch den Mechanismus der Signale und Slots.

In diesem Abschnitt werden einige grundlegende Konzepte beschrieben, die für den Zugriff auf Datenelemente durch andere Komponenten über eine Modellklasse von zentraler Bedeutung sind. Weitergehende Konzepte werden in späteren Abschnitten behandelt.

Modell-Indizes

Um sicherzustellen, dass die Darstellung der Daten von der Art und Weise, wie auf sie zugegriffen wird, getrennt bleibt, wird das Konzept eines Modellindexes eingeführt. Jede Information, die über ein Modell abgerufen werden kann, wird durch einen Modellindex dargestellt. Ansichten und Delegierte verwenden diese Indizes, um Datenelemente zur Anzeige anzufordern.

Infolgedessen muss nur das Modell wissen, wie es Daten erhalten kann, und die Art der vom Modell verwalteten Daten kann recht allgemein definiert werden. Modellindizes enthalten einen Zeiger auf das Modell, das sie erstellt hat, und dies verhindert Verwirrung, wenn man mit mehr als einem Modell arbeitet.

QAbstractItemModel *model = index.model();

Modellindizes bieten temporäre Verweise auf Informationen und können zum Abrufen oder Ändern von Daten über das Modell verwendet werden. Da Modelle ihre internen Strukturen von Zeit zu Zeit reorganisieren können, können Modellindizes ungültig werden und sollten nicht gespeichert werden. Wenn ein langfristiger Verweis auf eine Information benötigt wird, muss ein persistenter Modellindex erstellt werden. Dieser bietet einen Verweis auf die Informationen, die das Modell aktuell hält. Temporäre Modellindizes werden von der Klasse QModelIndex zur Verfügung gestellt, und persistente Modellindizes werden von der Klasse QPersistentModelIndex bereitgestellt.

Um einen Modellindex zu erhalten, der einem Datenelement entspricht, müssen drei Eigenschaften für das Modell angegeben werden: eine Zeilennummer, eine Spaltennummer und der Modellindex eines übergeordneten Elements. In den folgenden Abschnitten werden diese Eigenschaften im Detail beschrieben und erläutert.

Zeilen und Spalten

In seiner einfachsten Form kann auf ein Modell als einfache Tabelle zugegriffen werden, in der die Elemente durch ihre Zeilen- und Spaltennummern gekennzeichnet sind. Das bedeutet nicht, dass die zugrundeliegenden Daten in einer Array-Struktur gespeichert sind; die Verwendung von Zeilen- und Spaltennummern ist lediglich eine Konvention, die es den Komponenten ermöglicht, miteinander zu kommunizieren. Wir können Informationen über jedes beliebige Element abrufen, indem wir dem Modell seine Zeilen- und Spaltennummern mitteilen, und wir erhalten einen Index, der das Element darstellt:

QModelIndex index = model->index(row, column, ...);

Modelle, die Schnittstellen zu einfachen, einstufigen Datenstrukturen wie Listen und Tabellen bereitstellen, benötigen keine weiteren Informationen, aber, wie der obige Code zeigt, müssen wir mehr Informationen bereitstellen, wenn wir einen Modellindex erhalten.

Zeilen und Spalten

Das Diagramm zeigt eine Darstellung eines einfachen Tabellenmodells, in dem jedes Element durch ein Paar von Zeilen- und Spaltennummern lokalisiert wird. Wir erhalten einen Modellindex, der sich auf ein Datenelement bezieht, indem wir die entsprechenden Zeilen- und Spaltennummern an das Modell übergeben.

QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexB = model->index(1, 1, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());

Elemente der obersten Ebene in einem Modell werden immer durch die Angabe von QModelIndex() als übergeordnetes Element referenziert. Dies wird im nächsten Abschnitt behandelt.

Eltern von Elementen

Die tabellenähnliche Schnittstelle zu Elementdaten, die von Modellen bereitgestellt wird, ist ideal für die Verwendung von Daten in einer Tabellen- oder Listenansicht; das Zeilen- und Spaltenzahlensystem entspricht genau der Art und Weise, wie die Ansichten Elemente anzeigen. Bei Strukturen wie Baumansichten muss das Modell jedoch eine flexiblere Schnittstelle zu den darin enthaltenen Elementen bereitstellen. Infolgedessen kann jedes Element auch das übergeordnete Element einer anderen Tabelle mit Elementen sein, ähnlich wie ein Element der obersten Ebene in einer Baumansicht eine andere Liste von Elementen enthalten kann.

Wenn wir einen Index für ein Modellelement anfordern, müssen wir einige Informationen über das übergeordnete Element des Elements bereitstellen. Außerhalb des Modells ist die einzige Möglichkeit, auf ein Element zu verweisen, ein Modellindex, so dass auch ein übergeordneter Modellindex angegeben werden muss:

QModelIndex index = model->index(row, column, parent);
Übergeordnete Elemente, 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" sind als Geschwister der obersten Ebene im Modell dargestellt:

QModelIndex indexA = model->index(0, 0, QModelIndex());
QModelIndex indexC = model->index(2, 1, QModelIndex());

Element "A" hat eine Reihe von Kindern. Ein Modellindex für Element "B" wird mit dem folgenden Code ermittelt:

QModelIndex indexB = model->index(1, 0, indexA);

Elementrollen

Elemente in einem Modell können verschiedene Rollen für andere Komponenten übernehmen, so dass verschiedene Arten von Daten für verschiedene Situationen geliefert werden können. Zum Beispiel wird Qt::DisplayRole verwendet, um auf eine Zeichenkette zuzugreifen, die als Text in einer Ansicht angezeigt werden kann. Normalerweise enthalten Elemente Daten für eine Reihe von verschiedenen Rollen, und die Standardrollen werden von Qt::ItemDataRole definiert.

Wir können das Modell nach den Daten des Elements fragen, 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);
Element-Rollen

Die Rolle gibt dem Modell an, auf welche Art von Daten es sich bezieht. Ansichten können die Rollen auf unterschiedliche Weise anzeigen, daher ist es wichtig, für jede Rolle geeignete Informationen zu liefern.

Im Abschnitt Erstellen neuer Modelle werden einige spezifische Verwendungen von Rollen ausführlicher behandelt.

Die meisten gängigen Verwendungszwecke für Artikeldaten werden durch die in Qt::ItemDataRole definierten Standardrollen abgedeckt. Durch die Bereitstellung geeigneter Artikeldaten für jede Rolle können Modelle den Ansichten und Delegierten Hinweise darauf geben, wie die 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 geben Sichten und Delegierten Informationen über den Standort von Elementen, die von Modellen bereitgestellt werden, und zwar auf eine Weise, die von den zugrunde liegenden Datenstrukturen unabhängig ist.
  • Auf Elemente wird durch ihre Zeilen- und Spaltennummern und durch den Modellindex ihrer übergeordneten Elemente verwiesen.
  • Modellindizes werden von Modellen auf Anfrage von anderen Komponenten, wie z.B. Views und Delegates, erstellt.
  • Wenn ein gültiger Modellindex für das übergeordnete Element angegeben wird, wenn ein Index mit index() angefordert wird, 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.
  • Wenn ein ungültiger Modellindex für das übergeordnete Element angegeben wird, wenn ein Index mit index() angefordert wird, bezieht sich der zurückgegebene Index auf ein Element der obersten Ebene im Modell.
  • Die Website role unterscheidet zwischen den verschiedenen Arten von Daten, die mit einem Element verbunden sind.

Verwendung von Modellindizes

Um zu demonstrieren, wie Daten aus einem Modell unter Verwendung von Modellindizes abgerufen werden können, richten wir eine QFileSystemModel ohne Ansicht ein und zeigen die Namen von Dateien und Verzeichnissen in einem Widget an. Obwohl dies keine normale Art der Verwendung eines Modells darstellt, zeigt es die Konventionen, die von Modellen im Umgang mit Modellindizes verwendet werden.

QFileSystemModel Das Laden erfolgt asynchron, um die Nutzung von Systemressourcen zu minimieren. Das müssen wir beim Umgang mit diesem Modell berücksichtigen.

Wir konstruieren ein Dateisystemmodell auf die 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 beginnen wir mit der Einrichtung eines Standardmodells QFileSystemModel. Wir verbinden sein Signal directoryLoaded(QString) mit einem Lambda, in dem wir einen übergeordneten Index für das Verzeichnis mithilfe einer spezifischen Implementierung von index() erhalten, die von diesem Modell bereitgestellt wird.

In dem Lambda bestimmen wir die Anzahl der Zeilen im Modell mit Hilfe der Funktion rowCount().

Der Einfachheit halber interessieren wir uns nur für die Elemente in der ersten Spalte des Modells. Wir untersuchen jede Zeile der Reihe nach, wobei wir einen Modellindex für das erste Element in jeder Zeile erhalten, und lesen die für dieses Element im Modell gespeicherten Daten.

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

            QString text = model->data(index, Qt::DisplayRole).toString();

        }

Schließlich legen wir den Wurzelpfad von QFileSystemModel fest, damit das Laden der Daten beginnt und das Lambda ausgelöst wird.

Das obige Beispiel veranschaulicht die grundlegenden Prinzipien, die zum Abrufen von Daten aus einem Modell verwendet werden:

  • Die Dimensionen eines Modells können mit rowCount() und columnCount() ermittelt werden. Für diese Funktionen muss im Allgemeinen ein übergeordneter Modellindex angegeben werden.
  • Modellindizes werden für den Zugriff auf Elemente im Modell verwendet. Die Zeile, die Spalte und der Index des übergeordneten Modells werden benötigt, um das Element zu spezifizieren.
  • Um auf Elemente der obersten Ebene in einem Modell zuzugreifen, geben Sie einen Null-Modellindex als übergeordneten Index mit QModelIndex() an.
  • Elemente enthalten Daten für verschiedene Rollen. Um die Daten für eine bestimmte Rolle zu erhalten, müssen sowohl der Modellindex als auch die Rolle an das Modell übergeben werden.

Weitere Lektüre

Neue Modelle können durch die Implementierung der von QAbstractItemModel bereitgestellten Standardschnittstelle erstellt werden. Im Abschnitt " Erstellen neuer Modelle" wird dies anhand eines praktischen, gebrauchsfertigen Modells für die Speicherung von Listen von Zeichenketten demonstriert.

Ansichtsklassen

Konzepte

In der Modell/View-Architektur bezieht der View Daten aus dem Modell und präsentiert sie dem Benutzer. Die Art und Weise, wie die Daten präsentiert werden, muss nicht der vom Modell bereitgestellten Darstellung der Daten entsprechen und kann sich von der zugrunde liegenden Datenstruktur, die zur Speicherung der Datenelemente verwendet wird, völlig unterscheiden.

Die Trennung von Inhalt und Darstellung wird durch die Verwendung einer von QAbstractItemModel bereitgestellten Standardmodellschnittstelle, einer von QAbstractItemView bereitgestellten Standardansichtsschnittstelle und der Verwendung von Modellindizes, die Datenelemente auf allgemeine Weise darstellen, erreicht. Ansichten verwalten in der Regel das Gesamtlayout der aus Modellen gewonnenen Daten. Sie können einzelne Datenelemente selbst rendern oder Delegierte verwenden, um sowohl Rendering- als auch Bearbeitungsfunktionen zu handhaben.

Neben der Darstellung von Daten übernehmen die Ansichten auch die Navigation zwischen Elementen und einige Aspekte der Elementauswahl. Die Ansichten implementieren auch grundlegende Funktionen der Benutzeroberfläche, wie Kontextmenüs und Drag & Drop. Eine Ansicht kann Standardbearbeitungsfunktionen für Elemente bereitstellen oder mit einem Delegaten zusammenarbeiten, um einen benutzerdefinierten Editor bereitzustellen.

Eine Ansicht kann ohne ein Modell erstellt werden, aber ein Modell muss bereitgestellt werden, bevor sie nützliche Informationen anzeigen kann. Ansichten verfolgen die Elemente, die der Benutzer ausgewählt hat, durch die Verwendung von Auswahlen, die für jede Ansicht separat oder für mehrere Ansichten gemeinsam verwaltet werden können.

Einige Ansichten, wie z. B. QTableView und QTreeView, zeigen sowohl Kopfzeilen als auch Elemente an. Diese werden ebenfalls durch eine View-Klasse implementiert, QHeaderView. Kopfzeilen greifen in der Regel auf das gleiche Modell zu wie die Ansicht, die sie enthält. Sie rufen Daten aus dem Modell mit Hilfe der Funktion QAbstractItemModel::headerData() ab und zeigen normalerweise Kopfzeileninformationen in Form eines Labels an. Neue Header können von der Klasse QHeaderView subclassed werden, um speziellere Labels für Views bereitzustellen.

Verwendung einer bestehenden Ansicht

Qt bietet drei gebrauchsfertige View-Klassen, die Daten aus Modellen auf eine Art und Weise darstellen, die den meisten Benutzern vertraut ist. QListView kann Elemente aus einem Modell als einfache Liste oder in Form einer klassischen Icon-Ansicht darstellen. QTreeView zeigt Elemente aus einem Modell als Hierarchie von Listen an, wodurch tief verschachtelte Strukturen auf kompakte Weise dargestellt werden können. QTableView stellt Elemente aus einem Modell in Form einer Tabelle dar, ähnlich dem Layout einer Tabellenkalkulationsanwendung.

Das Standardverhalten der oben gezeigten Standardansichten sollte für die meisten Anwendungen ausreichend sein. Sie bieten grundlegende Bearbeitungsmöglichkeiten und können an die Bedürfnisse speziellerer Benutzeroberflächen angepasst werden.

Verwendung eines Modells

Wir nehmen das Modell der Zeichenkettenliste, das wir als Beispielmodell erstellt haben, richten es mit einigen Daten ein und konstruieren eine Ansicht, um den Inhalt des Modells anzuzeigen. Dies alles kann innerhalb einer einzigen Funktion durchgeführt werden:

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 StringListModel als QAbstractItemModel deklariert ist. Dadurch können wir die abstrakte Schnittstelle zum Modell verwenden und sicherstellen, 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 konstruieren die Ansicht und richten das Modell mit den folgenden Codezeilen ein:

QListView *view = new QListView;
view->setModel(model);

Die Ansicht wird auf die übliche Weise angezeigt:

    view->show();
    return app.exec();
}

Die Ansicht stellt den Inhalt eines Modells dar und greift auf die Daten über die Schnittstelle des Modells zu. Wenn der Benutzer versucht, ein Element zu bearbeiten, verwendet die Ansicht einen Standarddelegierten, um ein Editor-Widget bereitzustellen.

Das obige Bild zeigt, wie ein QListView die Daten im String-Listenmodell darstellt. Da das Modell bearbeitbar ist, ermöglicht die Ansicht automatisch die Bearbeitung jedes Elements in der Liste mithilfe des Standarddelegaten.

Mehrere Ansichten eines Modells verwenden

Die Bereitstellung mehrerer Ansichten auf ein und dasselbe Modell ist einfach eine Frage der Einstellung des gleichen Modells für jede Ansicht. Im folgenden Code erstellen wir zwei Tabellenansichten, die jeweils das gleiche 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/View-Architektur bedeutet, dass Änderungen am Modell an alle angeschlossenen Views weitergegeben werden können, so dass unabhängig von der verwendeten View immer auf dieselben Daten zugegriffen werden kann.

Die obige Abbildung zeigt zwei verschiedene Ansichten desselben Modells, die jeweils eine Reihe von ausgewählten Elementen enthalten. Obwohl die Daten aus dem Modell in allen Ansichten einheitlich angezeigt werden, verfügt jede Ansicht über ein eigenes internes Auswahlmodell. Dies kann in bestimmten Situationen nützlich sein, aber für viele Anwendungen ist ein gemeinsames Auswahlmodell wünschenswert.

Handhabung von Auswahlen von Elementen

Der Mechanismus für die Handhabung von Auswahlen von Elementen innerhalb von Ansichten wird von der Klasse QItemSelectionModel bereitgestellt. Alle Standardansichten erstellen standardmäßig ihre eigenen Selektionsmodelle und interagieren mit ihnen auf die übliche Weise. Das von einer Ansicht verwendete Selektionsmodell kann durch die Funktion selectionModel() ermittelt werden, und ein Ersatzselektionsmodell kann mit setSelectionModel() angegeben werden. Die Möglichkeit, das von einer Ansicht verwendete Auswahlmodell zu steuern, ist nützlich, wenn wir mehrere konsistente Ansichten auf dieselben Modelldaten bereitstellen wollen.

Im Allgemeinen müssen Sie den Inhalt von Auswahlen nicht direkt manipulieren, es sei denn, Sie unterklassifizieren ein Modell oder eine Ansicht. Auf die Schnittstelle zum Selektionsmodell kann jedoch bei Bedarf zugegriffen werden, und dies wird in Behandlung von Selektionen in Elementansichten untersucht.

Gemeinsame Nutzung von Selektionen in Ansichten

Obwohl es praktisch ist, dass die View-Klassen standardmäßig ihre eigenen Selektionsmodelle bereitstellen, ist es oft wünschenswert, dass sowohl die Daten des Modells als auch die Auswahl des Benutzers in allen Views konsistent angezeigt werden, wenn wir mehr als eine View auf dasselbe Modell verwenden. Da die View-Klassen es erlauben, ihre internen Selektionsmodelle zu ersetzen, können wir mit der folgenden Zeile eine einheitliche Auswahl zwischen den Views erreichen:

    secondTableView->setSelectionModel(firstTableView->selectionModel());

Die zweite Ansicht erhält das Selektionsmodell der ersten Ansicht. Beide Sichten arbeiten nun mit demselben Selektionsmodell, so dass sowohl die Daten als auch die ausgewählten Elemente synchronisiert bleiben.

In dem oben gezeigten Beispiel wurden zwei Ansichten desselben Typs verwendet, um die Daten desselben Modells anzuzeigen. Würden jedoch zwei verschiedene Arten von Ansichten verwendet, könnten die ausgewählten Elemente in jeder Ansicht sehr unterschiedlich dargestellt werden; beispielsweise kann eine zusammenhängende Auswahl in einer Tabellenansicht als eine fragmentierte Menge hervorgehobener Elemente in einer Baumansicht dargestellt werden.

Delegate-Klassen

Konzepte

Im Gegensatz zum Model-View-Controller-Pattern enthält der Model/View-Entwurf keine vollständig separate Komponente zur Verwaltung der Interaktion mit dem Benutzer. Im Allgemeinen ist der View für die Präsentation der Modelldaten für den Benutzer und für die Verarbeitung der Benutzereingaben verantwortlich. Um eine gewisse Flexibilität bei der Art und Weise, wie diese Eingaben erfolgen, zu ermöglichen, wird die Interaktion von Delegierten durchgeführt. Diese Komponenten bieten Eingabemöglichkeiten und sind auch für das Rendering einzelner Elemente in einigen Ansichten verantwortlich. Die Standardschnittstelle zur Steuerung von Delegates ist in der Klasse QAbstractItemDelegate definiert.

Von Delegaten wird erwartet, dass sie in der Lage sind, ihre Inhalte selbst zu rendern, indem sie die Funktionen paint() und sizeHint() implementieren. Einfache, auf Widgets basierende Delegaten können jedoch die Unterklasse QStyledItemDelegate anstelle von QAbstractItemDelegate verwenden und die Vorteile der Standardimplementierungen dieser Funktionen nutzen.

Editoren für Delegaten können entweder durch die Verwendung von Widgets zur Verwaltung des Editierprozesses oder durch die direkte Verarbeitung von Ereignissen implementiert werden. Der erste Ansatz wird später in diesem Abschnitt behandelt.

Verwendung eines bestehenden Delegaten

Die mit Qt gelieferten Standardansichten verwenden Instanzen von QStyledItemDelegate, um Editiermöglichkeiten zu bieten. Diese Standardimplementierung der Delegatenschnittstelle rendert Elemente im üblichen Stil für jede der Standardansichten: QListView, QTableView, und QTreeView.

Alle Standardrollen werden durch den Standarddelegaten, der von den Standardansichten verwendet wird, gehandhabt. Die Art und Weise, wie diese interpretiert werden, wird in der Dokumentation QStyledItemDelegate beschrieben.

Der Delegat, der von einer Ansicht verwendet wird, wird von der Funktion itemDelegate() zurückgegeben. Die Funktion setItemDelegate() ermöglicht es Ihnen, einen benutzerdefinierten Delegaten für eine Standardansicht zu installieren, und es ist notwendig, diese Funktion zu verwenden, wenn Sie den Delegaten für eine benutzerdefinierte Ansicht festlegen.

Ein einfacher Delegat

Der hier implementierte Delegat verwendet eine QSpinBox, um Bearbeitungsmöglichkeiten zu bieten, und ist hauptsächlich für die Verwendung mit Modellen gedacht, die Ganzzahlen anzeigen. Obwohl wir ein benutzerdefiniertes Integer-basiertes Tabellenmodell für diesen Zweck eingerichtet haben, hätten wir stattdessen auch QStandardItemModel verwenden können, da der benutzerdefinierte Delegat die Dateneingabe steuert. Wir konstruieren eine Tabellenansicht, um den Inhalt des Modells anzuzeigen, und diese wird den benutzerdefinierten Delegaten für die Bearbeitung verwenden.

Wir untergliedern den Delegaten von QStyledItemDelegate, da wir keine benutzerdefinierten Anzeigefunktionen schreiben wollen. Wir müssen jedoch noch 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 keine Editor-Widgets eingerichtet werden, wenn der Delegat konstruiert wird. Wir konstruieren nur dann ein Editor-Widget, wenn es benötigt wird.

Bereitstellen eines Editors

In diesem Beispiel, wenn die Tabellensicht einen Editor bereitstellen muss, bittet sie den Delegierten, ein Editor-Widget bereitzustellen, das für das zu ändernde Element geeignet ist. Die Funktion createEditor() wird mit allem versorgt, was der Delegierte benötigt, um ein geeignetes 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 aufbewahren müssen, da die Ansicht die Verantwortung dafür übernimmt, es zu zerstören, wenn es nicht mehr benötigt wird.

Wir installieren den Standard-Ereignisfilter des Delegaten auf dem Editor, um sicherzustellen, dass er die Standard-Editierkürzel bietet, die die Benutzer erwarten. Zusätzliche Shortcuts können dem Editor hinzugefügt werden, um ein ausgefeilteres Verhalten zu ermöglichen; diese werden im Abschnitt über Bearbeitungshinweise diskutiert.

Die Ansicht stellt sicher, dass die Daten und die Geometrie des Editors korrekt gesetzt werden, indem sie Funktionen aufruft, die wir später für diese Zwecke definieren. Je nach dem vom View gelieferten Modellindex können wir verschiedene Editoren erstellen. Wenn wir zum Beispiel eine Spalte mit ganzen Zahlen und eine Spalte mit Strings haben, könnten wir entweder QSpinBox oder QLineEdit zurückgeben, je nachdem, welche Spalte bearbeitet wird.

Der Delegat muss eine Funktion bereitstellen, um Modelldaten in den Editor zu kopieren. In diesem Beispiel lesen wir die Daten, die in display role gespeichert sind, und setzen den Wert in der Spin-Box 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 eine Spin-Box handelt, aber wir hätten auch verschiedene Editoren für verschiedene Datentypen im Modell bereitstellen können. In diesem Fall müssten wir das Widget in den entsprechenden Typ umwandeln, bevor wir auf seine Mitgliedsfunktionen zugreifen.

Übermittlung von Daten an das Modell

Wenn der Benutzer die Bearbeitung des Wertes im Drehfeld abgeschlossen hat, fordert die Ansicht den Delegierten auf, den bearbeiteten Wert im Modell zu speichern, indem er 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 Delegierten verwaltet, müssen wir das Modell nur mit dem Inhalt des mitgelieferten Editors aktualisieren. In diesem Fall vergewissern wir uns, dass das Drehfeld aktuell ist, und aktualisieren das Modell mit dem darin enthaltenen Wert unter Verwendung des angegebenen Index.

Die Standardklasse QStyledItemDelegate informiert die Ansicht, wenn sie die Bearbeitung beendet hat, indem sie das Signal closeEditor() ausgibt. Die Ansicht sorgt dafür, dass das Editor-Widget geschlossen und zerstört wird. In diesem Beispiel stellen wir nur einfache Bearbeitungsmöglichkeiten zur Verfügung, so dass wir dieses Signal nie ausgeben müssen.

Alle Datenoperationen werden über die von QAbstractItemModel bereitgestellte Schnittstelle durchgeführt. Dies macht den Delegaten weitgehend unabhängig von der Art der Daten, die er manipuliert, aber es müssen einige Annahmen getroffen werden, um bestimmte Arten von Editor-Widgets zu verwenden. In diesem Beispiel haben wir angenommen, dass das Modell immer Integer-Werte enthält, aber wir können diesen Delegaten auch mit anderen Arten von Modellen verwenden, da QVariant sinnvolle Standardwerte für unerwartete Daten bereitstellt.

Aktualisieren der Geometrie des Editors

Es ist die Aufgabe des Delegaten, die Geometrie des Editors zu verwalten. Die Geometrie muss festgelegt werden, wenn der Editor erstellt wird, und wenn die Größe oder Position des Elements in der Ansicht geändert wird. Glücklicherweise stellt die Ansicht alle notwendigen Geometrieinformationen in einem view option Objekt zur Verfügung.

void SpinBoxDelegate::updateEditorGeometry(QWidget *editor,
                                           const QStyleOptionViewItem &option,
                                           const QModelIndex &/* index */) const
{
    editor->setGeometry(option.rect);
}

In diesem Fall verwenden wir nur die Geometrieinformationen, die von der Ansichtsoption im Rechteck des Elements bereitgestellt werden. Ein Delegat, der Elemente mit mehreren Elementen rendert, würde das Elementrechteck nicht direkt verwenden. Er würde den Editor im Verhältnis zu den anderen Elementen im Element positionieren.

Hinweise zur Bearbeitung

Nach der Bearbeitung sollten Delegaten den anderen Komponenten Hinweise über das Ergebnis des Bearbeitungsprozesses geben und Hinweise liefern, die alle nachfolgenden Bearbeitungsvorgänge unterstützen. Dies wird erreicht, indem das Signal closeEditor() mit einem geeigneten Hinweis ausgegeben wird. Dafür sorgt der Standard-Ereignisfilter QStyledItemDelegate, den wir bei der Konstruktion der Spinbox installiert haben.

Das Verhalten des Drehkastens könnte angepasst werden, um ihn benutzerfreundlicher zu gestalten. Wenn der Benutzer im Standard-Ereignisfilter von QStyledItemDelegate auf Return klickt, um seine Auswahl im Drehkasten zu bestätigen, wird der Wert vom Delegaten in das Modell übernommen und der Drehkasten wird geschlossen. Wir können dieses Verhalten ändern, indem wir unseren eigenen Ereignisfilter auf dem Drehkasten installieren und Bearbeitungshinweise bereitstellen, die unseren Anforderungen entsprechen; zum Beispiel könnten wir closeEditor() mit dem Hinweis EditNextItem ausgeben, um automatisch mit der Bearbeitung des nächsten Elements in der Ansicht zu beginnen.

Ein anderer Ansatz, der die Verwendung eines Ereignisfilters nicht erfordert, ist die Bereitstellung eines eigenen Editor-Widgets, vielleicht als Unterklasse von QSpinBox. Dieser alternative Ansatz würde uns mehr Kontrolle darüber geben, wie sich das Editor-Widget verhält, allerdings auf Kosten des Schreibens von zusätzlichem Code. Es ist in der Regel einfacher, einen Ereignisfilter im Delegaten zu installieren, wenn Sie das Verhalten eines Standard-Qt-Editor-Widgets anpassen müssen.

Delegaten müssen diese Hinweise nicht ausgeben, aber diejenigen, die dies nicht tun, werden weniger in Anwendungen integriert und sind weniger benutzbar als diejenigen, die Hinweise ausgeben, um allgemeine Editieraktionen zu unterstützen.

Handhabung von Auswahlen in Elementansichten

Konzepte

Das Selektionsmodell, das in den Klassen der Elementansichten verwendet wird, bietet eine allgemeine Beschreibung von Selektionen, die auf den Möglichkeiten der Modell/View-Architektur basiert. Obwohl die Standardklassen für die Handhabung von Selektionen für die bereitgestellten Elementansichten ausreichend sind, ermöglicht das Selektionsmodell die Erstellung spezieller Selektionsmodelle, die den Anforderungen Ihrer eigenen Elementmodelle und -ansichten entsprechen.

Informationen über die in einer Ansicht ausgewählten Elemente werden in einer Instanz der Klasse QItemSelectionModel gespeichert. Diese verwaltet Modellindizes für Elemente in einem einzelnen Modell und ist unabhängig von allen Ansichten. Da es viele Ansichten auf ein Modell geben kann, ist es möglich, Auswahlen zwischen den Ansichten gemeinsam zu nutzen, so dass Anwendungen mehrere Ansichten auf konsistente Weise anzeigen können.

Auswahlen bestehen aus Auswahlbereichen. Diese verwalten effizient Informationen über große Auswahlen von Elementen, indem sie nur die Start- und Endmodellindizes für jeden Bereich ausgewählter Elemente aufzeichnen. Nicht zusammenhängende Auswahlen von Elementen werden konstruiert, indem mehr als ein Auswahlbereich zur Beschreibung der Auswahl verwendet wird.

Selektionen werden auf eine Sammlung von Modellindizes angewendet, die von einem Selektionsmodell gehalten werden. Die zuletzt angewendete Auswahl von Elementen wird als die 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 unabhängige Zustände. Ein Element kann gleichzeitig das aktuelle Element und das ausgewählte Element sein. Die Ansicht ist dafür verantwortlich, dass es immer ein aktuelles Element gibt, da z. B. die Tastaturnavigation ein aktuelles Element erfordert.

In der nachstehenden Tabelle werden die Unterschiede zwischen dem aktuellen Element und den ausgewählten Elementen erläutert.

Aktuelles ElementAusgewählte Elemente
Es kann nur ein aktuelles Element geben.Es können mehrere ausgewählte Elemente vorhanden sein.
Das aktuelle Element wird durch Tastennavigation oder Mausklicks geändert.Der Auswahlstatus von Elementen wird in Abhängigkeit von mehreren vordefinierten Modi - z. B. Einfachauswahl, Mehrfachauswahl usw. - gesetzt oder aufgehoben. - wenn der Benutzer mit den Elementen interagiert.
Das aktuelle Element wird bearbeitet, wenn die Bearbeitungstaste F2 gedrückt oder das Element doppelt angeklickt 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 angezeigt.Die ausgewählten Elemente werden durch das Auswahlrechteck gekennzeichnet.

Bei der Bearbeitung von Auswahlen ist es oft hilfreich, sich QItemSelectionModel als eine Aufzeichnung des Auswahlstatus aller Elemente in einem Elementmodell vorzustellen. Sobald ein Auswahlmodell eingerichtet ist, können Sammlungen von Elementen ausgewählt, abgewählt oder ihr Auswahlstatus 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-View-Klassen bieten Standard-Auswahlmodelle, die in den meisten Anwendungen verwendet werden können. Ein Selektionsmodell, das zu einer View gehört, kann mit der Funktion selectionModel() der View abgerufen und mit setSelectionModel() von vielen Views gemeinsam genutzt werden, so dass die Konstruktion neuer Selektionsmodelle im Allgemeinen nicht erforderlich ist.

Eine Auswahl wird durch die Angabe eines Modells und eines Paares von Modellindizes auf QItemSelection erstellt. Dabei werden die Indizes verwendet, um auf Elemente im gegebenen Modell zu verweisen, und sie werden als die Elemente oben links und 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 Weise geschehen, die sich jeweils unterschiedlich auf die bereits im Auswahlmodell vorhandenen Auswahlen auswirken.

Auswahl von Elementen

Um einige der Hauptmerkmale von Selektionen zu demonstrieren, erstellen wir eine Instanz eines benutzerdefinierten Tabellenmodells mit insgesamt 32 Elementen und öffnen eine Tabellensicht auf dessen Daten:

    TableModel *model = new TableModel(8, 4, &app);

    QTableView *table = new QTableView(0);
    table->setModel(model);

    QItemSelectionModel *selectionModel = table->selectionModel();

Das Standardauswahlmodell des Tabellenviews wird zur späteren Verwendung abgerufen. Wir ändern keine Elemente im Modell, sondern wählen stattdessen einige Elemente aus, die in der Ansicht oben links in der Tabelle angezeigt werden sollen. Zu diesem Zweck müssen wir die Modellindizes abrufen, die den Elementen oben links und unten rechts im 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 zu sehen, müssen wir ein Auswahlobjekt konstruieren und es dann auf das Auswahlmodell anwenden:

    QItemSelection selection(topLeft, bottomRight);
    selectionModel->select(selection, QItemSelectionModel::Select);

Die Auswahl wird auf das Auswahlmodell mit einem Befehl angewendet, der durch eine Kombination von selection flags definiert ist. In diesem Fall bewirken die verwendeten Flags, dass die im Auswahlobjekt aufgezeichneten Elemente in das Auswahlmodell aufgenommen werden, unabhängig von ihrem vorherigen Zustand. Die resultierende Auswahl wird in der Ansicht angezeigt.

Die Auswahl der Elemente kann durch verschiedene Operationen verändert werden, die durch die Auswahlflags definiert sind. Die Auswahl, die sich aus diesen Operationen ergibt, kann eine komplexe Struktur haben, wird aber durch das Auswahlmodell effizient dargestellt. Die Verwendung der verschiedenen Auswahlflags zur Bearbeitung der ausgewählten Elemente wird beschrieben, wenn wir untersuchen, wie eine Auswahl aktualisiert wird.

Lesen des Auswahlstatus

Die im Auswahlmodell gespeicherten Modellindizes können mit der Funktion selectedIndexes() gelesen werden. Diese Funktion gibt eine unsortierte Liste von Modellindizes zurück, über die wir iterieren können, solange wir wissen, für welches Modell sie gelten:

    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 iterieren und zu ändern.

Das Auswahlmodell sendet Signale aus, um Änderungen an der Auswahl anzuzeigen. Diese benachrichtigen andere Komponenten über Änderungen sowohl an der Auswahl als Ganzes als auch an dem aktuell fokussierten Element im Elementmodell. Wir können das Signal selectionChanged() mit einem Slot verbinden und die Elemente im Modell untersuchen, die ausgewählt oder abgewählt werden, wenn sich die Auswahl ändert. Der Slot wird mit zwei QItemSelection Objekten aufgerufen: eines enthält eine Liste von Indizes, die neu ausgewählten Elementen entsprechen; das andere enthält Indizes, die neu abgewählten Elementen entsprechen.

Im folgenden Code stellen wir einen Slot bereit, der das Signal selectionChanged() empfängt, die ausgewählten Elemente mit einer Zeichenfolge ausfü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 stellen wir einen Slot bereit, der das Signal currentChanged() empfängt und die bereitgestellten Informationen verwendet, um die Statusleiste eines QMainWindow zu aktualisieren:

void MainWindow::changeCurrent(const QModelIndex &current,
    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 einfach, aber wir können das Auswahlmodell auch direkt aktualisieren.

Aktualisieren einer Auswahl

Auswahlbefehle werden durch eine Kombination von Auswahlflags bereitgestellt, die durch 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 Select Flag, das das Auswahlmodell anweist, die angegebenen Elemente als ausgewählt zu speichern. Das Flag Toggle veranlasst das Auswahlmodell, den Zustand der angegebenen Elemente umzukehren, indem es alle angegebenen abgewä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 wird und diese auf das Auswahlmodell angewendet wird. Im folgenden Code wird eine zweite Auswahl von Elementen auf das oben gezeigte Tabellenmodell angewendet, wobei der Befehl Toggle verwendet wird, um den Auswahlstatus der angegebenen Elemente zu invertieren.

    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 Tabellendarstellung angezeigt, was eine bequeme Möglichkeit darstellt, das Erreichte zu visualisieren:

Standardmäßig wirken die Auswahlbefehle nur auf die einzelnen Elemente, die durch die Modellindizes angegeben sind. Das zur Beschreibung des Auswahlbefehls verwendete Flag kann jedoch mit weiteren Flags kombiniert werden, um ganze Zeilen und Spalten zu ändern. Wenn Sie z. B. select() mit nur einem Index aufrufen, aber mit einem Befehl, der 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, bedeutet die Verwendung der Auswahlflags Columns und Rows, dass zwei Spalten und zwei Zeilen ausgewählt werden. Die folgende Abbildung zeigt das Ergebnis dieser beiden Auswahlen:

Die Befehle, die für das Beispielmodell ausgeführt wurden, bestanden alle darin, eine Auswahl von Elementen im Modell zu akkumulieren. 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 mit dem Hinzufügen neuer Auswahlen beginnen, kombinieren Sie die anderen Auswahlflags mit dem Flag Clear. Dies hat den Effekt, dass die Sammlung der Modellindizes des Auswahlmodells zurückgesetzt wird.

Auswählen aller Elemente in einem Modell

Um alle Elemente in einem Modell auszuwählen, muss für jede Ebene des Modells eine Auswahl erstellt werden, die alle Elemente in dieser Ebene abdeckt. Dazu werden die Indizes abgerufen, 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 in dem Auswahlmodell ausgewählt:

    QItemSelection selection(topLeft, bottomRight);
    selectionModel->select(selection, QItemSelectionModel::Select);

Dies muss für alle Ebenen des Modells durchgeführt werden. Für Elemente der obersten Ebene würde man den übergeordneten Index auf die übliche Weise definieren:

Bei hierarchischen Modellen wird die Funktion hasChildren() verwendet, um festzustellen, ob ein bestimmtes Element das übergeordnete Element einer anderen Ebene von Elementen ist.

Neue Modelle erstellen

Die Trennung der Funktionalität zwischen Modell- und Ansichtskomponenten ermöglicht die Erstellung von Modellen, die die Vorteile vorhandener Ansichten nutzen können. Dieser Ansatz ermöglicht die Darstellung von Daten aus einer Vielzahl von Quellen unter Verwendung von Standardkomponenten für die grafische Benutzeroberfläche, wie 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, so dass die Möglichkeit besteht, Daten einzufügen, zu entfernen, zu ändern oder auf irgendeine Weise zu sortieren. Sie bietet auch 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 sind als Ausgangspunkt für einfache Listen- und Tabellenmodelle leichter zu verwenden.

In diesem Abschnitt erstellen wir ein einfaches Nur-Lese-Modell, um die Grundprinzipien der Modell/Ansichtsarchitektur zu erkunden. Später in diesem Abschnitt passen wir dieses einfache Modell so an, dass die Elemente vom Benutzer geändert werden können.

Ein Beispiel für ein komplexeres Modell finden Sie im Beispiel für ein einfaches Baummodell.

Die Anforderungen von QAbstractItemModel subclasses werden im Dokument Model Subclassing Reference ausführlicher beschrieben.

Entwerfen eines Modells

Bei der Erstellung eines neuen Modells für eine bestehende Datenstruktur ist es wichtig zu überlegen, welche Art von Modell verwendet werden soll, um eine Schnittstelle zu den Daten bereitzustellen. Wenn die Datenstruktur als Liste oder Tabelle von Elementen dargestellt werden kann, können Sie QAbstractListModel oder QAbstractTableModel unterklassifizieren, da diese Klassen geeignete Standardimplementierungen für viele Funktionen bieten.

Wenn die zugrundeliegende Datenstruktur jedoch nur durch eine hierarchische Baumstruktur dargestellt werden kann, ist es notwendig, QAbstractItemModel zu unterklassifizieren. Dieser Ansatz wird in dem Beispiel Simple Tree Model verfolgt.

In diesem Abschnitt implementieren wir ein einfaches Modell, das auf einer Liste von Strings basiert, so dass QAbstractListModel eine ideale Basisklasse darstellt, auf der man aufbauen kann.

Unabhängig von der Form der zugrunde liegenden Datenstruktur ist es in der Regel eine gute Idee, die Standard-API QAbstractItemModel in spezialisierten Modellen durch eine API zu ergänzen, die einen natürlicheren Zugriff auf die zugrunde liegende Datenstruktur ermöglicht. Dies macht es einfacher, das Modell mit Daten zu füllen, ermöglicht aber dennoch anderen allgemeinen Modell-/Ansichtskomponenten, mit dem Modell über die Standard-API zu interagieren. Das unten beschriebene Modell bietet einen eigenen Konstruktor für genau diesen Zweck.

Ein Nur-Lese-Beispielmodell

Das hier implementierte Modell ist ein einfaches, nicht-hierarchisches Nur-Lese-Datenmodell, das auf der Standardklasse QStringListModel basiert. Es hat eine QStringList als interne Datenquelle und implementiert nur das, was für ein funktionierendes Modell benötigt wird. Um die Implementierung zu vereinfachen, haben wir die Klasse QAbstractListModel untergeordnet, da sie ein sinnvolles Standardverhalten für Listenmodelle definiert und eine einfachere Schnittstelle als die Klasse QAbstractItemModel bietet.

Bei der Implementierung eines Modells ist es wichtig, daran zu denken, dass QAbstractItemModel selbst keine Daten speichert, sondern lediglich eine Schnittstelle bereitstellt, über die die Ansichten auf die Daten zugreifen. Für ein minimales Nur-Lese-Modell ist es nur notwendig, einige wenige Funktionen zu implementieren, da es für die meisten der Schnittstelle Standardimplementierungen gibt. 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.

Gute Modelle implementieren auch headerData(), um Baum- und Tabellenansichten etwas zu geben, das sie in ihren Kopfzeilen anzeigen können.

Beachten Sie, dass dies ein nicht-hierarchisches Modell ist, so dass wir uns nicht um die Eltern-Kind-Beziehungen kümmern müssen. Wenn unser Modell hierarchisch wäre, müssten wir auch die Funktionen index() und parent() implementieren.

Die Liste der Zeichenketten wird intern in der privaten Mitgliedsvariablen stringList gespeichert.

Dimensionen des Modells

Wir wollen, dass die Anzahl der Zeilen im Modell der Anzahl der Strings in der Stringliste entspricht. Wir implementieren die Funktion rowCount() mit diesem Ziel:

int StringListModel::rowCount(const QModelIndex &parent) const
{
    return stringList.count();
}

Da das Modell nicht hierarchisch ist, können wir den dem übergeordneten Element entsprechenden Modellindex getrost ignorieren. Standardmäßig enthalten Modelle, die von QAbstractListModel abgeleitet sind, nur eine Spalte, so dass wir die Funktion columnCount() nicht neu implementieren müssen.

Modellkopfzeilen und Daten

Für Elemente in der Ansicht möchten wir die Zeichenketten in der String-Liste zurückgeben. Die Funktion data() ist für die Rückgabe des Datenelements zuständig, das dem Indexargument 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 angegebene Modellindex gültig ist, die Zeilennummer innerhalb des Bereichs der Elemente in der Stringliste liegt und die angeforderte Rolle eine von uns unterstützte ist.

Einige Ansichten, wie z.B. QTreeView und QTableView, sind in der Lage, Kopfzeilen zusammen mit den Artikeldaten anzuzeigen. Wenn unser Modell in einer Ansicht mit Kopfzeilen angezeigt wird, sollen die Kopfzeilen die Zeilen- und Spaltennummern anzeigen. Wir können Informationen über die Kopfzeilen bereitstellen, indem wir die Funktion headerData() unterklassifizieren:

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 eine gültige QVariant zurück, wenn die Rolle eine von uns unterstützte ist. Bei der Entscheidung, welche Daten genau zurückgegeben werden sollen, wird auch die Ausrichtung der Kopfzeile berücksichtigt.

Nicht alle Ansichten zeigen Kopfzeilen mit den Artikeldaten an, und diejenigen, die dies tun, können so konfiguriert werden, dass sie ausgeblendet werden. Nichtsdestotrotz wird empfohlen, die Funktion headerData() zu implementieren, um relevante Informationen über die vom Modell bereitgestellten Daten bereitzustellen.

Ein Element kann mehrere Rollen haben, die je nach der angegebenen Rolle unterschiedliche Daten ausgeben. Die Elemente in unserem Modell haben nur eine Rolle, 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 DisplayRole bereitstellen, in anderen Rollen wiederverwenden, z. B. in ToolTipRole, die von Ansichten verwendet werden können, um Informationen über Elemente in einem Tooltip anzuzeigen.

Ein bearbeitbares Modell

Das Nur-Lese-Modell zeigt, wie einfache Auswahlmöglichkeiten für den Benutzer dargestellt werden können, aber für viele Anwendungen ist ein bearbeitbares Listenmodell viel nützlicher. Wir können das Nur-Lese-Modell ändern, um die Elemente editierbar zu machen, indem wir die data()-Funktion ändern, die wir für das Nur-Lese-Modell implementiert haben, und indem wir 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, ob ein Element editierbar ist, bevor er einen Editor erstellt. Das Modell muss den Delegaten wissen lassen, dass seine Elemente editierbar sind. Wir tun dies, indem wir die richtigen Flags für jedes Element im Modell zurückgeben; in diesem Fall aktivieren wir alle Elemente und machen sie sowohl auswählbar als auch editierbar:

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 Delegierte den eigentlichen Bearbeitungsprozess durchführt. Wir müssen dem Delegierten nur die Möglichkeit geben, die Daten im Modell zu setzen. Dies wird durch 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 Stringliste, das dem Modellindex entspricht, durch den angegebenen Wert ersetzt. Bevor wir jedoch die Stringliste ändern können, müssen wir sicherstellen, dass der Index gültig ist, dass das Element vom richtigen Typ ist und dass die Rolle unterstützt wird. Wir bestehen darauf, dass die Rolle EditRole ist, da dies die Rolle ist, die der Standard-Elementdelegierte verwendet. Für boolesche Werte können Sie jedoch Qt::CheckStateRole verwenden und das Kennzeichen Qt::ItemIsUserCheckable setzen; ein Kontrollkästchen wird dann zur Bearbeitung des Wertes verwendet. Die diesem Modell zugrundeliegenden Daten sind für alle Rollen gleich, so dass dieses Detail lediglich die Integration des Modells in Standardkomponenten erleichtert.

Wenn die Daten gesetzt wurden, muss das Modell den Ansichten mitteilen, dass sich einige Daten geändert haben. Dies geschieht durch die Ausgabe des Signals dataChanged(). Da sich nur ein Element der Daten geändert hat, ist der Bereich der im Signal angegebenen Elemente auf einen Modellindex beschränkt.

Auch die Funktion data() muss geändert werden, um den Test 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-List-Modell ist es nur sinnvoll, die Anzahl der Zeilen zu ändern, so dass wir nur die Funktionen zum Einfügen und Entfernen von Zeilen neu implementieren. 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 Strings in einer Liste entsprechen, fügt die Funktion insertRows() eine Anzahl leerer Strings in die Stringliste vor der angegebenen Position ein. Die Anzahl der eingefügten Strings entspricht der Anzahl der angegebenen Zeilen.

Der übergeordnete Index wird normalerweise verwendet, um zu bestimmen, an welcher Stelle im Modell die Zeilen eingefügt werden sollen. In diesem Fall haben wir nur eine einzige Liste von Strings auf oberster Ebene, so dass wir nur 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 andere Komponenten darüber zu informieren, dass sich die Anzahl der Zeilen ändern wird. Die Funktion gibt die Zeilennummern der ersten und der letzten neu einzufügenden Zeile sowie den Modellindex für ihr übergeordnetes Element an. Nach dem Ändern der Zeichenfolgenliste ruft sie endInsertRows() auf, um den Vorgang abzuschließen und andere Komponenten darüber zu informieren, dass sich die Abmessungen des Modells geändert haben, und gibt true zurück, um den Erfolg anzuzeigen.

Die Funktion zum Entfernen von Zeilen aus dem Modell ist ebenfalls einfach zu schreiben. Die Zeilen, die aus dem Modell entfernt werden sollen, werden durch die Position und die Anzahl der angegebenen Zeilen spezifiziert. Um unsere Implementierung zu vereinfachen, ignorieren wir den übergeordneten Index und entfernen nur die entsprechenden Elemente aus der String-Liste.

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 die zugrunde liegenden Daten entfernt werden, und gibt die erste und die letzte zu entfernende Zeile an. So können andere Komponenten auf die Daten zugreifen, 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 Abmessungen des Modells geändert haben.

Nächste Schritte

Wir können die von diesem oder einem anderen Modell bereitgestellten Daten mithilfe der Klasse QListView anzeigen, um die Elemente des Modells in Form einer vertikalen Liste darzustellen. Für das String-Listenmodell bietet diese Ansicht auch einen Standard-Editor, mit dem die Elemente bearbeitet werden können. Die Möglichkeiten, die die Standard-View-Klassen bieten, werden in View Classes untersucht.

Das Dokument Model Subclassing Reference diskutiert die Anforderungen von QAbstractItemModel Subklassen im Detail und bietet einen Leitfaden für die virtuellen Funktionen, die implementiert werden müssen, um verschiedene Features in verschiedenen Modelltypen zu ermöglichen.

Item View Convenience Classes

Die Namen der Element-basierten Widgets spiegeln ihre Verwendung wider: QListWidget bietet eine Liste von Elementen, QTreeWidget zeigt eine mehrstufige Baumstruktur an, und QTableWidget bietet eine Tabelle mit Zellenelementen. Jede Klasse erbt das Verhalten der Klasse QAbstractItemView, die das allgemeine Verhalten für die Elementauswahl und die Kopfzeilenverwaltung implementiert.

Listen-Widgets

Listen auf einer Ebene werden in der Regel mit einem QListWidget und einer Reihe von QListWidgetItemangezeigt. Ein Listen-Widget wird auf die gleiche Weise wie jedes andere Widget aufgebaut:

    QListWidget *listWidget = new QListWidget(this);

Listenelemente können dem Listen-Widget direkt hinzugefügt werden, wenn sie erstellt werden:

    new QListWidgetItem(tr("Sycamore"), listWidget);
    new QListWidgetItem(tr("Chestnut"), listWidget);
    new QListWidgetItem(tr("Mahogany"), listWidget);

Sie können auch ohne ein übergeordnetes Listen-Widget erstellt werden und zu einem späteren Zeitpunkt zu 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 für die Darstellung des Textes verwendet werden, können geändert werden, um ein individuelles Aussehen der Elemente zu erreichen. Tooltips, Statustipps und die "Was ist das?"-Hilfe lassen sich leicht konfigurieren, um sicherzustellen, dass die Liste richtig in die Anwendung integriert ist.

    newItem->setToolTip(toolTipText);
    newItem->setStatusTip(toolTipText);
    newItem->setWhatsThis(whatsThisText);

Standardmäßig werden die Elemente in einer Liste in der Reihenfolge ihrer Erstellung angezeigt. Listen von Elementen können nach den in Qt::SortOrder angegebenen Kriterien sortiert werden, um eine Liste von Elementen zu erzeugen, die in vorwärts- oder rückwärtsgerichteter alphabetischer Reihenfolge sortiert ist:

    listWidget->sortItems(Qt::AscendingOrder);
    listWidget->sortItems(Qt::DescendingOrder);

Baum-Widgets

Bäume oder hierarchische Listen von Einträgen werden von den Klassen QTreeWidget und QTreeWidgetItem bereitgestellt. Jedes Element in einem Tree-Widget kann eigene untergeordnete Elemente haben und eine Reihe von Informationsspalten anzeigen. Baum-Widgets werden genau wie jedes andere Widget erstellt:

    QTreeWidget *treeWidget = new QTreeWidget(this);

Bevor Elemente zum Tree-Widget hinzugefügt werden können, muss die Anzahl der Spalten festgelegt werden. Wir könnten zum Beispiel zwei Spalten definieren und eine Kopfzeile erstellen, um Beschriftungen am oberen Rand jeder Spalte bereitzustellen:

    treeWidget->setColumnCount(2);
    QStringList headers;
    headers << tr("Subject") << tr("Default");
    treeWidget->setHeaderLabels(headers);

Die einfachste Art, die Bezeichnungen für jeden Abschnitt festzulegen, ist die Angabe einer Stringliste. Für anspruchsvollere Kopfzeilen können Sie ein Baumelement konstruieren, es nach Belieben ausschmücken und es als Kopfzeile des Baum-Widgets verwenden.

Top-Level-Elemente im Tree-Widget werden mit dem Tree-Widget als übergeordnetem Widget konstruiert. Sie können in beliebiger Reihenfolge eingefügt werden, oder Sie können sicherstellen, dass sie in einer bestimmten Reihenfolge aufgelistet werden, indem Sie bei der Konstruktion jedes Elements das vorherige 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 gehen mit Objekten der obersten Ebene etwas anders um als mit anderen Objekten, die tiefer im Baum liegen. Einträge können von der obersten Ebene des Baums durch den Aufruf der Funktion takeTopLevelItem() des Tree-Widgets entfernt werden, aber Einträge von niedrigeren Ebenen werden durch den Aufruf der Funktion takeChild() des übergeordneten Eintrags entfernt. Elemente werden auf der obersten Ebene des Baums mit der Funktion insertTopLevelItem() eingefügt. Auf niedrigeren 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. Es muss lediglich geprüft werden, ob es sich bei den Elementen um Elemente der obersten Ebene handelt oder nicht, und diese Information wird von der Funktion parent() jedes Elements geliefert. Zum Beispiel können wir 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 des Baum-Widgets erfolgt nach demselben 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, die denen in Tabellenkalkulationsprogrammen ähneln, werden mit QTableWidget und QTableWidgetItem erstellt. Diese bieten ein scrollendes Tabellen-Widget mit Überschriften und Elementen, die darin verwendet werden können.

Tabellen können mit einer bestimmten Anzahl von Zeilen und Spalten erstellt werden, oder diese können zu einer Tabelle ohne Größe hinzugefügt werden, wenn sie benötigt werden.

    QTableWidget *tableWidget;
    tableWidget = new QTableWidget(12, 3, this);

Die Elemente werden außerhalb der Tabelle konstruiert, bevor sie der Tabelle an der gewünschten Stelle hinzugefü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 konstruiert 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 Merkmale

Es gibt eine Reihe von elementbasierten Funktionen, die allen Convenience-Klassen gemeinsam sind und die über dieselben Schnittstellen in jeder Klasse verfügbar sind. Wir stellen diese in den folgenden Abschnitten anhand einiger Beispiele für verschiedene Widgets vor. In der Liste der Model/View-Klassen für jedes der Widgets finden Sie weitere Einzelheiten über die Verwendung der einzelnen Funktionen.

Ausgeblendete Elemente

Manchmal ist es nützlich, Elemente in einem Widget für die Elementansicht ausblenden zu können, anstatt sie zu entfernen. Elemente für alle oben genannten Widgets können ausgeblendet und später wieder angezeigt werden. Sie können feststellen, ob ein Element versteckt ist, indem Sie die Funktion isItemHidden() aufrufen, und Elemente können mit setItemHidden() versteckt werden.

Da dieser Vorgang elementbasiert ist, ist dieselbe Funktion für alle drei Komfortklassen verfügbar.

Auswahlen

Die Art und Weise, wie Elemente ausgewählt werden, wird durch den Auswahlmodus des Widgets (QAbstractItemView::SelectionMode) gesteuert. Diese Eigenschaft steuert, ob der Benutzer ein oder mehrere Elemente auswählen kann und ob die Auswahl bei der Auswahl von vielen Elementen ein kontinuierlicher Bereich von Elementen sein muss. Der Auswahlmodus funktioniert für alle oben genannten Widgets auf die gleiche Weise.

Auswahl eines einzelnen Elements: 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 von mehreren Einträgen: In diesem Modus kann der Benutzer den Auswahlstatus eines beliebigen Elements im Widget umschalten, ohne die bestehende Auswahl zu ändern, ähnlich wie nicht-exklusive Kontrollkästchen unabhängig voneinander umgeschaltet werden können.

Erweiterte Auswahlen: Für Widgets, bei denen häufig viele benachbarte Elemente ausgewählt werden müssen, wie z. B. in Tabellenkalkulationen, ist der Modus ExtendedSelection erforderlich. 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 nicht an andere ausgewählte Elemente im Widget angrenzen, können ebenfalls erstellt werden, wenn Modifikatortasten verwendet werden.

Wenn der Benutzer ein Element auswählt, ohne eine Modifikatortaste zu verwenden, wird die bestehende Auswahl gelöscht.

Die ausgewählten Elemente in einem Widget werden mit der Funktion selectedItems() gelesen, die eine Liste relevanter Elemente liefert, über die iteriert werden kann. Mit dem folgenden Code lässt sich zum Beispiel die Summe aller numerischen Werte in 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 Einfachauswahlmodus das aktuelle Element in der Auswahl steht. In den Modi Mehrfachauswahl und erweiterte Auswahl liegt das aktuelle Element möglicherweise nicht in der Auswahl, je nachdem, wie der Benutzer die Auswahl gebildet hat.

Durchsuchen

Es ist oft nützlich, Elemente in einem Elementansichts-Widget finden zu können, entweder als Entwickler oder als Dienst, der den Benutzern angeboten wird. Alle drei Komfortklassen der Elementansicht bieten eine gemeinsame findItems() Funktion, um dies so konsistent und einfach wie möglich zu gestalten.

Die Elemente werden nach dem Text gesucht, den sie enthalten, und zwar nach Kriterien, die durch eine Auswahl von Werten aus Qt::MatchFlags festgelegt sind. Eine Liste der übereinstimmenden Elemente erhalten wir mit der Funktion findItems():

    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.

Verwenden von Drag and Drop mit Item Views

Qt's Drag and Drop Infrastruktur wird vollständig vom Model/View Framework unterstützt. Elemente in Listen, Tabellen und Bäumen können innerhalb der Ansichten gezogen werden, und Daten können als MIME-kodierte Daten importiert und exportiert werden.

Die Standardansichten unterstützen automatisch internes Ziehen und Ablegen, wobei Elemente verschoben werden können, um die Reihenfolge zu ändern, in der sie angezeigt werden. Standardmäßig ist die Drag&Drop-Funktion für diese Ansichten nicht aktiviert, da sie für die einfachsten und häufigsten Verwendungszwecke konfiguriert sind. Um das Verschieben von Elementen zu ermöglichen, müssen bestimmte Eigenschaften der Ansicht aktiviert werden, und die Elemente selbst müssen das Verschieben ebenfalls zulassen.

Die Anforderungen an ein Modell, das nur das Exportieren von Elementen aus einer Ansicht und nicht das Ablegen von Daten in der Ansicht erlaubt, sind geringer als die Anforderungen an ein vollständig aktiviertes Drag&Drop-Modell.

Weitere Informationen zum Aktivieren der Drag&Drop-Unterstützung in neuen Modellen finden Sie in der Referenz zur Modellsubklassifizierung.

Verwenden von Komfortansichten

Jeder der Elementtypen, die mit QListWidget, QTableWidget und QTreeWidget verwendet werden, ist so konfiguriert, dass er standardmäßig einen anderen Satz von Flags verwendet. Zum Beispiel ist jedes QListWidgetItem oder QTreeWidgetItem anfänglich aktiviert, überprüfbar, auswählbar und kann als Quelle einer Drag & Drop-Operation verwendet werden; jedes QTableWidgetItem kann auch bearbeitet werden und als Ziel einer Drag & Drop-Operation verwendet werden.

Obwohl bei allen Standardelementen eine oder beide Flags für das Ziehen und Ablegen gesetzt sind, müssen Sie im Allgemeinen verschiedene Eigenschaften in der Ansicht selbst einstellen, um die integrierte Unterstützung für das Ziehen und Ablegen zu nutzen:

  • Um das Ziehen von Elementen zu ermöglichen, setzen Sie die Eigenschaft dragEnabled der Ansicht auf true.
  • Um dem Benutzer die Möglichkeit zu geben, entweder interne oder externe Elemente innerhalb der Ansicht abzulegen, setzen Sie die Eigenschaft acceptDrops der Ansicht viewport() auf true.
  • Um dem Benutzer zu zeigen, wo das Element, das gerade gezogen wird, platziert wird, wenn es abgelegt wird, setzen Sie die Eigenschaft showDropIndicator der Ansicht. Dadurch erhält der Benutzer ständig aktualisierte Informationen über die Platzierung des Elements innerhalb der Ansicht.

Mit den folgenden Codezeilen können wir zum Beispiel Drag & Drop in einem Listen-Widget 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, das das Kopieren von Elementen innerhalb der Ansicht ermöglicht und sogar das Ziehen von Elementen zwischen Ansichten, die denselben Datentyp enthalten, erlaubt. In beiden Fällen werden die Elemente kopiert und nicht verschoben.

Um dem Benutzer die Möglichkeit zu geben, die Elemente innerhalb der Ansicht zu verschieben, müssen wir die dragDropMode des Listen-Widgets festlegen:

listWidget->setDragDropMode(QAbstractItemView::InternalMove);

Model/View-Klassen verwenden

Das Einrichten einer Ansicht für das Ziehen und Ablegen folgt dem gleichen Muster wie bei den Komfortansichten. Zum Beispiel kann eine QListView auf die gleiche Weise eingerichtet werden wie eine QListWidget:

QListView *listView = new QListView(this);
listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
listView->setDragEnabled(true);
listView->setAcceptDrops(true);
listView->setDropIndicatorShown(true);

Da der Zugriff auf die vom View angezeigten Daten durch ein Modell gesteuert wird, muss das verwendete Modell auch Unterstützung für Drag&Drop-Operationen bieten. Die von einem Modell unterstützten Aktionen können durch Neuimplementierung der Funktion QAbstractItemModel::supportedDropActions() festgelegt werden. Zum Beispiel werden Kopier- und Verschiebevorgänge mit dem folgenden Code aktiviert:

Qt::DropActions DragDropListModel::supportedDropActions() const
{
    return Qt::CopyAction | Qt::MoveAction;
}

Obwohl jede Kombination von Werten aus Qt::DropActions angegeben werden kann, muss das Modell so geschrieben werden, dass es sie unterstützt. Damit beispielsweise Qt::MoveAction ordnungsgemäß mit einem Listenmodell verwendet werden kann, muss das Modell eine Implementierung von QAbstractItemModel::removeRows() bereitstellen, entweder direkt oder durch Vererbung der Implementierung von seiner Basisklasse.

Aktivieren von Drag&Drop für Elemente

Modelle zeigen den Ansichten an, welche Elemente gezogen werden können und welche fallen gelassen werden können, indem sie die Funktion QAbstractItemModel::flags() neu implementieren und geeignete Flags bereitstellen.

Beispielsweise kann ein Modell, das eine einfache Liste auf der Grundlage von QAbstractListModel bereitstellt, die Zieh- und Ablegefunktion für jedes Element aktivieren, indem sichergestellt wird, 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 der obersten Ebene des Modells abgelegt werden können, das Ziehen aber nur für gültige Elemente möglich ist.

Da das Modell von QStringListModel abgeleitet ist, erhalten wir im obigen Code einen Standardsatz von Flags, indem wir dessen Implementierung der Funktion flags() aufrufen.

Kodierung exportierter Daten

Wenn Datenelemente aus einem Modell durch Ziehen und Ablegen 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 nur reinen Text liefert, würde zum Beispiel die folgende Implementierung bereitstellen:

QStringList DragDropListModel::mimeTypes() const
{
    QStringList types;
    types << "application/vnd.text.list";
    return types;
}

Das Modell muss auch Code für die Kodierung von Daten in dem angekündigten Format bereitstellen. Dies wird erreicht, indem die Funktion QAbstractItemModel::mimeData() neu implementiert wird, 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 Stream-Operatoren für sie implementiert werden müssen. Siehe die Beschreibung der Klasse QMetaObject für Details.

Einfügen von gelöschten Daten in ein Modell

Die Art und Weise, wie ein bestimmtes Modell mit gelöschten Daten umgeht, hängt sowohl von seinem Typ (Liste, Tabelle oder Baum) als auch von der Art und Weise ab, wie sein Inhalt dem Benutzer präsentiert werden soll. Im Allgemeinen sollte der Ansatz für die Aufnahme gelöschter Daten derjenige sein, der am besten zum zugrunde liegenden Datenspeicher des Modells passt.

Verschiedene Modelltypen neigen dazu, gelöschte Daten auf unterschiedliche Weise zu behandeln. Listen- und Tabellenmodelle bieten nur eine flache Struktur, in der Datenelemente gespeichert werden. Folglich können sie neue Zeilen (und Spalten) einfügen, wenn Daten auf einem bestehenden Element in einer Ansicht abgelegt werden, oder sie können den Inhalt des Elements im Modell mit einigen der gelieferten Daten überschreiben. Baummodelle sind oft in der Lage, untergeordnete Elemente mit neuen Daten zu ihren zugrunde liegenden Datenspeichern hinzuzufügen, und verhalten sich daher für den Benutzer vorhersehbarer.

Verlorene Daten werden durch die Neuimplementierung von QAbstractItemModel::dropMimeData() in einem Modell behandelt. So kann beispielsweise ein Modell, das eine einfache Liste von Zeichenketten verarbeitet, eine Implementierung vorsehen, die Daten, die auf vorhandene Elemente fallengelassen werden, anders behandelt als Daten, die auf der obersten Ebene des Modells fallengelassen werden (d. h. auf ein ungültiges Element).

Modelle können das Fallenlassen auf bestimmte Elemente oder abhängig von den fallengelassenen Daten verbieten, indem sie QAbstractItemModel::canDropMimeData() neu implementieren.

Das Modell muss zunächst sicherstellen, dass die Operation ausgeführt werden soll, dass die übergebenen Daten ein brauchbares Format haben und dass ihr Ziel 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 Modell mit einer einspaltigen Stringliste kann einen Fehler anzeigen, wenn die gelieferten Daten nicht im Klartext vorliegen oder wenn die für die Übergabe angegebene Spaltennummer ungültig ist.

Die Daten, die in das Modell eingefügt werden sollen, werden unterschiedlich behandelt, je nachdem, ob sie auf einem vorhandenen Element abgelegt werden oder nicht. In diesem einfachen Beispiel wollen wir Dropdowns zwischen vorhandenen Elementen, vor dem ersten Element in der Liste und nach dem letzten Element zulassen.

Bei einem Drop ist der Modellindex, der dem übergeordneten Element entspricht, entweder gültig, was anzeigt, dass der Drop auf einem Element stattfand, oder er ist ungültig, was anzeigt, dass der Drop irgendwo in der Ansicht stattfand, die der obersten Ebene des Modells entspricht.

    int beginRow;

    if (row != -1)
        beginRow = row;

Zunächst wird die gelieferte Zeilennummer geprüft, um zu sehen, 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();

Wenn der Index des übergeordneten Modells gültig ist, wurde ein Element eingefügt. In diesem einfachen Listenmodell ermitteln wir die Zeilennummer des Elements und verwenden diesen Wert, um die fallen gelassenen Elemente in die oberste Ebene des Modells einzufügen.

    else
        beginRow = rowCount(QModelIndex());

Wenn ein Drop an einer anderen Stelle in der Ansicht stattfindet und die Zeilennummer unbrauchbar ist, fügen wir Elemente an die oberste Ebene des Modells an.

In hierarchischen Modellen wäre es besser, wenn bei einem fallengelassenen Element neue Elemente als Kinder dieses Elements in das Modell eingefügt würden. In dem hier gezeigten einfachen Beispiel hat das Modell nur eine Ebene, so dass dieser Ansatz nicht angemessen ist.

Dekodierung importierter Daten

Jede Implementierung von dropMimeData() muss die Daten auch dekodieren und in die zugrunde liegende Datenstruktur des Modells einfügen.

Bei einem einfachen String-Listenmodell 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 Zeichenketten können dann in den zugrunde liegenden Datenspeicher eingefügt werden. Aus Gründen der Konsistenz kann dies über die eigene Schnittstelle des Modells 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 normalerweise Implementierungen der Funktionen QAbstractItemModel::insertRows() und QAbstractItemModel::setData() bereitstellen muss.

Proxy-Modelle

Im Model/View-Framework können Daten, die von einem einzigen Model geliefert werden, von einer beliebigen Anzahl von Views gemeinsam genutzt werden, und jede dieser Views kann dieselben Informationen möglicherweise auf völlig unterschiedliche Weise darstellen. Benutzerdefinierte Ansichten und Delegaten sind effektive Möglichkeiten, um radikal unterschiedliche Darstellungen derselben Daten bereitzustellen. Anwendungen müssen jedoch häufig herkömmliche Ansichten auf verarbeitete Versionen derselben Daten bereitstellen, wie z. B. unterschiedlich sortierte Ansichten auf eine Liste von Elementen.

Obwohl es sinnvoll erscheint, Sortier- und Filteroperationen als interne Funktionen von Ansichten durchzuführen, erlaubt dieser Ansatz nicht, dass mehrere Ansichten die Ergebnisse solcher potenziell kostspieligen Operationen gemeinsam nutzen. Der alternative Ansatz, der eine Sortierung innerhalb des Modells selbst vorsieht, führt zu einem ähnlichen Problem, bei dem jede Ansicht Datenelemente anzeigen muss, die entsprechend der letzten Verarbeitungsoperation organisiert sind.

Um dieses Problem zu lösen, verwendet das Modell/View-Framework Proxy-Modelle, um die zwischen den einzelnen Modellen und Views gelieferten Informationen zu verwalten. Proxy-Modelle sind Komponenten, die sich aus Sicht einer Ansicht wie gewöhnliche Modelle verhalten und im Namen dieser Ansicht auf Daten aus Quellmodellen zugreifen. Die Signale und Slots, die vom Model/View-Framework verwendet werden, stellen sicher, dass jede Ansicht entsprechend aktualisiert wird, unabhängig davon, wie viele Proxy-Modelle zwischen ihr und dem Quellmodell platziert sind.

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 ein benutzerdefiniertes Filter- und Sortierverhalten zu ermöglichen. Die Klasse QSortFilterProxyModel kann auf die folgende Weise 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 werden und können von verschiedenen Ansichten gemeinsam genutzt werden. Sie können auch verwendet werden, um 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 können durch Unterklassifizierung dieser Klasse und Implementierung der erforderlichen Vergleichsoperationen erstellt werden.

Anpassung von Proxy-Modellen

Im Allgemeinen besteht die Art der Verarbeitung in einem Proxy-Modell darin, dass jedes Datenelement von seinem ursprünglichen Speicherort im Quellmodell entweder auf einen anderen Speicherort im Proxy-Modell abgebildet wird. In einigen Modellen können einige Elemente keinen entsprechenden Ort im Proxy-Modell haben; diese Modelle sind Filter-Proxy-Modelle. Ansichten greifen auf Elemente zu, indem sie Modellindizes verwenden, die vom Proxy-Modell bereitgestellt werden, und diese enthalten keine Informationen über das Quellmodell oder die Positionen der ursprünglichen Elemente in diesem Modell.

QSortFilterProxyModel ermöglicht es, Daten aus einem Quellmodell zu filtern, bevor sie an Ansichten geliefert werden, und ermöglicht es auch, den Inhalt eines Quellmodells an Ansichten als vorsortierte Daten zu liefern.

Benutzerdefinierte Filtermodelle

Die Klasse QSortFilterProxyModel bietet ein recht vielseitiges Filtermodell, das in einer Vielzahl gängiger Situationen verwendet werden kann. Für fortgeschrittene Benutzer kann QSortFilterProxyModel in Unterklassen unterteilt werden, die einen Mechanismus bereitstellen, mit dem benutzerdefinierte Filter implementiert werden können.

Unterklassen von QSortFilterProxyModel können zwei virtuelle Funktionen reimplementieren, die immer dann aufgerufen werden, wenn ein Modellindex aus dem Proxy-Modell angefordert oder verwendet wird:

  • filterAcceptsColumn() wird verwendet, um bestimmte Spalten aus einem Teil des Quellmodells zu filtern.
  • filterAcceptsRow() wird verwendet, um bestimmte Zeilen aus einem Teil des Quellmodells zu filtern.

Die Standardimplementierungen der oben genannten Funktionen in QSortFilterProxyModel geben true zurück, um sicherzustellen, dass alle Elemente an die Ansichten weitergegeben werden; Neuimplementierungen dieser Funktionen sollten false zurückgeben, um einzelne Zeilen und Spalten herauszufiltern.

Benutzerdefinierte Sortiermodelle

QSortFilterProxyModel Instanzen verwenden die Funktion std::stable_sort(), um Zuordnungen zwischen Elementen im Quellmodell und denen im Proxymodell einzurichten, so dass eine sortierte Hierarchie von Elementen in Ansichten angezeigt werden kann, ohne die Struktur des Quellmodells zu ändern. Um ein benutzerdefiniertes Sortierverhalten bereitzustellen, reimplementieren Sie die Funktion lessThan(), um benutzerdefinierte Vergleiche durchzuführen.

Modell-Subklassifizierungs-Referenz

Modellunterklassen müssen Implementierungen vieler der virtuellen Funktionen bereitstellen, die in der Basisklasse QAbstractItemModel definiert sind. Die Anzahl dieser Funktionen, die implementiert werden müssen, hängt von der Art des Modells ab - ob es Ansichten mit einer einfachen Liste, einer Tabelle oder einer komplexen Hierarchie von Elementen liefert. Modelle, die von QAbstractListModel und QAbstractTableModel erben, können die Vorteile der Standardimplementierungen von Funktionen nutzen, die von diesen Klassen bereitgestellt werden. Modelle, die Datenelemente in baumartigen Strukturen darstellen, müssen Implementierungen für viele der virtuellen Funktionen in QAbstractItemModel bereitstellen.

Die Funktionen, die in einer Modellunterklasse implementiert werden müssen, können in drei Gruppen unterteilt werden:

  • Behandlung von Datenelementen: Alle Modelle müssen Funktionen implementieren, die es Ansichten und Delegierten ermöglichen, die Dimensionen des Modells abzufragen, Elemente zu untersuchen und Daten abzurufen.
  • Navigation und Indexerstellung: Hierarchische Modelle müssen Funktionen bereitstellen, die von Sichten aufgerufen werden können, um in den von ihnen dargestellten baumartigen Strukturen zu navigieren und Modellindizes für Elemente zu erhalten.
  • Unterstützung für Drag & Drop und MIME-Typ-Verarbeitung: Modelle erben Funktionen, die die Art und Weise steuern, wie interne und externe Drag-and-Drop-Operationen durchgeführt werden. Mit diesen Funktionen können Datenelemente in Form von MIME-Typen beschrieben werden, die andere Komponenten und Anwendungen verstehen können.

Handhabung von Datenelementen

Modelle können unterschiedliche Zugriffsstufen auf die von ihnen bereitgestellten Daten bieten: Sie können einfache Nur-Lese-Komponenten sein, einige Modelle können Größenänderungsoperationen unterstützen, und andere können die Bearbeitung von Elementen ermöglichen.

Nur-Lese-Zugriff

Um einen Nur-Lese-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 zu erhalten. In vielen Modellen sollte die Kombination von Flags Qt::ItemIsEnabled und Qt::ItemIsSelectable enthalten.
data()Wird verwendet, um Elementdaten an Ansichten und Delegierte zu liefern. Im Allgemeinen müssen Modelle nur Daten für Qt::DisplayRole und alle anwendungsspezifischen Benutzerrollen bereitstellen, aber es ist auch gute Praxis, Daten für Qt::ToolTipRole, Qt::AccessibleTextRole und Qt::AccessibleDescriptionRole bereitzustellen. Siehe die Qt::ItemDataRole enum Dokumentation für Informationen über die Typen, die mit jeder Rolle verbunden sind.
headerData()Versorgt Ansichten mit Informationen, die in ihren Kopfzeilen angezeigt werden sollen. Die Informationen werden nur von Ansichten abgerufen, die Kopfzeileninformationen anzeigen können.
rowCount()Liefert die Anzahl der Datenzeilen, die das Modell anzeigt.

Diese vier Funktionen müssen in allen Modelltypen implementiert werden, einschließlich Listenmodellen (QAbstractListModel subclasses) und Tabellenmodellen (QAbstractTableModel subclasses).

Zusätzlich müssen die folgenden Funktionen in direkten Unterklassen von QAbstractTableModel und QAbstractItemModel implementiert werden:

columnCount()Gibt die Anzahl der Datenspalten des Modells an. Listenmodelle bieten diese Funktion nicht, da sie bereits in QAbstractListModel implementiert ist.

Editierbare Elemente

Editierbare Modelle ermöglichen die Änderung von Datenelementen und können auch Funktionen bereitstellen, die das Einfügen und Entfernen von Zeilen und Spalten ermöglichen. Um die Bearbeitung zu ermöglichen, müssen die folgenden Funktionen korrekt implementiert sein:

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 Nur-Lese-Modell angewendet werden, Qt::ItemIsEditable enthalten.
setData()Wird verwendet, um das Datenelement zu ändern, das mit einem bestimmten Modellindex verbunden ist. Um Benutzereingaben, die von Elementen der Benutzeroberfläche bereitgestellt werden, akzeptieren zu können, muss diese Funktion Daten verarbeiten, die mit Qt::EditRole verbunden sind. Die Implementierung kann auch Daten akzeptieren, die mit vielen verschiedenen Arten von Rollen verbunden sind, die durch Qt::ItemDataRole spezifiziert werden. Nach der Änderung eines Datenelements müssen die Modelle das Signal dataChanged() aussenden, um andere Komponenten über die Änderung zu informieren.
setHeaderData()Wird verwendet, um horizontale und vertikale Kopfdaten zu ändern. Nach der Änderung der Daten müssen die Modelle das Signal headerDataChanged() aussenden, um andere Komponenten über die Änderung zu informieren.

Veränderbare Modelle

Alle Arten von Modellen können das Einfügen und Entfernen von Zeilen unterstützen. Tabellenmodelle und hierarchische Modelle können auch das Einfügen und Entfernen von Spalten unterstützen. Es ist wichtig, andere Komponenten über Änderungen an den Dimensionen des Modells zu informieren, sowohl bevor als auch nachdem sie auftreten. Daher können die folgenden Funktionen implementiert werden, um die Größenänderung des Modells zu ermöglichen, aber Implementierungen müssen sicherstellen, dass die entsprechenden Funktionen aufgerufen werden, um angehängte Views und Delegates zu benachrichtigen:

insertRows()Wird verwendet, um neue Zeilen und Datenelemente zu allen Arten von Modellen hinzuzufügen. Implementierungen müssen beginInsertRows() aufrufen , bevor neue Zeilen in die zugrunde liegenden Datenstrukturen eingefügt werden, und unmittelbar danach endInsertRows() aufrufen.
removeRows()Dient zum Entfernen von Zeilen und den darin enthaltenen Datenelementen aus allen Modelltypen. Die 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. Die Implementierungen müssen beginInsertColumns() aufrufen , bevor neue Spalten in die zugrunde liegenden 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 den 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 die Operation nur teilweise erfolgreich war; zum Beispiel, wenn weniger als die angegebene Anzahl von Zeilen eingefügt werden konnte. In solchen Fällen sollte das Modell false zurückgeben, um einen Misserfolg anzuzeigen, damit die angeschlossenen Komponenten mit der Situation umgehen können.

Die Signale, die von den Funktionen ausgegeben werden, die in den Implementierungen der Größenänderungs-API aufgerufen werden, geben den angeschlossenen Komponenten die Möglichkeit, Maßnahmen zu ergreifen, bevor die Daten nicht mehr verfügbar sind. Die Kapselung von Einfüge- und Entnahmevorgängen mit Anfangs- und Endfunktionen ermöglicht es dem Modell auch, 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. Für komplexere Änderungen an der Struktur des Modells, die vielleicht eine interne Reorganisation, eine Sortierung der Daten oder eine andere strukturelle Änderung beinhalten, ist es notwendig, die folgende Sequenz durchzuführen:

Diese Sequenz kann für jede Strukturaktualisierung anstelle der höherwertigen und bequemeren geschützten Methoden verwendet werden. Wenn zum Beispiel bei einem Modell mit zwei Millionen Zeilen alle ungeraden Zeilen entfernt werden müssen, sind das 1 Million diskontinuierliche Bereiche mit jeweils 1 Element. Es wäre möglich, beginRemoveRows und endRemoveRows 1 Million Mal zu verwenden, aber das wäre offensichtlich ineffizient. Stattdessen kann dies als eine einzige Layout-Änderung signalisiert werden, die alle erforderlichen persistenten Indizes auf einmal aktualisiert.

Lazy Population von Modelldaten

Die "Lazy Population" von Modelldaten ermöglicht es, Anfragen nach Informationen über das Modell so lange zu verschieben, bis sie von den Ansichten tatsächlich benötigt werden.

Einige Modelle müssen Daten aus entfernten Quellen beziehen oder zeitaufwändige Operationen durchführen, um Informationen über die Art der Datenorganisation zu erhalten. Da die Ansichten in der Regel so viele Informationen wie möglich anfordern, um die Modelldaten korrekt anzuzeigen, kann es sinnvoll sein, die Menge der an sie zurückgegebenen Informationen zu beschränken, um unnötige Folgeanfragen nach Daten zu vermeiden.

In hierarchischen Modellen, bei denen die Suche nach der Anzahl der Kinder eines bestimmten Elements eine kostspielige Operation ist, ist es sinnvoll, sicherzustellen, dass die rowCount()-Implementierung des Modells nur bei Bedarf aufgerufen wird. In solchen Fällen kann die Funktion hasChildren() neu implementiert werden, um eine kostengünstige Möglichkeit für Ansichten zu bieten, das Vorhandensein von Kindern zu prüfen und, im Fall von QTreeView, die entsprechende Dekoration für ihr übergeordnetes Element zu zeichnen.

Unabhängig davon, ob die Neuimplementierung von hasChildren() true oder false zurückgibt, ist es für die Ansicht nicht notwendig, rowCount() aufzurufen, um herauszufinden, wie viele Kinder vorhanden sind. Zum Beispiel muss QTreeView nicht wissen, wie viele Kinder vorhanden sind, wenn das übergeordnete Element nicht erweitert wurde, um sie anzuzeigen.

Wenn bekannt ist, dass viele Einträge Kinder haben werden, ist es manchmal sinnvoll, hasChildren() neu zu implementieren, um unbedingt true zurückzugeben. Dadurch wird sichergestellt, dass jedes Element später auf Kinder untersucht werden kann, während die anfängliche Population der Modelldaten so schnell wie möglich erfolgt. Der einzige Nachteil ist, dass Elemente ohne Unterelemente in einigen Ansichten falsch angezeigt werden können, bis der Benutzer versucht, die nicht vorhandenen Unterelemente anzuzeigen.

Hierarchische Modelle müssen Funktionen bereitstellen, die von den Ansichten aufgerufen werden können, um in den von ihnen dargestellten baumartigen Strukturen zu navigieren und Modellindizes für Elemente zu erhalten.

Eltern und Kinder

Da die Struktur, die den Ansichten angezeigt wird, von der zugrunde liegenden Datenstruktur bestimmt wird, muss jede Modellunterklasse ihre eigenen Modellindizes erstellen, indem sie Implementierungen der folgenden Funktionen bereitstellt:

index()Wenn ein Modellindex für ein übergeordnetes Element gegeben ist, erlaubt diese Funktion Ansichten und Delegierten den Zugriff auf Kinder dieses Elements. Wenn kein gültiges untergeordnetes Element - entsprechend der angegebenen Zeile, Spalte und dem übergeordneten Modellindex - gefunden werden kann, muss die Funktion QModelIndex() zurückgeben, was ein ungültiger Modellindex ist.
parent()Liefert einen Modellindex, der dem übergeordneten Element eines beliebigen untergeordneten Elements entspricht. Wenn der angegebene Modellindex einem Element der obersten Ebene im Modell entspricht oder wenn es kein gültiges übergeordnetes Element im Modell gibt, muss die Funktion einen ungültigen Modellindex zurückgeben, der mit dem leeren QModelIndex()-Konstruktor erstellt wurde.

Beide obigen Funktionen verwenden die createIndex() Fabrikfunktion, um Indizes für andere Komponenten zu erzeugen. Es ist normal, dass Modelle einen eindeutigen Bezeichner an diese Funktion übergeben, um sicherzustellen, dass der Modellindex später wieder mit dem entsprechenden Element verknüpft werden kann.

Unterstützung von Drag & Drop und MIME-Typ-Behandlung

Die Model/View-Klassen unterstützen Drag&Drop-Operationen und bieten ein Standardverhalten, das für viele Anwendungen ausreichend ist. Es ist jedoch auch möglich, die Art und Weise, wie Elemente bei Drag-and-Drop-Operationen kodiert werden, ob sie standardmäßig kopiert oder verschoben werden und wie sie in bestehende Modelle eingefügt werden, anzupassen.

Darüber hinaus implementieren die Convenience-View-Klassen ein spezielles Verhalten, das sich eng an die Erwartungen von Entwicklern anlehnen sollte. Der Abschnitt Convenience Views bietet einen Überblick über dieses Verhalten.

MIME-Daten

Standardmäßig verwenden die eingebauten Modelle und Ansichten einen internen MIME-Typ (application/x-qabstractitemmodeldatalist), um Informationen über Modellindizes zu übermitteln. Dieser spezifiziert Daten für eine Liste von Elementen, die die Zeilen- und Spaltennummern jedes Elements und Informationen über die Rollen, die jedes Element unterstützt, enthalten.

Daten, die mit diesem MIME-Typ kodiert sind, können durch den Aufruf von QAbstractItemModel::mimeData() mit einer QModelIndexList, die die zu serialisierenden Elemente enthält, erhalten werden.

Bei der Implementierung von 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 application/x-qabstractitemmodeldatalist zurückzugeben.

Unterklassen können das Standardobjekt QMimeData von der Basisklasse beziehen und ihm Daten in zusätzlichen Formaten hinzufügen.

Für viele Modelle ist es nützlich, den Inhalt von Elementen in gängigen Formaten bereitzustellen, die durch MIME-Typen wie text/plain und image/png repräsentiert werden. Beachten Sie, dass Bilder, Farben und HTML-Dokumente mit den Funktionen QMimeData::setImageData(), QMimeData::setColorData() und QMimeData::setHtml() leicht zu einem QMimeData Objekt hinzugefügt werden können.

Akzeptieren von abgelegten Daten

Wenn eine Drag&Drop-Operation über eine Ansicht ausgeführt wird, wird das zugrunde liegende Modell abgefragt, um festzustellen, welche Arten von Operationen 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 außer Kraft setzen, unterstützen Kopiervorgänge und den internen Standard-MIME-Typ für Elemente.

Wenn serialisierte Artikeldaten in einer Ansicht abgelegt werden, werden die Daten in das aktuelle Modell mit seiner Implementierung von QAbstractItemModel::dropMimeData() eingefügt. Die Standardimplementierung dieser Funktion überschreibt niemals Daten im Modell; stattdessen wird versucht, die Datenelemente entweder als Geschwister eines Elements oder als Kinder dieses Elements einzufügen.

Um die Vorteile der Standardimplementierung von QAbstractItemModel für den eingebauten MIME-Typ zu nutzen, müssen neue Modelle Neuimplementierungen der folgenden Funktionen bereitstellen:

insertRows()Diese Funktionen ermöglichen dem Modell das automatische Einfügen neuer Daten unter Verwendung der bestehenden Implementierung von QAbstractItemModel::dropMimeData().
insertColumns()
setData()Ermöglicht die Befüllung der neuen Zeilen und Spalten mit Elementen.
setItemData()Diese Funktion bietet eine effizientere Unterstützung für das Einfügen neuer Elemente.

Um andere Formen von Daten zu akzeptieren, müssen diese Funktionen neu implementiert werden:

supportedDropActions()Wird verwendet, um eine Kombination von drop actions zurückzugeben, die die Arten von Drag & Drop-Operationen angibt, die das Modell akzeptiert.
mimeTypes()Wird verwendet, um eine Liste von MIME-Typen zurückzugeben, die vom Modell dekodiert und verarbeitet werden können. Im Allgemeinen sind die MIME-Typen, die für die Eingabe in das Modell unterstützt werden, die gleichen, die es bei der Kodierung von Daten zur Verwendung durch externe Komponenten verwenden kann.
dropMimeData()Führt die eigentliche Dekodierung der durch Drag&Drop-Operationen übertragenen Daten durch, bestimmt, wo im Modell sie gesetzt werden sollen, und fügt bei Bedarf neue Zeilen und Spalten ein. Wie diese Funktion in den Unterklassen implementiert wird, hängt von den Anforderungen an die Daten ab, die das jeweilige Modell bereitstellt.

Wenn die Implementierung der Funktion dropMimeData() die Dimensionen eines Modells durch Einfügen oder Entfernen von Zeilen oder Spalten ändert oder wenn Datenelemente geändert werden, muss darauf geachtet werden, dass alle relevanten Signale ausgegeben werden. Es kann nützlich sein, einfach Neuimplementierungen anderer Funktionen in der Unterklasse aufzurufen, wie setData(), insertRows() und insertColumns(), um sicherzustellen, dass sich das Modell konsistent verhält.

Um sicherzustellen, dass Drag-Operationen richtig funktionieren, ist es wichtig, die folgenden Funktionen, die Daten aus dem Modell entfernen, neu zu implementieren:

Weitere Informationen zum Ziehen und Ablegen mit Elementansichten finden Sie unter Verwenden von Ziehen und Ablegen mit Elementansichten.

Komfortable Ansichten

Die Komfortansichten (QListWidget, QTableWidget und QTreeWidget) setzen die standardmäßige Drag&Drop-Funktionalität außer Kraft und bieten ein weniger flexibles, aber natürlicheres Verhalten, das für viele Anwendungen geeignet ist. Da es z. B. häufiger vorkommt, dass Daten in Zellen in einem QTableWidget abgelegt werden, wobei der vorhandene Inhalt durch die zu übertragenden Daten ersetzt wird, setzt das zugrunde liegende Modell die Daten der Zielelemente, anstatt neue Zeilen und Spalten in das Modell einzufügen. Weitere Informationen zum Ziehen und Ablegen in Komfortansichten finden Sie unter Verwenden von Ziehen und Ablegen mit Elementansichten.

Leistungsoptimierung für große Datenmengen

Die Funktion canFetchMore() prüft, ob in der übergeordneten Datei weitere Daten verfügbar sind und gibt entsprechend true oder false zurück. Die Funktion fetchMore() holt Daten auf der Grundlage des angegebenen übergeordneten Elements ab. Diese beiden Funktionen können z. B. in einer Datenbankabfrage mit inkrementellen Daten kombiniert werden, um ein QAbstractItemModel aufzufüllen. Wir implementieren canFetchMore() neu, um anzuzeigen, ob weitere Daten abgerufen werden müssen, und fetchMore(), um das Modell wie erforderlich aufzufüllen.

Ein weiteres Beispiel wären dynamisch aufgefü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 ihre Standardimplementierung false zurückgibt und nichts tut.

Die Model/View-Klassen

Diese Klassen verwenden das Model/View Design Pattern, bei dem die zugrundeliegenden Daten (im Model) von der Art und Weise, wie die Daten durch den Benutzer dargestellt und bearbeitet werden (in der View), getrennt sind.

QAbstractItemDelegate

Sie dienen zur Anzeige und Bearbeitung von Datenelementen aus einem Modell.

QAbstractItemModel

Die abstrakte Schnittstelle für Elementmodellklassen

QAbstractItemView

Die Grundfunktionalität für Item-View-Klassen

QAbstractListModel

Abstraktes Modell, das zur Erstellung eindimensionaler Listenmodelle unterklassifiziert werden kann

QAbstractProxyModel

Basisklasse für Proxy-Elementmodelle, die Sortier-, Filter- oder andere Datenverarbeitungsaufgaben übernehmen können

QAbstractTableModel

Abstraktes Modell, das unterklassifiziert werden kann, um Tabellenmodelle zu erstellen

QColumnView

Modell/View-Implementierung einer Spaltenansicht

QConcatenateTablesProxyModel

Proxies für mehrere Quellmodelle, Verkettung ihrer Zeilen

QDataWidgetMapper

Mapping zwischen einem Abschnitt eines Datenmodells und Widgets

QFileSystemModel

Datenmodell für das lokale Dateisystem

QHeaderView

Kopfzeile oder Kopfspalte für Elementansichten

QIdentityProxyModel

Verweist auf sein unverändertes Quellmodell

QItemDelegate

Anzeige- und Bearbeitungsmöglichkeiten für Datenelemente aus einem Modell

QItemEditorCreator

Ermöglicht die Erstellung von Item-Editor-Creator-Basen ohne Unterklassifizierung von QItemEditorCreatorBase

QItemEditorCreatorBase

Abstrakte Basisklasse, die bei der Implementierung neuer Item-Editor-Creators unterklassifiziert werden muss

QItemEditorFactory

Widgets für die Bearbeitung von Elementdaten in Ansichten und Delegaten

QItemSelection

Verwaltet Informationen über ausgewählte Elemente in einem Modell

QItemSelectionModel

Verfolgt die ausgewählten Elemente in einer Ansicht

QItemSelectionRange

Verwaltet Informationen über einen Bereich von ausgewählten Elementen in einem Modell

QListView

Listen- oder Symbolansicht auf ein Modell

QListWidget

Element-basiertes Listen-Widget

QListWidgetItem

Element zur Verwendung mit der Element-Ansichtsklasse QListWidget

QModelIndex

Wird zum Auffinden von Daten in einem Datenmodell verwendet

QModelRoleData

Enthält eine Rolle und die mit dieser Rolle verbundenen Daten

QModelRoleDataSpan

Spanne über QModelRoleData-Objekte

QPersistentModelIndex

Wird zum Auffinden von Daten in einem Datenmodell verwendet

QSortFilterProxyModel

Unterstützung für das Sortieren und Filtern von Daten, die zwischen einem anderen Modell und einer Ansicht übergeben werden

QStandardItem

Item zur Verwendung mit der Klasse QStandardItemModel

QStandardItemEditorCreator

Die Möglichkeit, Widgets zu registrieren, ohne die Unterklasse QItemEditorCreatorBase zu benötigen

QStandardItemModel

Generisches Modell zum Speichern von benutzerdefinierten Daten

QStringListModel

Modell, das Strings an Ansichten liefert

QStyledItemDelegate

Anzeige- und Bearbeitungsmöglichkeiten für Datenelemente aus einem Modell

QTableView

Standardmodell/View-Implementierung einer Tabellensicht

QTableWidget

Elementbasierte Tabellenansicht mit einem Standardmodell

QTableWidgetItem

Element zur Verwendung mit der Klasse QTableWidget

QTableWidgetSelectionRange

Möglichkeit zur Interaktion mit der Auswahl in einem Modell ohne Verwendung von Modellindizes und einem Auswahlmodell

QTreeView

Standard Modell/Ansicht Implementierung einer Baumansicht

QTreeWidget

Baumansicht, die ein vordefiniertes Baummodell verwendet

QTreeWidgetItem

Element zur Verwendung mit der Komfortklasse QTreeWidget

QTreeWidgetItemIterator

Möglichkeit zur Iteration über die Elemente in einer QTreeWidget-Instanz

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