Qt 自定义数据结构,重写QTreeView和QAbstractItemModel。

前言

  一直以来都知道view/model,平时工作也在用,但很多点都很模糊,所以从头写一遍。

效果

在这里插入图片描述

TreeItem:树的数据结构

 数据结构类比较重要的是信息(属性),和对信息的获取、变更的方法。根据自己的需要去定义数据结构即可。
 这里需要实现树结构,所有每个节点的信息有:父节点、子节点列表、信息。
TreeItem.h

#pragma once

#include <QString>
#include <QList>

class TreeItem
{
public:
	TreeItem(TreeItem *parent = nullptr,QString caption = QString(),QString description = QString());
	~TreeItem();

	TreeItem *parent() { return m_parent; }
	void setParent(TreeItem* parent) { this->m_parent = parent; }

	TreeItem *child(int index) { return m_childrens.at(index); }

	int childCount() { return m_childrens.size(); }

	void setCaption(QString caption) { m_caption = caption; }
	QString getCaption() { return m_caption; }

	void setDescription(QString description) { m_description = description; }
	QString getDescription() { return m_description; }

	int indexOf(TreeItem *item) { return m_childrens.indexOf(item); }

	void addItem(TreeItem *item);

private:
	TreeItem*			m_parent;
	QString				m_caption;
	QString				m_description;
	QList<TreeItem *>	m_childrens;
};

TreeItem.cpp

#include "TreeItem.h"

TreeItem::TreeItem(TreeItem * parent, QString caption, QString description )
	:m_parent(parent),m_caption(caption),m_description(description)
{
	if(parent)
		parent->addItem(this);

	m_childrens.clear();
}

TreeItem::~TreeItem()
{
}

void TreeItem::addItem(TreeItem * item)
{
	if (item)
	{
		m_childrens.append(item);
		item->setParent(this);
	}
	else
		Q_ASSERT(0);
}

CModel:继承QAbstractItemModel

必须要重写的五个虚函数:index、parent、rowCount、columnCount、data。针对不同的数据类型,也有不同的实现,这里是针对树结构的实现。
 flag是为了实现可编辑才重写的。非必须。
 setData是为了编辑之后把数据保存到数据结构才重写的。非必须。
CModel.h:

#pragma once
#include <qabstractitemmodel.h>

class TreeItem;

class CModel :
	public QAbstractItemModel
{
public:
	CModel(QObject *parent = nullptr);
	~CModel();
	TreeItem* getTopNode() { return m_topTreeItem; }

	// 通过 QAbstractItemModel 继承
	virtual Q_INVOKABLE QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const override;
	virtual Q_INVOKABLE QModelIndex parent(const QModelIndex & child) const override;
	virtual Q_INVOKABLE int rowCount(const QModelIndex & parent = QModelIndex()) const override;
	virtual Q_INVOKABLE int columnCount(const QModelIndex & parent = QModelIndex()) const override;
	virtual Q_INVOKABLE QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const override;
	Qt::ItemFlags flags(const QModelIndex &index) const;
	bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);

private:
	TreeItem *m_topTreeItem;
};


CModel.cpp:

#include "CModel.h"
#include "TreeItem.h"

CModel::CModel(QObject * parent) :QAbstractItemModel(parent)
{
	m_topTreeItem = new TreeItem();
}

CModel::~CModel()
{
}

Q_INVOKABLE QModelIndex CModel::index(int row, int column, const QModelIndex & parent) const
{
	if (!hasIndex(row, column, parent))
		return Q_INVOKABLE QModelIndex();

	TreeItem *parentItem;
	if (parent.isValid())
		parentItem = (TreeItem *)parent.internalPointer();
	else
		parentItem = m_topTreeItem;

	TreeItem *item = parentItem->child(row);

	return createIndex(row, column, item);
}

