在 Qt 中使用 TreeView

提示:本文中的 Demo 已 push 到 github,可忽略本文直接到 我的github 中查看代码。

Qt 提供了 QuickControl TreeView 。但是比较奇葩的是该控件不能直接使用,而需要用户自己扩展实现。
官方给出了一个示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
TreeView {
TableViewColumn {
title: "Name"
role: "fileName"
width: 300
}
TableViewColumn {
title: "Permissions"
role: "filePermissions"
width: 100
}
model: fileSystemModel
}

它声明了一个 TreeView控件,该控件有 2 列,分别为 Name 和 Permissions,还有一个名为 fileSystemModel 的 model 。对用户来说,这里的 model 是一个关键性的对象,它需要用户使用 C++ 实现 ,并注册到 qml 中供 TreeView 使用。按官方的说法, model 是一个 为 tree view 提供数据的属性,它包含了 tree view 将要展示的数据
用户的 model 必须继承于 QAbstractItemModel
该类是一个抽象类,在运行中,treeview 从该类中获取用户数据,再在UI上展示。该类有如下纯虚函数,必须在子类中实现:

1
2
3
4
5
virtual int    columnCount(const QModelIndex &parent = QModelIndex()) const = 0;
virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const = 0;
virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const = 0;
virtual QModelIndex parent(const QModelIndex &index) const = 0;
virtual int rowCount(const QModelIndex &parent = QModelIndex()) const = 0;

columnCount(const QModelIndex &parent = QModelIndex())

返回 parent 的列数。这里的参数 parent 给出默认值,因为一般来说,一个 treeview 中的列数都是固定的。当然 ,如果 parent is valid, 需要返回 0.

rowCount(const QModelIndex &parent = QModelIndex())

返回 parent 下数据的行数。

parent(const QModelIndex &index)

返回给定节点的 index 的父节点的 index .如果其没有父节点,需要返回 QModelIndex() 默认值,否则需要调用 createIndex(int row, int column, quintptr id) 构建父节点的 index并返回。一般来说,只有第 1 列才会有子节点,所以 column 直接给 0 就行了

index(int row, int column, const QModelIndex &parent = QModelIndex())

给定节点的 行、列以及父节点 index,返回该节点的 index

data(const QModelIndex &index, int role = Qt::DisplayRole)

给定节点 index, 返回该节点存储的 role 相关的数据。这里出现了一个参数 role, 它和官方给出的 qml 例子中的 role 是一个东西,都是用来给数据分类的标识。这需要另一个虚函数的配合: QHash<int, QByteArray> QAbstractItemModel::roleNames() const

QAbstractItemModel::roleNames()

它返回角色的 数值 到 字符串 的映射,以将 TableViewColumn 中的 role 与 C++ 中的属性连接起来。

按以上,给出我们实现 QAbstractItemModel 类的定义:
treeview_item.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class TreeViewModel : public QAbstractItemModel
{
Q_OBJECT
public:
enum ItemRoles{
MROLE_Name=Qt::UserRole + 1,
MROLE_CreateDate
};
Q_ENUM(ItemRoles)
public:
TreeViewModel(QObject *parent=nullptr);
virtual int columnCount(const QModelIndex &parent) const override;
virtual QModelIndex index(int row, int column, const QModelIndex &parent) const override;
virtual QVariant data(const QModelIndex &index, int role) const override;
virtual QModelIndex parent(const QModelIndex &child) const override;
virtual int rowCount(const QModelIndex &parent) const override;
virtual QHash<int,QByteArray> roleNames() const override;
private:
TreeViewItem *m_rootItem;
};

该类还定义了两个新的角色,这些角色将在后面的例子中用到。另外该类还有一个成员变量 TreeViewItem *m_rootItem ,它是用户自定义的数据。做为一个树型结构,用户数据的抽象应该有树型数据结构的常用操作。我们将 TreeViewItem 数据结构抽象如下:
treeview_item.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class TreeViewItem
{
public:
TreeViewItem();
TreeViewItem(const QList<QVariant>& data, TreeViewItem* parent);
~TreeViewItem();
void appendChild(TreeViewItem* item);
void clearAllChild();
TreeViewItem* child(int row);
int childCount() const;
int columnCount() const;
QVariant data(int column) const;
TreeViewItem* parent() const;
int row() const;
private:
TreeViewItem* m_parentItem;
QList<TreeViewItem*> m_childItems;
QList<QVariant> m_data;
};

具体的代码可以到 我的 github 中 checkout。Demo 中遍历了输出目录的文件,并添加到TreeView 中。Deme 的效果如下图: