目标
用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);
}