目标
用qt的模型-视图框架实现树型层次节点的显示,从QAbstractItemModel派生自己的模型类MyTreeItemModel,用boost::property_tree::ptree操作树型数据结构,为了演示,此处只实现了个只读的模型
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的实现
- 简化代码,直接在构造函数创建出树型数据结构,用的是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");
}
- 模型类必须实现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();
}
- 模型类必须实现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);
}
- 模型类必须实现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();
}
- 模型类必须实现columnCount虚函数
int MyTreeItemModel::columnCount(const QModelIndex &parent /*= QModelIndex()*/) const
{
return 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);
}
- 重写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);
}
- 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);
}
运行演示