Auf dieser Seite

Beispiel für ein bearbeitbares Baummodell

Dieses Beispiel zeigt, wie ein einfaches, elementbasiertes Baummodell implementiert wird, das mit anderen Klassen im Model-View-Framework verwendet werden kann.

Anwendung, die die Bearbeitung eines Baummodells veranschaulicht

Das Modell unterstützt bearbeitbare Elemente, benutzerdefinierte Kopfzeilen sowie das Einfügen und Entfernen von Zeilen und Spalten. Mit diesen Funktionen ist es auch möglich, neue untergeordnete Elemente einzufügen, was im begleitenden Beispielcode veranschaulicht wird.

Übersicht

Wie in der Referenz zur Modellunterklassenbildung beschrieben, müssen Modelle Implementierungen für den Standardsatz an Modellfunktionen bereitstellen: ` flags()`, ` data()`, ` headerData()`, ` columnCount()` und ` rowCount()`. Darüber hinaus müssen hierarchische Modelle wie dieses Implementierungen für ` index()` und ` parent()` bereitstellen.

Ein bearbeitbares Modell muss Implementierungen von ` setData()` und ` setHeaderData()` bereitstellen und über seine Funktion ` flags()` eine geeignete Kombination von Flags zurückgeben.

Da in diesem Beispiel die Dimensionen des Modells geändert werden können, müssen wir außerdem die Funktionen „ insertRows()“, „ insertColumns()“, „ removeRows()“ und „ removeColumns()“ implementieren.

Entwurf

Wie im Beispiel „Simple Tree Model“ fungiert das Modell lediglich als Wrapper um eine Sammlung von Instanzen einer TreeItem -Klasse. Jedes TreeItem ist so konzipiert, dass es Daten für eine Zeile von Elementen in einer Baumansicht enthält; daher enthält es eine Liste von Werten, die den in den einzelnen Spalten angezeigten Daten entsprechen.

Da „ QTreeView “ eine zeilenorientierte Ansicht auf ein Modell bietet, liegt es nahe, ein zeilenorientiertes Design für Datenstrukturen zu wählen, die über ein Modell Daten an diese Art von Ansicht liefern. Dies macht das Baummodell zwar weniger flexibel und möglicherweise weniger geeignet für den Einsatz mit komplexeren Ansichten, vereinfacht jedoch die Konzeption und erleichtert die Umsetzung.

Schematische Darstellung der Beziehungen zwischen Stamm-, über- und untergeordneten ElementenBeziehungen zwischen internen Elementen

Beim Entwurf einer Datenstruktur für die Verwendung mit einem benutzerdefinierten Modell ist es sinnvoll, das übergeordnete Element jedes Elements über eine Funktion wie `TreeItem::parent()` zugänglich zu machen, da dies das Schreiben der modellspezifischen Funktion ` parent()` erleichtert. Ebenso ist eine Funktion wie `TreeItem::child()` hilfreich bei der Implementierung der Funktion ` index()` des Modells. Dadurch verwaltet jedes ` TreeItem ` Informationen über sein übergeordnetes Element und seine untergeordneten Elemente, was es uns ermöglicht, die Baumstruktur zu durchlaufen.

Das Diagramm zeigt, wie „ TreeItem “-Instanzen über ihre Funktionen „parent()“ und „child()“ miteinander verbunden sind.

Im gezeigten Beispiel lassen sich zwei Elemente der obersten Ebene, A und B, durch Aufruf der Funktion „child()“ des Wurzelelements abrufen, und jedes dieser Elemente gibt über seine Funktion „parent()“ den Wurzelknoten zurück, obwohl dies nur für Element A dargestellt ist.

Jedes „ TreeItem “-Element speichert Daten für jede Spalte der von ihm dargestellten Zeile in seinem privaten Element „ itemData “ (eine Liste von „ QVariant “-Objekten). Da zwischen jeder Spalte in der Ansicht und jedem Eintrag in der Liste eine Eins-zu-Eins-Zuordnung besteht, stellen wir eine einfache „data()“ -Funktion zum Auslesen der Einträge in der Liste „ itemData “ sowie eine „setData()“ -Funktion zur Verfügung, mit der diese geändert werden können. Wie bei anderen Funktionen in diesem Element vereinfacht dies die Implementierung der Modellfunktionen „ data()“ und „ setData()“.

