TreeView 自定义model && 实现一个系统目录层次结构

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

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
自定义C++ model 实现 QML 的 TreeView,需要遵循以下步骤: 1. 创建一个 C++ 类,继承自 QAbstractItemModel。 2. 实现必要的虚函数,包括 rowCount、columnCount、parent、index、data 等。 3. 在 model 中创建形结构。 4. 在 QML 中创建 TreeView 控件,并绑定数据源为自定义C++ model。 5. 为 TreeView 控件定义 itemDelegate,以便在 TreeView 中显示自定义的数据。 以下是一个简单的示例: C++ model: ```c++ class TreeModel : public QAbstractItemModel { Q_OBJECT public: explicit TreeModel(QObject *parent = nullptr); ~TreeModel(); // 实现必要的虚函数 QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override; QModelIndex parent(const QModelIndex &child) const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override; int columnCount(const QModelIndex &parent = QModelIndex()) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; Qt::ItemFlags flags(const QModelIndex &index) const override; private: // 存储形结构的数据 QList<TreeItem*> m_items; }; ``` QML TreeView: ```qml TreeView { model: myModel itemDelegate: Rectangle { width: 200 height: 30 Text { text: model.name // 自定义数据 anchors.centerIn: parent } } } ``` 在 TreeModel 中,需要实现一个 TreeItem 类来存储形结构的数据。在 data 函数中,可以根据需要返回不同的数据,例如 name、icon 等。在 QML 中,可以通过 itemDelegate 自定义每个 item 的显示方式。 需要注意的是,自定义C++ model 在 QML 中使用时需要在 main 函数中注册。可以使用 qmlRegisterType 函数将自定义C++ 类注册到 QML 中,例如: ```c++ qmlRegisterType<TreeModel>("MyModel", 1, 0, "TreeModel"); ``` 这样就可以在 QML 中使用 TreeModel 作为数据源了。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值