一、介绍
使用MVC架构,Tree View与Tree Widget 相比而言,需要为tree view 设置一个model,使Tree View 能有效降低内存的使用率。下面参考Qt官方提供的demo——Simple Tree Model(说是简单的树试图模型Demo,其实一点都不简单)效果图如下:
二 、 自定义树模型结构TreeItem
使用TreeItem对象把数据存储在模型内部,这些对象以基于指针的树结构链接在一起。通常,每个TreeItem都有一个父项,也可以有许多子项。然而,树结构中的根项没有父项,而且它永远不会在模型之外被引用。每个TreeItem都包含它在树结构中的位置信息;它可以返回父项及其行号。有了这些信息就可以更容易地实现模型。
由于树视图中的每一项通常包含几列数据,因此很自然地将这些信息存储在每一项中。为了简单起见,我们将使用QVariant对象列表来存储项中每一列的数据。
在 tree model中顶级项(上图中的 A、C以及同级的Item)对应的模型索引的父索引(QModelIndex::parent()函数获得)是无效的。各项的数据保存在QModelIndex中。
数据结构如下:
treeitem.h
#include <QList>
#include <QVariant>
class TreeItem
{
public:
TreeItem(const QList<QVariant> &data, TreeItem *parent = nullptr);
~TreeItem();
// 构建子Item列表;
void appendChild(TreeItem *child);
// 获取该Item指定行号的子Item;
TreeItem *child(int row);
// 获取该Item的子Item个数;
int childCount() const;
// 获取该Item的列数(数据段数);
int columnCount() const;
// 获取该Item指定段的数据;
QVariant data(int column) const;
// 获取该item所在parent的row;
int row() const;
// 该Item的父Item;
TreeItem *parentItem();
private:
TreeItem* m_parentItem; // 该Item的父Item;
QList<TreeItem*> m_childItems; // 该Item的子Item列表;
QList<QVariant> m_itemData; // 该Item的各列数据;
};
treeitem.cpp
#include "tree_item.h"
TreeItem::TreeItem(const QList<QVariant> &data, TreeItem *parent)
:m_itemData(data),
m_parentItem(parent)
{
}
TreeItem::~TreeItem()
{
qDeleteAll(m_childItems);
}
void TreeItem::appendChild(TreeItem *child)
{
m_childItems.append(child);
}
TreeItem *TreeItem::child(int row)
{
// 无效的row;
if (row < 0 || row >= m_childItems.size())
return nullptr;
return m_childItems.at(row);
}
int TreeItem::childCount() const
{
return m_childItems.count();
}
int TreeItem::columnCount() const
{
return m_itemData.count();
}
QVariant TreeItem::data(int column) const
{
// 无效的索引返回空的QVariant;
if (column < 0 || column >= m_itemData.size())
return QVariant();
return m_itemData.at(column);
}
int TreeItem::row() const
{
if (m_parentItem)
m_parentItem->m_childItems.indexOf(const_cast<TreeItem*>(this));
// 尽管根项(没有父项)被自动分配了行号0,但模型从不使用此信息。
return 0;
}
TreeItem *TreeItem::parentItem()
{
return m_parentItem;
}
三、自定义Model
一般用TreeModel都是用自己的类,于是,按着文档上说明的,关于继承QAbstractItemModel的时候,必须实现如下几个函数:index(), parent(), rowCount(), columnCount(), data(), 要让Model变成可以编辑的话,必须还要实现 setData(), flags() 这两个函数,让flags()返回值有ItemIsEditable。 同时,还可以实现headerData()和 setHeaderData() 来控制View中的标题。需要重写父类的函数(override标记)每个函数有较详细的解释。
treemodel.h
#ifndef TREEMODEL_H
#define TREEMODEL_H
#include <QAbstractItemModel>
#include <QModelIndex>
#include <QVariant>
class TreeItem;
//! [0]
class TreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit TreeModel(const QString &data, QObject *parent = 0);
~TreeModel();
QVariant data(const QModelIndex &index, int role) const override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
QVariant headerData(int section, Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
QModelIndex index(int row, int column,
const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
private:
void setupModelData(const QStringList &lines, TreeItem *parent);
TreeItem *rootItem;
};
//! [0]
#endif // TREEMODEL_H
treemodel.cpp
#include "treeitem.h"
#include "treemodel.h"
#include <QStringList>
//! [0]
TreeModel::TreeModel(const QString &data, QObject *parent)
: QAbstractItemModel(parent)
{
QList<QVariant> rootData;
rootData << "Title" << "Summary"; // set header
rootItem = new TreeItem(rootData);
setupModelData(data.split(QString("\n")), rootItem);
}
//! [0]
//! [1]
TreeModel::~TreeModel()
{
delete rootItem;
}
//! [1]
//! [2]
int TreeModel::columnCount(const QModelIndex &parent) const
{
if (parent.isValid())
return static_cast<TreeItem*>(parent.internalPointer())->columnCount();
else
return rootItem->columnCount();
}
//! [2]
//! [3]
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
TreeItem *item = static_cast<TreeItem*>(index.internalPointer());
return item->data(index.column());
}
//! [3]
//! [4]
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return 0;
return QAbstractItemModel::flags(index);
}
//! [4]
//! [5]
QVariant TreeModel::headerData(int section, Qt::Orientation orientation,
int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
return rootItem->data(section);
return QVariant();
}
//! [5]
//! [6]
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent)
const
{
if (!hasIndex(row, column, parent))
return QModelIndex();
TreeItem *parentItem;
if (!parent.isValid())
parentItem = rootItem;
else
parentItem = static_cast<TreeItem*>(parent.internalPointer());
TreeItem *childItem = parentItem->child(row);
if (childItem)
return createIndex(row, column, childItem);
else
return QModelIndex();
}
//! [6]
//! [7]
QModelIndex TreeModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return QModelIndex();
TreeItem *childItem = static_cast<TreeItem*>(index.internalPointer());
TreeItem *parentItem = childItem->parentItem();
if (parentItem == rootItem)
return QModelIndex();
return createIndex(parentItem->row(), 0, parentItem);
}
//! [7]
//! [8]
int TreeModel::rowCount(const QModelIndex &parent) const
{
TreeItem *parentItem;
if (parent.column() > 0)
return 0;
if (!parent.isValid())
parentItem = rootItem;
else
parentItem = static_cast<TreeItem*>(parent.internalPointer());
return parentItem->childCount();
}
//! [8]
// 参数1: txt文件所有行的数据,参数2:TreeItem的父节点
void TreeModel::setupModelData(const QStringList &lines, TreeItem *parent)
{
QList<TreeItem*> parents;
QList<int> indentations;
parents << parent;
indentations << 0;
int number = 0;
while (number < lines.count()) {
int position = 0;
// 获取此行中第一个不是' '的位置
while (position < lines[number].length()) {
if (lines[number].at(position) != ' ') {
break;
}
position++;
}
// 获取此位置之后的所有字符串,然后去除字符串前面和后面的的空格
QString lineData = lines[number].mid(position).trimmed();
if (!lineData.isEmpty()) {
//使用“\t”划分字符串为字符串列表.
QStringList columnStrings = lineData.split("\t", QString::SkipEmptyParts);
QList<QVariant> columnData; // 列数据
for (int column = 0; column < columnStrings.count(); ++column)
columnData << columnStrings[column];
// 根据缩进来判断是成为当前分支的兄弟,还是当前分支的儿子
if (position > indentations.last()) {
// The last child of the current parent is now the new parent
// unless the current parent has no children.
//当成为前分支的儿子
if (parents.last()->childCount() > 0) {
parents << parents.last()->child(parents.last()->childCount() - 1);
indentations << position;
}
}
else {
while (position < indentations.last() && parents.count() > 0) {
parents.pop_back();
indentations.pop_back();
}
}
// Append a new item to the current parent's list of children.
parents.last()->appendChild(new TreeItem(columnData, parents.last()));
}
++number;
}
}
四、程序的入口
资源文件
main函数
#include <QApplication>
#include <QFile>
#include <QTreeView>
int main(int argc, char *argv[])
{
Q_INIT_RESOURCE(simpletreemodel);
QApplication app(argc, argv);
QFile file(":/default.txt");
file.open(QIODevice::ReadOnly);
TreeModel model(file.readAll());
file.close();
// 创建以View显示model
QTreeView view;
view.setModel(&model);
view.setWindowTitle(QObject::tr("Simple Tree Model"));
view.show();
return app.exec();
}
参考:
Qt官方demo日拱一卒- simple tree model_feima9999的博客-CSDN博客