Wir platzieren ein Element an der Wurzel des Elementbaums. Dieses Stamm-Element entspricht dem Null-Modellindex „ QModelIndex()“, der bei der Verarbeitung von Modellindizes verwendet wird, um das übergeordnete Element eines Elements der obersten Ebene darzustellen. Obwohl das Stamm-Element in keiner der Standardansichten sichtbar ist, nutzen wir seine interne Liste von „ QVariant “-Objekten, um eine Liste von Zeichenfolgen zu speichern, die an die Ansichten übergeben werden, um als horizontale Kopfzeilen verwendet zu werden.

Darstellung des Baummodells mit Zeilen- und SpaltenindizesZugriff auf Daten über das Modell

Im im Diagramm dargestellten Fall kann die durch a dargestellte Information mithilfe der Standard-Modell/Ansicht-API abgerufen werden:

QVariant a = model->index(0, 0, QModelIndex()).data();

Da jedes „item“ Daten für jede Spalte einer bestimmten Zeile enthält, kann es viele Modellindizes geben, die auf dasselbe „ TreeItem “-Objekt verweisen. Beispielsweise lässt sich die durch „b“ dargestellte Information mit dem folgenden Code abrufen:

QVariant b = model->index(1, 0, QModelIndex()).data();

Um Informationen für die anderen Modellindizes in derselben Zeile wie „b“ abzurufen, würde auf dasselbe zugrunde liegende „ TreeItem “-Objekt zugegriffen werden.

In der Modellklasse „ TreeModel “ verknüpfen wir „ TreeItem “-Objekte mit Modellindizes, indem wir bei der Erstellung des entsprechenden Modellindexes mit „ QAbstractItemModel::createIndex()“ in unseren Implementierungen von „index()“ und „parent()“ für jedes Element einen Zeiger übergeben. Wir können die auf diese Weise gespeicherten Zeiger abrufen, indem wir die Funktion ` internalPointer()` für den entsprechenden Modellindex aufrufen – wir erstellen unsere eigene Funktion `getItem()`, die diese Aufgabe für uns übernimmt, und rufen sie aus unseren Implementierungen von `data()` und `parent() ` auf.

Das Speichern von Zeigern auf Elemente ist praktisch, wenn wir die Erstellung und Löschung dieser Elemente kontrollieren, da wir davon ausgehen können, dass eine über ` internalPointer()` erhaltene Adresse ein gültiger Zeiger ist. Manche Modelle müssen jedoch Elemente verarbeiten, die von anderen Komponenten eines Systems bereitgestellt werden, und in vielen Fällen ist es nicht möglich, die Erstellung oder Löschung von Elementen vollständig zu kontrollieren. In solchen Situationen muss ein rein zeigerbasierter Ansatz durch Sicherheitsvorkehrungen ergänzt werden, um sicherzustellen, dass das Modell nicht versucht, auf bereits gelöschte Elemente zuzugreifen.

Speichern von Informationen in der zugrunde liegenden Datenstruktur

Mehrere Datenelemente werden als „ QVariant “-Objekte im Mitglied „ itemData “ jeder „ TreeItem “-Instanz gespeichert.

Das Diagramm zeigt, wie Informationen, die in den beiden vorherigen Diagrammen durch die Bezeichnungen a, b und c dargestellt wurden, in den Elementen A, B und C der zugrunde liegenden Datenstruktur gespeichert sind. Beachten Sie, dass Informationen aus derselben Zeile im Modell alle aus demselben Element stammen. Jedes Element in einer Liste entspricht einer Information, die von jeder Spalte in einer bestimmten Zeile im Modell bereitgestellt wird.

Schematische Darstellung einer möglichen Datenstruktur unter Verwendung von QVariant