Q_INVOKABLE QModelIndex CModel::parent(const QModelIndex & child) const
{
	if (!child.isValid())
		return QModelIndex();

	TreeItem *item = (TreeItem *)child.internalPointer();
	TreeItem *parentItem = item->parent();

	if (parentItem == m_topTreeItem)
		return QModelIndex();

	int row = 0;
	if (parentItem->parent())
		row = parentItem->parent()->indexOf(parentItem);

	return createIndex(row,0,parentItem);
}

Q_INVOKABLE int CModel::rowCount(const QModelIndex & parent) const
{
	TreeItem *parentItem;
	if (parent.column() > 0)
		return 0;

	if (parent.isValid())
		parentItem = (TreeItem *)parent.internalPointer();
	else
		parentItem = m_topTreeItem;

	return parentItem->childCount();
}

Q_INVOKABLE int CModel::columnCount(const QModelIndex & parent) const
{
	return 2;
}

Q_INVOKABLE QVariant CModel::data(const QModelIndex & index, int role) const
{
	if (!index.isValid())
		return QVariant();

	TreeItem *item = (TreeItem *)index.internalPointer();
	int colum = index.column();
	switch (role)
	{
	case Qt::EditRole:
	case Qt::DisplayRole:
		if (index.column() == 0)
		{
			return item->getCaption();
		}
		else if (index.column() == 1)
		{
			return item->getDescription();
		}
		break;
	default:
		break;
	}

	return QVariant();
}

Qt::ItemFlags CModel::flags(const QModelIndex & index) const
{
	return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
}

bool CModel::setData(const QModelIndex & index, const QVariant & value, int role)
{
	if (!index.isValid())
		return false;

	TreeItem *item = (TreeItem *)index.internalPointer();
	int column = index.column();
	if (column == 0)
	{
		item->setCaption(value.toString());
		return true;
	}
	else if (column == 1)
	{
		item->setDescription(value.toString());
		return true;
	}
	else
	{
		Q_ASSERT(0);
	}

	return false;
}

CTreeView:继承QTreeView

 在CTreeView中实现一些具体的操作:添加一级节点、添加子节点。(没写删除节点)
 如果要处理每项之前的图标那块,就重写drawBranches。
 如果要处理每行的显示,(没有代理的情况下),重写drawRow。
CTreeView.h

#pragma once
#include <qtreeview.h>

class CModel;
class CTreeView :
	public QTreeView
{
	Q_OBJECT
public:
	CTreeView(QWidget *parent = nullptr);
	virtual ~CTreeView();
	//设置model
	void setTreeModel(QAbstractItemModel *model);
	//设置CModel
	CModel *getCModel() { return m_model; }

public slots:
	//slot:添加一级节点
	void slotAddOneLevelItem();
	//slot:添加子节点
	void slotAddSubItem();

protected:
	void mousePressEvent(QMouseEvent *event);

private:
	//获得选中行的首列QModelIndex
	QModelIndexList getSelectedIndexList();
	//获得选中的行数
	int32_t getSelectedRows();
	void addFolder(QModelIndex index = QModelIndex(), QString caption = QStringLiteral("newFolder"), QString des = QStringLiteral("newDes"));
	
private:
	CModel *m_model;
};


CTreeView.cpp

#include "CTreeView.h"
#include "CModel.h"
#include "TreeItem.h"

#include <QMouseEvent>

CTreeView::CTreeView(QWidget * parent):QTreeView(parent), m_model(nullptr)
{
	//支持多选
	this->setSelectionMode(QAbstractItemView::ExtendedSelection);
}

CTreeView::~CTreeView()
{
}

void CTreeView::setTreeModel(QAbstractItemModel * model)
{
	m_model = dynamic_cast<CModel*>(model);
	if (!m_model)
		Q_ASSERT(0);

	this->setModel(m_model);
}

