一、介绍

使用MVC架构,Tree View与Tree Widget 相比而言,需要为tree view 设置一个model,使Tree View 能有效降低内存的使用率。下面参考Qt官方提供的demo——Simple Tree Model(说是简单的树试图模型Demo,其实一点都不简单)效果图如下:

QTreemodel 自定义 model_ide

二 、 自定义树模型结构TreeItem

使用TreeItem对象把数据存储在模型内部,这些对象以基于指针的树结构链接在一起。通常,每个TreeItem都有一个父项,也可以有许多子项。然而,树结构中的根项没有父项,而且它永远不会在模型之外被引用。每个TreeItem都包含它在树结构中的位置信息;它可以返回父项及其行号。有了这些信息就可以更容易地实现模型。

由于树视图中的每一项通常包含几列数据,因此很自然地将这些信息存储在每一项中。为了简单起见,我们将使用QVariant对象列表来存储项中每一列的数据。

QTreemodel 自定义 model_#include_02

在 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;
    }
}

 四、程序的入口

资源文件

QTreemodel 自定义 model_qt_03

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博客

Qt —— 细说自定义Tree View Model_ypy9323的博客-CSDN博客

QT Tree model_zhu_nn的博客-CSDN博客