Da die Implementierung von „ TreeModel “ für die Verwendung mit „ QTreeView “ konzipiert wurde, haben wir eine Einschränkung hinsichtlich der Verwendung von „ TreeItem “-Instanzen hinzugefügt: Jedes Element muss die gleiche Anzahl von Datenspalten bereitstellen. Dies sorgt für eine konsistente Darstellung des Modells, da wir anhand des Stammelements die Anzahl der Spalten für jede beliebige Zeile ermitteln können, und erfordert lediglich, dass wir Elemente erstellen, die genügend Daten für die Gesamtzahl der Spalten enthalten. Infolgedessen sind das Einfügen und Entfernen von Spalten zeitaufwändige Vorgänge, da wir den gesamten Baum durchlaufen müssen, um jedes Element anzupassen.

Ein alternativer Ansatz wäre, die Klasse „ TreeModel “ so zu gestalten, dass sie die Datenliste in einzelnen „ TreeItem “-Instanzen bei Änderungen an den Datenelementen verkürzt oder erweitert. Dieser „lazy“-Ansatz zur Größenanpassung würde es uns jedoch nur erlauben, Spalten am Ende jeder Zeile einzufügen oder zu entfernen, und würde das Einfügen oder Entfernen von Spalten an beliebigen Positionen innerhalb jeder Zeile nicht zulassen.

Schematische Darstellung des Zugriffs auf das Baummodell mithilfe von ModellindizesVerknüpfung von Elementen mithilfe von Modellindizes

Wie im Beispiel des „Simple Tree Model“ muss die ` TreeModel ` in der Lage sein, einen Modellindex zu übernehmen, die entsprechende ` TreeItem` zu finden und die Modellindizes zurückzugeben, die ihren Eltern- und Kinderelementen entsprechen.

Im Diagramm zeigen wir anhand der in einem vorherigen Diagramm dargestellten Elemente, wie die `parent()` -Implementierung des Modells den Modellindex ermittelt, der dem vom Aufrufer übergebenen Elternelement eines Elements entspricht.

Ein Zeiger auf Element C wird mithilfe der Funktion ` QModelIndex::internalPointer()` aus dem entsprechenden Modellindex abgerufen. Der Zeiger wurde bei der Erstellung intern im Index gespeichert. Da das untergeordnete Element einen Zeiger auf sein übergeordnetes Element enthält, verwenden wir dessen `parent()` -Funktion, um einen Zeiger auf Element B zu erhalten. Der Modellindex des übergeordneten Elements wird mithilfe der Funktion ` QAbstractItemModel::createIndex()` erstellt, wobei der Zeiger auf Element B als interner Zeiger übergeben wird.

Definition der Klasse `TreeItem`

Die Klasse „ TreeItem “ stellt einfache Elemente bereit, die mehrere Datenelemente enthalten, darunter Informationen über ihre über- und untergeordneten Elemente:

class TreeItem
{
public:
    explicit TreeItem(QVariantList data, TreeItem *parent = nullptr);

    TreeItem *child(int number);
    int childCount() const;
    int columnCount() const;
    QVariant data(int column) const;
    bool insertChildren(int position, int count, int columns);
    bool insertColumns(int position, int columns);
    TreeItem *parent();
    bool removeChildren(int position, int count);
    bool removeColumns(int position, int columns);
    int row() const;
    bool setData(int column, const QVariant &value);

private:
    std::vector<std::unique_ptr<TreeItem>> m_childItems;
    QVariantList itemData;
    TreeItem *m_parentItem;
};

Wir haben die API so gestaltet, dass sie der von QAbstractItemModel ähnelt, indem wir jedem Element Funktionen zur Verfügung stellen, mit denen die Anzahl der Spalten, das Lesen und Schreiben von Daten sowie das Einfügen und Entfernen von Spalten möglich sind. Allerdings machen wir die Beziehung zwischen den Elementen explizit, indem wir Funktionen bereitstellen, die sich auf „Kinder“ statt auf „Zeilen“ beziehen.

Jedes Element enthält eine Liste von Zeigern auf untergeordnete Elemente, einen Zeiger auf sein übergeordnetes Element sowie eine Liste von „ QVariant “-Objekten, die den Informationen entsprechen, die in den Spalten einer bestimmten Zeile im Modell gespeichert sind.

