一個簡單的樹模型
QModelIndex 實際上並不知道它的父/子索引,它只包含一行,一列和一個指標,並且模型有責任使用這些資料來提供索引關係的資訊。因此,模型需要從儲存在 QModelIndex
內部的 void*
到內部資料型別進行大量轉換。
TreeModel.h:
#pragma once
#include <QAbstractItemModel>
class TreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit TreeModel(QObject *parent = nullptr);
// Reimplementation of QAbstractItemModel methods
int rowCount(const QModelIndex &index) const override;
int columnCount(const QModelIndex &index) const override;
QModelIndex index(const int row, const int column,
const QModelIndex &parent) const override;
QModelIndex parent(const QModelIndex &childIndex) const override;
QVariant data(const QModelIndex &index, const int role) const override;
bool setData(const QModelIndex &index, const QVariant &value,
const int role) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
void addRow(const QModelIndex &parent, const QVector<QVariant> &values);
void removeRow(const QModelIndex &index);
private:
struct Item
{
~Item();
// This could individual members, or maybe some other object that
// contains the data we want to display/edit
QVector<QVariant> values;
// It is this information that the model needs to be able to answer
// questions like "What's the parent QModelIndex of this QModelIndex?"
QVector<Item *> children;
Item *parent = nullptr;
// Convenience method that's used in several places
int rowInParent() const;
};
Item *m_root;
};
TreeModel.cpp:
#include "TreeModel.h"
// Adapt this to own needs
static constexpr int COLUMNS = 3;
TreeModel::Item::~Item()
{
qDeleteAll(children);
}
int TreeModel::Item::rowInParent() const
{
if (parent) {
return parent->children.indexOf(const_cast<Item *>(this));
} else {
return 0;
}
}
TreeModel::TreeModel(QObject *parent)
: QAbstractItemModel(parent), m_root(new Item) {}
int TreeModel::rowCount(const QModelIndex &parent) const
{
// Parent being invalid means we ask for how many rows the root of the
// model has, thus we ask the root item
// If parent is valid we access the Item from the pointer stored
// inside the QModelIndex
return parent.isValid()
? static_cast<Item *>(parent.internalPointer())->children.size()
: m_root->children.size();
}
int TreeModel::columnCount(const QModelIndex &parent) const
{
return COLUMNS;
}
QModelIndex TreeModel::index(const int row, const int column,
const QModelIndex &parent) const
{
// hasIndex checks if the values are in the valid ranges by using
// rowCount and columnCount
if (!hasIndex(row, column, parent)) {
return QModelIndex();
}
// In order to create an index we first need to get a pointer to the Item
// To get started we have either the parent index, which contains a pointer
// to the parent item, or simply the root item
Item *parentItem = parent.isValid()
? static_cast<Item *>(parent.internalPointer())
: m_root;
// We can now simply look up the item we want given the parent and the row
Item *childItem = parentItem->children.at(row);
// There is no public constructor in QModelIndex we can use, instead we need
// to use createIndex, which does a little bit more, like setting the
// model() in the QModelIndex to the model that calls createIndex
return createIndex(row, column, childItem);
}
QModelIndex TreeModel::parent(const QModelIndex &childIndex) const
{
if (!childIndex.isValid()) {
return QModelIndex();
}
// Simply get the parent pointer and create an index for it
Item *parentItem = static_cast<Item*>(childIndex.internalPointer())->parent;
return parentItem == m_root
? QModelIndex() // the root doesn't have a parent
: createIndex(parentItem->rowInParent(), 0, parentItem);
}
QVariant TreeModel::data(const QModelIndex &index, const int role) const
{
// Usually there will be more stuff here, like type conversion from
// QVariant, handling more roles etc.
if (!index.isValid() || role != Qt::DisplayRole) {
return QVariant();
}
Item *item = static_cast<Item *>(index.internalPointer());
return item->values.at(index.column());
}
bool TreeModel::setData(const QModelIndex &index, const QVariant &value,
const int role)
{
// As in data there will usually be more stuff here, like type conversion to
// QVariant, checking values for validity etc.
if (!index.isValid() || role != Qt::EditRole) {
return false;
}
Item *item = static_cast<Item *>(index.internalPointer());
item->values[index.column()] = value;
emit dataChanged(index, index, QVector<int>() << role);
return true;
}
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
if (index.isValid()) {
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
} else {
return Qt::NoItemFlags;
}
}
// Simple add/remove functions to illustrate {begin,end}{Insert,Remove}Rows
// usage in a tree model
void TreeModel::addRow(const QModelIndex &parent,
const QVector<QVariant> &values)
{
Item *parentItem = parent.isValid()
? static_cast<Item *>(parent.internalPointer())
: m_root;
beginInsertRows(parent,
parentItem->children.size(), parentItem->children.size());
Item *item = new Item;
item->values = values;
item->parent = parentItem;
parentItem->children.append(item);
endInsertRows();
}
void TreeModel::removeRow(const QModelIndex &index)
{
if (!index.isValid()) {
return;
}
Item *item = static_cast<Item *>(index.internalPointer());
Q_ASSERT(item != m_root);
beginRemoveRows(index.parent(), item->rowInParent(), item->rowInParent());
item->parent->children.removeOne(item);
delete item;
endRemoveRows();
}