Вот мое решение проблемы.
Прежде всего, мое первоначальное предположение, что QModelIndex
не в состоянии сохранить отношения родитель-ребенок, является правильным. На самом деле метод QModelIndex::parent
просто вызывает QAbstractItemModel::parent
, и задача реализации родительского метода остается за классом модели. Когда базовой моделью является правильное дерево, указатель на узлы дерева может быть сохранен в классе QModelIndex
, но в моем случае мы имеем дело с «виртуальным» деревом, и эта связь недоступна. Таким образом, мы вынуждены ввести какое-то дополнительное хранилище, чтобы иметь возможность определить, где мы находимся в дереве. Если бы QModelIndex
изначально поддерживало наличие указателя на родительский индекс, эта проблема была бы решена гораздо проще. Но поскольку QModelIndex
является классом значений, у нас не может быть указатель на родительский элемент, а скорее мы должны хранить все родительские индексы внутри класса QModelIndex
, и, возможно, разработчики Qt нашли какой-то хороший способ не делать этого. Поэтому я сохранил QVector<QModelIndex>
в поле внутреннего указателя QModelIndex
. Есть некоторые вещи, о которых нужно позаботиться, например, избегать выделения больше, чем необходимо, таких индексов, а также помнить об освобождении памяти, когда они больше не нужны (здесь мы не можем использовать иерархию QObject). Могут возникнуть дополнительные проблемы, которые нужно решить, когда модель предназначена для чтения и записи, но в этом случае я имею дело с моделью только для чтения.
Моя реализация следует. Методы rowCount
и data
определяют это виртуальное дерево c. Другие методы можно абстрагировать в классе, который можно использовать повторно.
class MyModel : public QAbstractItemModel
{
Q_OBJECT
private:
struct IndexData
{
QVector<QModelIndex> parents;
};
public:
explicit MyModel(QObject *parent = nullptr);
~MyModel();
QVariant data(const QModelIndex &index, int role) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
protected:
IndexData * indexData(const QModelIndex &index) const;
QList<int> indexPath(const QModelIndex &index) const;
QString indexString(const QModelIndex &index) const;
QString indexString(int row, int column, const QModelIndex &parent) const;
public:
int indexDepth(const QModelIndex &index) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
private:
QMap<QString, IndexData*> indexData_;
Model model;
};
реализация:
MyModel::MyModel(QObject *parent)
: QAbstractItemModel(parent)
{
model.nodes.resize(2);
model.nodes[0].name = "node1";
model.nodes[0].properties.resize(2);
model.nodes[0].properties[0].name = "property1";
model.nodes[0].properties[0].value = "value1";
model.nodes[0].properties[1].name = "property2";
model.nodes[0].properties[1].value = "value2";
model.nodes[1].name = "node2";
model.nodes[1].properties.resize(1);
model.nodes[1].properties[0].name = "property1";
model.nodes[1].properties[0].value = "someValue";
model.tracks.resize(3);
model.tracks[0].length = 2;
model.tracks[0].channel = "A";
model.tracks[1].length = 4;
model.tracks[1].channel = "B";
model.tracks[2].length = 3;
model.tracks[2].channel = "C";
}
MyModel::~MyModel()
{
for(auto v : indexData_) delete v;
}
QVariant MyModel::data(const QModelIndex &index, int role) const
{
if(!index.isValid() || role != Qt::DisplayRole) return {};
int d = indexDepth(index);
auto path = indexPath(index);
if(d == 1) return "Model";
if(d == 2 && path[0] == 0 && path[1] == 0) return "Nodes";
if(d == 2 && path[0] == 0 && path[1] == 1) return "Tracks";
if(d == 3 && path[0] == 0 && path[1] == 0) return QString("Node \"%1\"").arg(model.nodes[path[2]].name);
if(d == 4 && path[0] == 0 && path[1] == 0) return "Properties";
if(d == 5 && path[0] == 0 && path[1] == 0 && path[3] == 0) return QString("Property %1 = %2").arg(model.nodes[path[2]].properties[path[4]].name, model.nodes[path[2]].properties[path[4]].value);
if(d == 3 && path[0] == 0 && path[1] == 1) return QString("Track %1...").arg(index.row() + 1);
return {};
}
QModelIndex MyModel::index(int row, int column, const QModelIndex &parent) const
{
QString dataKey = indexString(row, column, parent);
auto it = indexData_.find(dataKey);
IndexData *data;
if(it == indexData_.end())
{
data = new IndexData;
const_cast<MyModel*>(this)->indexData_.insert(dataKey, data);
if(parent.isValid())
{
data->parents.append(parent);
data->parents.append(indexData(parent)->parents);
}
}
else
{
data = it.value();
}
return createIndex(row, column, data);
}
QModelIndex MyModel::parent(const QModelIndex &index) const
{
if(!index.isValid()) return {};
auto data = indexData(index);
if(data->parents.empty()) return {};
return data->parents.at(0);
}
MyModel::IndexData * MyModel::indexData(const QModelIndex &index) const
{
if(!index.internalPointer()) return nullptr;
return reinterpret_cast<IndexData*>(index.internalPointer());
}
QList<int> MyModel::indexPath(const QModelIndex &index) const
{
QList<int> path;
auto data = indexData(index);
for(int i = data->parents.size() - 1; i >= 0; i--)
path.push_back(data->parents[i].row());
path.push_back(index.row());
return path;
}
QString MyModel::indexString(const QModelIndex &index) const
{
return indexString(index.row(), index.column(), index.parent());
}
QString MyModel::indexString(int row, int column, const QModelIndex &parent) const
{
QString pre = parent.isValid() ? indexString(parent) + "." : "";
return pre + QString("[%1,%2]").arg(row).arg(column);
}
int MyModel::indexDepth(const QModelIndex &index) const
{
if(!index.isValid()) return 0;
return 1 + indexDepth(index.parent());
}
int MyModel::rowCount(const QModelIndex &parent) const
{
if(!parent.isValid()) return 1; // root item
int d = indexDepth(parent);
auto path = indexPath(parent);
//if(d == 0) return 1; // root item
if(d == 1) return 2;
if(d == 2 && path[0] == 0 && path[1] == 0) return model.nodes.size();
if(d == 2 && path[0] == 0 && path[1] == 1) return model.tracks.size();
if(d == 3 && path[0] == 0 && path[1] == 0) return 1;
if(d == 4 && path[0] == 0 && path[1] == 0 && path[3] == 0) return model.nodes[path[2]].properties.size();
return 0;
}
int MyModel::columnCount(const QModelIndex &parent) const
{
return 1;
}
Qt::ItemFlags MyModel::flags(const QModelIndex &index) const
{
if(index.isValid()) return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
return {};
}