Implementierung der Klasse „TreeItem“

Jedes ` TreeItem ` wird mit einer Liste von Daten und einem optionalen übergeordneten Element erstellt:

TreeItem::TreeItem(QVariantList data, TreeItem *parent)
    : itemData(std::move(data)), m_parentItem(parent)
{}

Anfangs hat jedes Element keine untergeordneten Elemente. Diese werden mithilfe der später beschriebenen Funktion „ insertChildren() “ zum internen „ childItems “-Mitglied des Elements hinzugefügt.

Die untergeordneten Elemente werden in `std::unique_ptr` gespeichert, um sicherzustellen, dass jedes dem Element hinzugefügte untergeordnete Element gelöscht wird, wenn das Element selbst gelöscht wird.

Da jedes Element einen Zeiger auf sein übergeordnetes Element speichert, ist die Funktion ` parent() ` trivial:

TreeItem *TreeItem::parent()
{
    return m_parentItem;
}

Drei Funktionen liefern Informationen über die untergeordneten Elemente eines Elements. ` child() ` gibt ein bestimmtes untergeordnetes Element aus der internen Liste der untergeordneten Elemente zurück:

TreeItem *TreeItem::child(int number)
{
    return (number >= 0 && number < childCount())
        ? m_childItems.at(number).get() : nullptr;
}

Die Funktion „ childCount() “ gibt die Gesamtzahl der untergeordneten Elemente zurück:

int TreeItem::childCount() const
{
    return int(m_childItems.size());
}

Die Funktion „ row() “ wird verwendet, um den Index eines Kindes in der Liste der Kinder seines Elternelements zu ermitteln. Sie greift direkt auf das Element „ childItems “ des Elternelements zu, um diese Information zu erhalten:

int TreeItem::row() const
{
    if (!m_parentItem)
        return 0;
    const auto it = std::find_if(m_parentItem->m_childItems.cbegin(), m_parentItem->m_childItems.cend(),
                                 [this](const std::unique_ptr<TreeItem> &treeItem) {
        return treeItem.get() == this;
    });

    if (it != m_parentItem->m_childItems.cend())
        return std::distance(m_parentItem->m_childItems.cbegin(), it);
    Q_ASSERT(false); // should not happen
    return -1;
}

Das Stammelement hat kein übergeordnetes Element; für dieses Element geben wir den Wert Null zurück, um die Konsistenz mit den anderen Elementen zu gewährleisten.

Die Funktion ` columnCount() ` gibt einfach die Anzahl der Elemente in der internen Liste „ itemData “ der Objekte vom Typ „ QVariant “ zurück:

int TreeItem::columnCount() const
{
    return int(itemData.count());
}

Daten werden mithilfe der Funktion ` data() ` abgerufen, die auf das entsprechende Element in der Liste ` itemData ` zugreift:

QVariant TreeItem::data(int column) const
{
    return itemData.value(column);
}

Daten werden mithilfe der Funktion ` setData() ` gesetzt, die Werte nur für gültige Listenindizes in der Liste ` itemData ` speichert, die den Spaltenwerten im Modell entsprechen:

bool TreeItem::setData(int column, const QVariant &value)
{
    if (column < 0 || column >= itemData.size())
        return false;

    itemData[column] = value;
    return true;
}

Um die Implementierung des Modells zu vereinfachen, geben wir „true“ zurück, um anzuzeigen, dass die Daten erfolgreich gesetzt wurden.

Bearbeitbare Modelle müssen oft in der Größe anpassbar sein, damit Zeilen und Spalten eingefügt und entfernt werden können. Das Einfügen von Zeilen unterhalb eines bestimmten Modellindexes im Modell führt zum Einfügen neuer untergeordneter Elemente in das entsprechende Element, was von der Funktion ` insertChildren() ` abgewickelt wird:

bool TreeItem::insertChildren(int position, int count, int columns)
{
    if (position < 0 || position > qsizetype(m_childItems.size()))
        return false;

    for (int row = 0; row < count; ++row) {
        QVariantList data(columns);
        m_childItems.insert(m_childItems.cbegin() + position,
                std::make_unique<TreeItem>(data, this));
    }

    return true;
}

