文章目录
TreeView
treeView、tableView、listView都是经典的model - view模式。即存在视图 model相互关联及交互流程。
标准model
QT为treeView提供了标准的QStandardItemModel,使用此标准model能够很方便的实现一个简单demo
QStandardItem* itemOne = new QStandardItem("class2");
QStandardItem* itemTwo = new QStandardItem("class3");
model->appendRow(itemOne);
model->appendRow(itemTwo);
QList<QStandardItem*> items;
QStandardItem* item0 = new QStandardItem("name0");
QStandardItem* item1 = new QStandardItem("0");
items << item0 << item1;
itemTwo->appendRow(items);
大概实现的效果如下(层次结构)
实际上使用标准model确实非常方便,但是如果想要使用一些非常规的功能,或者数据量非常大的时候,就需要用到自定义model了。(常规model在初始化tree的过程就比自定义model慢很多,更可怕的是,它所占用的内存开销是自定义model的数倍甚至数十倍!数据量越大内存差距越明显。)
自定义model
我们要将数据显示到QTreeView中,按照Model/View框架介绍,需要定义2个类TreeModel和TreeItem。
TreeModel继承于QAbstractItemModel,用于向View提供数据TreeItem用于定义我们的数据节点,然后被model获取数据。
QTreeView与TreeItem交互过程大致如下:
使用treeView实现一个系统目录的遍历demo
对于某一个系统目录来说,目录下只会有两种类型
- 文件(非文件夹都视为文件)
- 文件夹
但实际上文件也包括文件夹,可以只使用Node_File结构就可以了
struct Node_File{
QString name;
};
struct Node_dir{
QString name;
QVector<Node_File* > file;
QVector<Node_dir* >dir;
};
TreeItem结构
class TreeItem{
public:
enum Type{
UNKNOWN = -1,
DIR,
FILE
};
public :
explicit TreeItem(TreeItem *parent = nullptr);
~TreeItem();
void addChild(TreeItem *item);
void removeChildren();
TreeItem *child(int row) { return _children.value(row); }
TreeItem *parent() { return _parent; }
int childCount() const { return _children.count(); }
QVariant data(int column) const;
//设置、获取节点存的数据指针
void setPtr(void* p) { _ptr = p; }
void* ptr() const { return _ptr; }
// 保存该节点是其父节点的第几个子节点,查询优化所用
void setRow(int row) { _row = row; }
// 返回本节点位于父节点下第几个子节点
int row() const { return _row; }
Type getType() const { return _type; }
void setType(const Type &value) { _type = value; }
void removeRow(TreeItem* row);
private:
QList<TreeItem*> _children; // 子节点
TreeItem *_parent; // 父节点
Type _type; // 此节点保存的数据类型
void* _ptr; // 存储数据的指针
int _row; // 此item位于父节点中第几个
};
TreeModel结构
class TreeItem;
class TreeModel : public QAbstractItemModel
{
Q_OBJECT
public:
explicit TreeModel(const QStringList& headers, QObject *parent = nullptr);
~TreeModel();
TreeItem *root();
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
QVariant data(const QModelIndex &index, int role) const override;
QModelIndex index(int row, int column, const QModelIndex &parent) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent) const override;
int columnCount(const QModelIndex &parent) const override;
void removeRow(int row);
void clear();
public:
TreeItem *itemFromIndex(const QModelIndex &index) const;
private:
QStringList _headers;
TreeItem* _rootItem;
};
几个比较重要的函数:
// 获取表头数据
QVariant TreeModel::headerData(int section, Qt::Orientation orientation,int role) const
{
if (orientation == Qt::Horizontal)
{
if(role == Qt::DisplayRole)
{
return _headers.at(section);
}
}
return QVariant();
}
// 获取index.row行,index.column列数据
QVariant TreeModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();
TreeItem *item = itemFromIndex(index);
if (role == Qt::DisplayRole)
{
return item->data(index.column());
}
return QVariant();
}
// 在parent节点下,第row行,第column列位置上创建索引
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
{
if (!hasIndex(row, column, parent))
return QModelIndex();
TreeItem *parentItem = itemFromIndex(parent);
TreeItem *item = parentItem->child(row);
if (item)
return createIndex(row, column, item);
else
return QModelIndex();
}
// 创建index的父索引
QModelIndex TreeModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return QModelIndex();
TreeItem *item = itemFromIndex(index);
TreeItem *parentItem = item->parent();
if (parentItem == _rootItem)
return QModelIndex();
return createIndex(parentItem->row(), 0, parentItem);
}
// 获取索引parent下有多少行
int TreeModel::rowCount(const QModelIndex &parent) const
{
if (parent.column() > 0)
return 0;
TreeItem* item = itemFromIndex(parent);
return item->childCount();
}
// 返回索引parent下有多少列
int TreeModel::columnCount(const QModelIndex &parent) const
{
return _headers.size();
}
获取目录下文件思路
定义好了两个结构,然后通过ui -> treeView -> setModel(mode),把视图、model相关联起来
然后修改数据只需要对TreeItem里的成员变量进行修改,在视图层就可以展示出来
展示出来的结构如下:
获取目录文件的思路是先获取目录下的第一层文件或文件夹,当双击文件夹后才展开该文件夹下面的所有文件和文件夹(也有一种思路是第一次就获得目录及目录下的所有文件和文件夹,使用递归。但是如果选中文件多的盘会造成等待。所以这里没有这样选择)
//这里的目录结构完全是由TreeItem* root第二个参数控制,如果root是顶则生成的是一级节点,如果root作为参数来的时候是三级节点,那么下面找到的都会挂到三级节点下面
void ShowDirLog::showFile(QString path, TreeItem* root)
{
QDir Qdir = path;
if (!QDir(path).exists(path))
{
return ;
}
QFileInfoList Qflist = Qdir.entryInfoList(QDir::Files|QDir::NoDotAndDotDot|QDir::Dirs);
foreach (auto var, Qflist)
{
//如果抓到了一个路径
if (var.isDir())
{
Node_dir* dir = new Node_dir();
dir -> name = "∨ " + var.absoluteFilePath();
TreeItem* temp = new TreeItem(root);
temp -> setPtr(dir);
temp -> setType(TreeItem::DIR);
root->addChild(temp);
}
//如果抓到了一个文件
else if (var.isFile())
{
Node_File * file = new Node_File();
file -> name =" " + var.fileName();
TreeItem* temp = new TreeItem(root);
temp -> setPtr(file);
temp -> setType(TreeItem::FILE);
root->addChild(temp);
}
}
ui.treeView->doItemsLayout();
return ;
}
右击提供删除功能
//右击Item触发的槽函数
void ShowDirLog::on_treeView_customContextMenuRequested(const QPoint &pos)
{
QMenu menu;
QModelIndex index = ui.treeView->indexAt(pos);//当前点击的元素的index
if (index.isValid())
{
//如果index有效,去deleteItem删除掉这个Item
menu.addAction(("删除"), this, SLOT(deleteItem()));
}
menu.exec(QCursor::pos()); //显示菜单
}
//
void ShowDirLog::deleteItem()
{
QModelIndex index = ui.treeView->currentIndex();
TreeItem *treeItem = model->itemFromIndex(index);
TreeItem* root = treeItem->parent();
root->removeRow(treeItem);
ui.treeView->doItemsLayout();
}
删除前
删除后
demo展示
treeViewDemo