目标

用qt的模型-视图框架实现树型层次节点的显示,从QAbstractItemModel派生自己的模型类MyTreeItemModel,用boost::property_tree::ptree操作树型数据结构,为了演示,此处只实现了个只读的模型

基于Qt的Model-View显示树形数据_Model-View

MyTreeItemModel的定义

#pragma once

#include <QAbstractItemModel>
#include "boost/property_tree/ptree.hpp"

class MyTreeItemModel : public QAbstractItemModel
{
	Q_OBJECT

public:
	MyTreeItemModel(QObject *parent = 0);
	~MyTreeItemModel();

	virtual QModelIndex index(int row, int column,
		const QModelIndex &parent = QModelIndex()) const override;
	virtual QModelIndex parent(const QModelIndex &child) const override;
	virtual int rowCount(const QModelIndex &parent = QModelIndex()) const override;
	virtual int columnCount(const QModelIndex &parent = QModelIndex()) const override;
	virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
	virtual Qt::ItemFlags flags(const QModelIndex &index) const override;
private:
	const boost::property_tree::ptree* GetParent(const boost::property_tree::ptree* pChild,
		int& nParentRow ) const;

private:
	// 创建一个property_tree
	boost::property_tree::ptree m_TreeItem;
};

MyTreeItemModel的实现

  1. 简化代码,直接在构造函数创建出树型数据结构,用的是boost库的property_tree::ptree
MyTreeItemModel::MyTreeItemModel(QObject *parent)
	: QAbstractItemModel(parent)
{
	// 添加数据
	m_TreeItem.put("node", "root value");
	m_TreeItem.put("node.child1", "child1 value1");
	m_TreeItem.put("node.child2", "child2 value2");
	m_TreeItem.put("node.child3", "child3 value3");

	// 添加子节点
	boost::property_tree::ptree child;
	child.put("grandchild", "grandchild value4");
	m_TreeItem.add_child("node.child4", child);
	m_TreeItem.put("node.child4", "child4 value4");
}
  1. 模型类必须实现index虚函数,视图、委托会调用index函数来访问模型数据,具体参看qt帮助文档
QModelIndex MyTreeItemModel::index(int row, int column,
	const QModelIndex &parent /*= QModelIndex()*/) const
{
	if (!hasIndex(row, column, parent))
	{
		return QModelIndex();
	}

	const boost::property_tree::ptree* pParent = nullptr;
	if (!parent.isValid())
	{
		pParent = &m_TreeItem;
	}
	else
	{
		pParent = reinterpret_cast<boost::property_tree::ptree*>( parent.internalPointer() );
	}

	auto it = pParent->begin();
	it = std::next(it, row);
	const boost::property_tree::ptree* pThisItem = &(it->second);
	if (pThisItem)
	{
		return createIndex(row, column, (void*)pThisItem);
	}

	return QModelIndex();
}
  1. 模型类必须实现parent虚函数
QModelIndex MyTreeItemModel::parent(const QModelIndex &child) const
{
	if (!child.isValid())
		return QModelIndex();

	boost::property_tree::ptree *childItem = static_cast<boost::property_tree::ptree*>(child.internalPointer());
	
	int nParentRow = 0;
	const boost::property_tree::ptree* pParentItem = GetParent(childItem, nParentRow);
	if (!pParentItem)
	{
		Q_ASSERT(false);
		return QModelIndex();
	}

	if (pParentItem == &m_TreeItem)
		return QModelIndex();

	return createIndex(nParentRow, 0, (void *)pParentItem);
}
  1. 模型类必须实现rowCount虚函数
int MyTreeItemModel::rowCount(const QModelIndex &parent /*= QModelIndex()*/) const
{
	if (parent.column() > 0) //第一列才有子节点
		return 0;

	const boost::property_tree::ptree* pParent = nullptr;
	if (!parent.isValid())
		pParent = &m_TreeItem;
	else
		pParent = static_cast<const boost::property_tree::ptree*>(parent.internalPointer());

	return pParent->size();
}
  1. 模型类必须实现columnCount虚函数
int MyTreeItemModel::columnCount(const QModelIndex &parent /*= QModelIndex()*/) const
{
	return 1; //只支持一列
}
  1. 模型类必须实现data虚函数,因为是只读,只实现了Qt::DisplayRole
QVariant MyTreeItemModel::data(const QModelIndex &index, int role /*= Qt::DisplayRole*/) const
{
	if (!index.isValid())
		return QVariant();

	if (role != Qt::DisplayRole)
		return QVariant();

	auto pTreeItem = static_cast<const boost::property_tree::ptree*>(index.internalPointer());
	if (!pTreeItem)
	{
		Q_ASSERT(false);
		return QVariant();
	}

	QString strValue = QString::fromStdString(pTreeItem->data());
	return QVariant(strValue);
}
  1. 重写flags函数,告诉使用者,本模型只提供只读功能,这里基类QAbstractItemModel的实现正好符合需求,可不重写,我这里写出来只是说明下而已
Qt::ItemFlags MyTreeItemModel::flags(const QModelIndex &index) const
{
	if (!index.isValid())
		return 0;

	//The base class implementation returns a combination of flags that enables 
	//the item (ItemIsEnabled) and allows it to be selected (ItemIsSelectable).
	return QAbstractItemModel::flags(index);
}
  1. boost::property_tree::ptree没有提供访问父节点的功能,故加了个GetParent函数,仅测试用,实际不应该这么用,效率低
const boost::property_tree::ptree* MyTreeItemModel::GetParent(const boost::property_tree::ptree* pChild,
	int& nParentRow) const
{
	if (!pChild)
	{
		return nullptr;
	}

	std::stack<const boost::property_tree::ptree*> mNodeStack;
	mNodeStack.push(&m_TreeItem);
	while (!mNodeStack.empty())
	{
		const boost::property_tree::ptree* pParent = mNodeStack.top();
		mNodeStack.pop();
		if (!pParent)
		{
			continue;
		}

		//在子节点列表中搜索指定节点
		int nRow = 0;
		for (auto it = pParent->begin(); it != pParent->end(); ++it, ++nRow)
		{
			const boost::property_tree::ptree* pChildItem = &(it->second);
			if (pChildItem == pChild)
			{
				nParentRow = nRow;
				return pParent;
			}
			
			mNodeStack.push(pChildItem);
		}
	}

	nParentRow = 0;
	return nullptr;
}

使用自写的模型类

QtGuiApplication1::QtGuiApplication1(QWidget *parent)
	: QMainWindow(parent)
{
	//ui.setupUi(this);

	auto pCentralWidget = new QWidget(this);
	this->setCentralWidget(pCentralWidget);

	auto pVLayout = new QVBoxLayout();
	QAbstractItemModel *pModel = new MyTreeItemModel();
	QTreeView *pTreeView = new QTreeView();
	pTreeView->setModel(pModel);
	pVLayout->addWidget(pTreeView);
	pCentralWidget->setLayout(pVLayout);
}

运行演示

基于Qt的Model-View显示树形数据_虚函数_02