Dadurch wird sichergestellt, dass neue Elemente mit der erforderlichen Anzahl an Spalten erstellt und an einer gültigen Position in der internen Liste „ childItems “ eingefügt werden. Elemente werden mit der Funktion „ removeChildren() “ entfernt:

bool TreeItem::removeChildren(int position, int count)
{
    if (position < 0 || position + count > qsizetype(m_childItems.size()))
        return false;

    for (int row = 0; row < count; ++row)
        m_childItems.erase(m_childItems.cbegin() + position);

    return true;
}

Wie oben erläutert, werden die Funktionen zum Einfügen und Entfernen von Spalten anders verwendet als die zum Einfügen und Entfernen von untergeordneten Elementen, da sie für jedes Element im Baum aufgerufen werden sollen. Dazu rufen wir diese Funktion rekursiv für jedes untergeordnete Element des Elements auf:

bool TreeItem::insertColumns(int position, int columns)
{
    if (position < 0 || position > itemData.size())
        return false;

    for (int column = 0; column < columns; ++column)
        itemData.insert(position, QVariant());

    for (auto &child : std::as_const(m_childItems))
        child->insertColumns(position, columns);

    return true;
}

Definition der Klasse „TreeModel“

Die Klasse „ TreeModel “ stellt eine Implementierung der Klasse „ QAbstractItemModel “ bereit und stellt die erforderliche Schnittstelle für ein Modell bereit, das bearbeitet und in der Größe angepasst werden kann.

class TreeModel : public QAbstractItemModel
{
    Q_OBJECT

public:
    Q_DISABLE_COPY_MOVE(TreeModel)

    TreeModel(const QStringList &headers, const QString &data,
              QObject *parent = nullptr);
    ~TreeModel() override;

Der Konstruktor und der Destruktor sind spezifisch für dieses Modell.

    QVariant data(const QModelIndex &index, int role) const override;
    QVariant headerData(int section, Qt::Orientation orientation,
                        int role = Qt::DisplayRole) const override;

    QModelIndex index(int row, int column,
                      const QModelIndex &parent = {}) const override;
    QModelIndex parent(const QModelIndex &index) const override;

    int rowCount(const QModelIndex &parent = {}) const override;
    int columnCount(const QModelIndex &parent = {}) const override;

Schreibgeschützte Baummodelle müssen lediglich die oben genannten Funktionen bereitstellen. Die folgenden öffentlichen Funktionen bieten Unterstützung für die Bearbeitung und Größenanpassung:

    Qt::ItemFlags flags(const QModelIndex &index) const override;
    bool setData(const QModelIndex &index, const QVariant &value,
                 int role = Qt::EditRole) override;
    bool setHeaderData(int section, Qt::Orientation orientation,
                       const QVariant &value, int role = Qt::EditRole) override;

    bool insertColumns(int position, int columns,
                       const QModelIndex &parent = {}) override;
    bool removeColumns(int position, int columns,
                       const QModelIndex &parent = {}) override;
    bool insertRows(int position, int rows,
                    const QModelIndex &parent = {}) override;
    bool removeRows(int position, int rows,
                    const QModelIndex &parent = {}) override;

private:
    void setupModelData(const QList<QStringView> &lines);
    TreeItem *getItem(const QModelIndex &index) const;

    std::unique_ptr<TreeItem> rootItem;
};

Zur Vereinfachung dieses Beispiels werden die vom Modell bereitgestellten Daten durch die Funktion `setupModelData()` des Modells in einer Datenstruktur organisiert. Viele Modelle in der Praxis verarbeiten die Rohdaten gar nicht, sondern arbeiten einfach mit einer bestehenden Datenstruktur oder einer Bibliotheks-API.

Implementierung der Klasse `TreeModel`

Der Konstruktor erstellt ein Stammelement und initialisiert es mit den bereitgestellten Kopfdaten:

