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

目标

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

MyTreeItemModel的定义

cpp 复制代码
#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

    cpp 复制代码
    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");
    }
  2. 模型类必须实现index虚函数,视图、委托会调用index函数来访问模型数据,具体参看qt帮助文档

    cpp 复制代码
    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();
    }
  3. 模型类必须实现parent虚函数

    cpp 复制代码
    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);
    }
  4. 模型类必须实现rowCount虚函数

    cpp 复制代码
    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();
    }
  5. 模型类必须实现columnCount虚函数

    cpp 复制代码
    int MyTreeItemModel::columnCount(const QModelIndex &parent /*= QModelIndex()*/) const
    {
    	return 1; //只支持一列
    }
  6. 模型类必须实现data虚函数,因为是只读,只实现了Qt::DisplayRole

    cpp 复制代码
    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);
    }
  7. 重写flags函数,告诉使用者,本模型只提供只读功能,这里基类QAbstractItemModel的实现正好符合需求,可不重写,我这里写出来只是说明下而已

    cpp 复制代码
    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);
    }
  8. boost::property_tree::ptree没有提供访问父节点的功能,故加了个GetParent函数,仅测试用,实际不应该这么用,效率低

    cpp 复制代码
    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;
    }

使用自写的模型类

cpp 复制代码
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);
}

运行演示