qttreeview添加button_Qt⼗万级树结构
⽬录
(放个⽬录⽅便预览。⽬录是从博客复制过来的,点击会跳转到博客)
简介
源码
效果预览
原理说明
关键代码
model
view
性能测试
测试环境
测试⽅法
数据⽣成
性能测试
测试结果
简介
最近遇到⼀些需求,要在Qt/Qml中开发树结构,并能够导⼊、导出json格式。
于是我写了⼀个简易的Demo,并做了⼀些性能测试。
在这⾥将源码、实现原理、以及性能测试都记录、分享出来,算是抛砖引⽟吧,希望有更多⼈来讨论、交流。
源码
先放源码
效果预览
Qml实现的树结构编辑器, 功能包括:
树结构的缩进
节点展开、折叠
添加节点
删除节点
重命名节点
搜索
导⼊
导出
节点属性编辑(完善中)
原理说明
数据model的实现,使⽤C++,继承于QAbstractListModel,并实现rowCount、data等⽅法。
model本⾝是List结构的,在此基础上,对model数据进⾏扩展以模拟树结构,例如增加了 “节点深度”、“是否有⼦节点”等数据段。view使⽤Qml Controls 2中的ListView模拟实现(Controls 1 中的TreeView即将被废弃)。
关键代码
model
基本model的声明如下:
template <typename T>
class TaoListModel : public QAbstractListModel {
public:
//声明⽗类
using Super = QAbstractListModel;
TaoListModel(QObject* parent = nullptr);
TaoListModel(const QList<T>& nodeList, QObject* parent = nullptr);
const QList<T>& nodeList() const
{
return m_nodeList;
qt listview}
void setNodeList(const QList<T>& nodeList);
int rowCount(const QModelIndex& parent) const override;
QVariant data(const QModelIndex& index, int role) const override;
bool setData(const QModelIndex& index, const QVariant& value, int role) override;
bool insertRows(int row, int count, const QModelIndex& parent = QModelIndex()) override;
bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
Qt::DropActions supportedDropActions() const override;
protected:
QList<T> m_nodeList;
};
其中数据成员使⽤ QList m_nodeList 存储, ⼤部分成员函数是对此数据的操作。
Json格式的model声明如下:
const static QString cDepthKey = QStringLiteral("TModel_depth");
const static QString cExpendKey = QStringLiteral("TModel_expend");
const static QString cChildrenExpendKey = QStringLiteral("TModel_childrenExpend");
const static QString cHasChildendKey = QStringLiteral("TModel_hasChildren");
const static QString cParentKey = QStringLiteral("TModel_parent");
const static QString cChildrenKey = QStringLiteral("TModel_children");
const static QString cRecursionKey = QStringLiteral("subType");
const static QStringList cFilterKeyList = { cDepthKey, cExpendKey, cChildrenExpendKey, cHasChildendKey, cParentKey, cChildrenKey }; class TaoJsonTreeModel : public TaoListModel<QJsonObject> {
Q_OBJECT
Q_PROPERTY(int count READ count NOTIFY countChanged)
public:
//声明⽗类
using Super = TaoListModel<QJsonObject>;
//从json⽂件读⼊数据
Q_INVOKABLE void loadFromJson(const QString& jsonPath, const QString& recursionKey = cRecursionKey);
//导出到json⽂件
Q_INVOKABLE bool saveToJson(const QString& jsonPath, bool compact = false) const;
Q_INVOKABLE void clear();
//设置指定节点的数值
Q_INVOKABLE void setNodeValue(int index, const QString &key, const QVariant &value);
//在index添加⼦节点。刷新⽗级,返回新项index
Q_INVOKABLE int addNode(int index, const QJsonObject& json);
Q_INVOKABLE int addNode(const QModelIndex& index, const QJsonObject& json)
{
return w(), json);
}
//删除。递归删除所有⼦级,刷新⽗级
Q_INVOKABLE void remove(int index);
Q_INVOKABLE void remove(const QModelIndex& index)
{
w());
}
Q_INVOKABLE QList<int> search(const QString& key, const QString& value, Qt::CaseSensitivity cs = Qt::CaseInsensitive) const; //展开⼦级。只展开⼀级,不递归
Q_INVOKABLE void expand(int index);
Q_INVOKABLE void expand(const QModelIndex& index)
{
w());
}
//折叠⼦级。递归全部⼦级。
Q_INVOKABLE void collapse(int index);
Q_INVOKABLE void collapse(const QModelIndex& index)
{
w());
}
//展开到指定项。递归
Q_INVOKABLE void expandTo(int index);
Q_INVOKABLE void expandTo(const QModelIndex& index)
{
w());
}
//展开全部
Q_INVOKABLE void expandAll();
//折叠全部
Q_INVOKABLE void collapseAll();
int count() const;
Q_INVOKABLE QVariant data(int idx, int role = Qt::DisplayRole) const
{
return Super::data(Super::index(idx), role);
}
signals:
void countChanged();
...
};
TaoJsonTreeModel继承于TaoListModel,并提供⼤量Q_INVOKABLE函数,以供Qml调⽤。
view
TreeView的模拟实现如下:
Item {
id: root
readonly property string __depthKey: "TModel_depth"
readonly property string __expendKey: "TModel_expend"
readonly property string __childrenExpendKey: "TModel_childrenExpend"
readonly property string __hasChildendKey: "TModel_hasChildren"
readonly property string __parentKey: "TModel_parent"
readonly property string __childrenKey: "TModel_children"
...
ListView {
id: listView
anchors.fill: parent
anchors.fill: parent
currentIndex: -1
delegate: Rectangle {
id: delegateRect
width: listView.width
color: (listView.currentIndex === index || area.hovered) ? alColor : config.darkerColor // 根据 expaned 判断是否展开,不展开的情况下⾼度为0
height: model.display[__expendKey] === true ? 35 : 0
// 优化。⾼度为0时visible为false,不渲染。
visible: height > 0
property alias editable: nameEdit.editable
property alias editItem: nameEdit
TTextInput {
id: nameEdit
anchors.verticalCenter: parent.verticalCenter
//按深度缩进
x: root.basePadding + model.display[__depthKey] * root.subPadding
text: model.display["name"]
height: parent.height
width: parent.width * 0.8 - x
editable: false
onTEditFinished: {
sourceModel.setNodeValue(index, "name", displayText)
}
}
TTransArea {
id: area
height: parent.height
width: parent.width - controlIcon.x
hoverEnabled: true
acceptedButtons: Qt.LeftButton | Qt.RightButton
onPressed: {
//单击时切换当前选中项
if (listView.currentIndex !== index) {
listView.currentIndex = index;
} else {
listView.currentIndex = -1;
}
}
onTDoubleClicked: {
//双击进⼊编辑状态
delegateRect.editable = true;
nameEdit.forceActiveFocus()
}
}
Image {
id: controlIcon
anchors {
verticalCenter: parent.verticalCenter
right: parent.right
rightMargin: 20
}
//有⼦节点时,显⽰⼩图标
visible: model.display[__hasChildendKey]
source: model.display[__childrenExpendKey] ? "qrc:/img/collapse.png" : "qrc:/img/expand.png" MouseArea {
anchors.fill: parent
onClicked: {
//点击⼩图标时,切换折叠、展开的状态
if (model.display[__hasChildendKey]) {
if( true === model.display[__childrenExpendKey]) {
collapse(index)
} else {
expand(index)
}
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论