TreeModel::TreeModel(const QStringList &headers, const QString &data, QObject *parent)
    : QAbstractItemModel(parent)
{
    QVariantList rootData;
    for (const QString &header : headers)
        rootData << header;

    rootItem = std::make_unique<TreeItem>(rootData);
    setupModelData(QStringView{data}.split(u'\n'));
}

Wir rufen die interne Funktion `setupModelData()` auf, um die übergebenen Textdaten in eine Datenstruktur zu konvertieren, die wir mit dem Modell verwenden können. Andere Modelle können mit einer vorgefertigten Datenstruktur initialisiert werden oder eine API aus einer Bibliothek nutzen, die ihre eigenen Daten verwaltet.

TreeModel::~TreeModel() = default;

Der Destruktor muss lediglich das Stammelement löschen, wodurch alle untergeordneten Elemente rekursiv gelöscht werden. Dies erfolgt automatisch durch den Standarddestruktor, da das Stammelement in einem `unique_ptr` gespeichert ist.

Da die Schnittstelle des Modells zu den anderen Modell-/Ansichtskomponenten auf Modellindizes basiert und da die interne Datenstruktur elementbasiert ist, müssen viele der vom Modell implementierten Funktionen in der Lage sein, einen beliebigen Modellindex in das entsprechende Element umzuwandeln. Aus Gründen der Einfachheit und Konsistenz haben wir eine Funktion ` getItem() ` definiert, um diese sich wiederholende Aufgabe zu übernehmen:

TreeItem *TreeModel::getItem(const QModelIndex &index) const
{
    if (index.isValid()) {
        if (auto *item = static_cast<TreeItem*>(index.internalPointer()))
            return item;
    }
    return rootItem.get();
}

Jeder an diese Funktion übergebene Modellindex sollte einem gültigen Element im Speicher entsprechen. Ist der Index ungültig oder verweist sein interner Zeiger nicht auf ein gültiges Element, wird stattdessen das Stammelement zurückgegeben.

Die Implementierung von ` rowCount() ` im Modell ist einfach: Zunächst wird mit der Funktion ` getItem() ` das entsprechende Element abgerufen; anschließend wird die Anzahl der darin enthaltenen untergeordneten Elemente zurückgegeben:

int TreeModel::rowCount(const QModelIndex &parent) const
{
    if (parent.isValid() && parent.column() > 0)
        return 0;

    const TreeItem *parentItem = getItem(parent);

    return parentItem ? parentItem->childCount() : 0;
}

Im Gegensatz dazu muss die Implementierung von ` columnCount() ` nicht nach einem bestimmten Element suchen, da alle Elemente so definiert sind, dass ihnen dieselbe Anzahl von Spalten zugeordnet ist.

int TreeModel::columnCount(const QModelIndex &parent) const
{
    Q_UNUSED(parent);
    return rootItem->columnCount();
}

Daher kann die Anzahl der Spalten direkt aus dem Stammelement ermittelt werden.

Damit Elemente bearbeitet und ausgewählt werden können, muss die Funktion ` flags() ` so implementiert werden, dass sie eine Kombination aus Flags zurückgibt, die die Flags ` Qt::ItemIsEditable ` und ` Qt::ItemIsSelectable ` sowie ` Qt::ItemIsEnabled` enthält:

Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
    if (!index.isValid())
        return Qt::NoItemFlags;

    return Qt::ItemIsEditable | QAbstractItemModel::flags(index);
}

Das Modell muss in der Lage sein, Modellindizes zu generieren, damit andere Komponenten Daten und Informationen über seine Struktur abfragen können. Diese Aufgabe wird von der Funktion ` index() ` übernommen, mit der Modellindizes abgerufen werden, die den untergeordneten Elementen eines bestimmten übergeordneten Elements entsprechen:

QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
{
    if (parent.isValid() && parent.column() != 0)
        return {};

In diesem Modell geben wir Modellindizes für untergeordnete Elemente nur dann zurück, wenn der übergeordnete Index ungültig ist (was dem Stammelement entspricht) oder wenn er die Spaltennummer Null aufweist.

Wir verwenden die benutzerdefinierte Funktion `getItem()`, um eine ` TreeItem `-Instanz abzurufen, die dem übergebenen Modellindex entspricht, und fordern deren untergeordnetes Element an, das der angegebenen Zeile entspricht.

    TreeItem *parentItem = getItem(parent);
    if (!parentItem)
        return {};

    if (auto *childItem = parentItem->child(row))
        return createIndex(row, column, childItem);
    return {};
}