//slot:添加一级文件夹
void CTreeView::slotAddOneLevelItem()
{
	//判断选中行数
	int32_t selectedRows = this->getSelectedRows();
	if (selectedRows < 0)
	{
		Q_ASSERT(0);
		return;
	}

	//获得根节点
	TreeItem* topNode = getCModel()->getTopNode();
	if (!topNode)
		Q_ASSERT(0);

	//在根节点添加一级节点
	TreeItem* newItem = new TreeItem(topNode, QStringLiteral("新建文件夹"), QStringLiteral("..."));
	
	//刷新界面
	this->doItemsLayout();
}

//slot:添加子节点
void CTreeView::slotAddSubItem()
{
	//判断选中行数
	int32_t selectedRows = this->getSelectedRows();
	if (selectedRows != 1)
	{
		return;
	}

	QModelIndexList selectedList = this->getSelectedIndexList();
	TreeItem* selectedItem = (TreeItem*)selectedList.at(0).internalPointer();
	if (!selectedItem)
	{
		Q_ASSERT(0);
		return;
	}

	TreeItem* newItem = new TreeItem(selectedItem, QStringLiteral("新建文件夹"), QStringLiteral("..."));

	//刷新界面
	this->doItemsLayout();
}

void CTreeView::mousePressEvent(QMouseEvent * event)
{
	QTreeView::mousePressEvent(event);
}

//获得选中行的首列QModelIndex
QModelIndexList CTreeView::getSelectedIndexList()
{
	//获得所有选中的QModelIndex
	QModelIndexList selectedList = this->selectionModel()->selectedIndexes();

	//只留下每行column为0的QModelIndex
	for (int i = 0; i < selectedList.size(); ++i)
	{
		if (selectedList.at(i).column() != 0)
			selectedList.removeOne(selectedList.at(i));
	}

	return selectedList;
}

//获得选中的行数
int32_t CTreeView::getSelectedRows()
{
	QModelIndexList selectedList = this->getSelectedIndexList();

	return selectedList.size();
}

void CTreeView::addFolder(QModelIndex index, QString caption, QString des)
{
	
}

记录一些点:

  1. QTreeView支持shift多选,ctrl点选:
	m_treeView->setSelectionMode(QAbstractItemView::ExtendedSelection);
  1. 更新了数据结构,调用QTreeView的doItemsLayout()刷新界面

  2. QTreeView的selectedIndexs(),获得是QModelIndexList,其中包含了选中行的所有QModelIndex。比如:选中三行,但是每行有两列,那么List中就有6个QModelIndex

  • 5
    点赞
  • 35
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
Qt自定义Tree(树)是指开发者可以根据自己的需求对Qt中的Tree控件进行定制和扩展。 首先,在Qt中,Tree控件常用的有QTreeWidget和QTreeView两种。 QTreeWidget是一个直接继承自QTreeWidget类的控件,使用它可以很方便地创建一个简单的树形控件。我们可以通过addItem()方法来添加子项,通过setHeaderLabels()方法来设置表头(列标题),以及通过setExpanded()方法来设置节点的展开与折叠。 如果我们需要更复杂的树形控件,就可以使用QTreeViewQTreeView允许我们通过使用自定义的模型(QAbstractItemModel的子类)来完全自定义树形控件的数据和样式。我们可以继承QAbstractItemModel类并实现其抽象方法来创建自己的模型,通过设置setModel()方法将自定义模型与QTreeView关联起来。 在自定义模型中,我们可以通过重写data()方法来返回树形控件中的数据,重写headerData()方法来设置表头数据,重写flags()方法来设置节点的编辑和选择状态等。 此外,如果需要对树形控件中的节点进行自定义绘制,我们可以通过重写QTreeView的paintEvent()方法来实现。在该方法中,我们可以使用painter对象进行绘制,绘制每个节点的背景、文本等内容。 除了模型和绘制,我们还可以使用样式表(Qt Style Sheets)来对树形控件的样式进行自定义。样式表可以设置每个节点的背景、前景颜色,调整行高、缩进等等。 总之,Qt提供了丰富的API和机制,使开发者能够灵活自定义Tree控件。通过继承、重写方法、使用自定义模型、样式表等方式,开发者可以根据需求实现各种复杂的树形控件。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值