Da jedes Element Informationen für eine gesamte Datenzeile enthält, erstellen wir einen Modellindex, um es eindeutig zu identifizieren, indem wir ` createIndex()` mit den Zeilen- und Spaltennummern sowie einem Zeiger auf das Element aufrufen. In der Funktion `data()` verwenden wir den Item-Zeiger und die Spaltennummer, um auf die mit dem Modellindex verknüpften Daten zuzugreifen; in diesem Modell wird die Zeilennummer zur Identifizierung der Daten nicht benötigt.

Die Funktion ` parent() ` stellt Modellindizes für die übergeordneten Elemente von Elementen bereit, indem sie das entsprechende Element für einen gegebenen Modellindex ermittelt, mithilfe der Funktion `parent()` dessen übergeordnetes Element abruft und anschließend einen Modellindex erstellt, der das übergeordnete Element repräsentiert. (Siehe das obige Diagramm).

QModelIndex TreeModel::parent(const QModelIndex &index) const
{
    if (!index.isValid())
        return {};

    TreeItem *childItem = getItem(index);
    TreeItem *parentItem = childItem ? childItem->parent() : nullptr;

    return (parentItem != rootItem.get() && parentItem != nullptr)
        ? createIndex(parentItem->row(), 0, parentItem) : QModelIndex{};
}

Elemente ohne übergeordnete Elemente, einschließlich des Stammelements, werden durch die Rückgabe eines Null-Modellindexes behandelt. Andernfalls wird ein Modellindex erstellt und wie in der Funktion `index()` zurückgegeben, mit einer geeigneten Zeilennummer, jedoch mit der Spaltennummer Null, um mit dem in der Implementierung von `index()` verwendeten Schema konsistent zu sein.

Testen des Modells

Die korrekte Implementierung eines Elementmodells kann eine Herausforderung darstellen. Die Klasse ` QAbstractItemModelTester ` aus dem Qt Test Moduls prüft die Konsistenz des Modells, beispielsweise die Erstellung des Modellindexes und die Eltern-Kind-Beziehungen.

Sie können Ihr Modell testen, indem Sie einfach eine Modellinstanz an den Konstruktor der Klasse übergeben, beispielsweise im Rahmen eines Qt-Unit-Tests:

class TestEditableTreeModel : public QObject
{
    Q_OBJECT

private slots:
    void testTreeModel();
};

void TestEditableTreeModel::testTreeModel()
{
    constexpr auto fileName = ":/default.txt"_L1;
    QFile file(fileName);
    QVERIFY2(file.open(QIODevice::ReadOnly | QIODevice::Text),
             qPrintable(fileName + " cannot be opened: "_L1 + file.errorString()));

   const QStringList headers{"column1"_L1, "column2"_L1};
    TreeModel model(headers, QString::fromUtf8(file.readAll()));

    QAbstractItemModelTester tester(&model);
}

QTEST_APPLESS_MAIN(TestEditableTreeModel)

#include "test.moc"

Um einen Test zu erstellen, der mit dem Programm „ ctest “ ausgeführt werden kann, wird „ add_test() “ verwendet:

# Unit Test

include(CTest)

qt_add_executable(editabletreemodel_tester
    test.cpp
    treeitem.cpp treeitem.h
    treemodel.cpp treemodel.h)

target_link_libraries(editabletreemodel_tester PRIVATE
    Qt6::Core
    Qt6::Test
)

if(ANDROID)
    target_link_libraries(editabletreemodel_tester PRIVATE
        Qt6::Gui
    )
endif()

qt_add_resources(editabletreemodel_tester "editabletreemodel_tester"
    PREFIX
        "/"
    FILES
        ${editabletreemodel_resource_files}
)

add_test(NAME editabletreemodel_tester
         COMMAND editabletreemodel_tester)

Beispielprojekt @ code.qt.io

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