先上效果图
QML中TreeView的使用总体比较简单,重点还是在于自定义Model的编写上,也踩了一个大坑,就是动态添加和删除Item的时候,界面似乎没有自动更新,最后加了两行代码才解决:
emit layoutAboutToBeChanged();
emit layoutChanged();
如果有大神知道其他解决办法的,请评论区发出来。
注: 测试环境为Opensuse 15.5 + Qt 6.6.2
下面贴出关键代码:
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
import QtQuick 6.5
import NetTool
import QtQuick.Controls 6.5
import Qt.labs.qmlmodels
import content
Window {
id: window
width: 400
height: 300
visible: true
title: "NetTool"
SplitView{
anchors.fill: parent
orientation: Qt.Horizontal
TreeView {
id:treeView
SplitView.maximumWidth: Math.max(350, parent.width/2)
SplitView.minimumWidth: Math.min(200, parent.width/4)
SplitView.fillHeight: true
columnSpacing: 0
rowSpacing: 0
ScrollBar.horizontal: ScrollBar {}
ScrollBar.vertical: ScrollBar {}
ScrollIndicator.horizontal: ScrollIndicator {}
ScrollIndicator.vertical: ScrollIndicator {}
boundsBehavior: Flickable.StopAtBounds
clip: true
model: TreeModel{
onRowsInserted:(index, first, last)=> {
console.log("onRowsInserted: index=" + index + ", first=" + first + ", last=" + last)
}
}
selectionModel: ItemSelectionModel {
// model: tableView.model
onCurrentChanged: (cur, pre)=>{
let item = treeView.itemAtIndex(pre);
if(item != null){
let itemPre = treeView.itemAtIndex(pre).configWnd
itemPre.parent = treeView.itemAtIndex(pre);
itemPre.visible = false;
console.log("hide " + pre)
}
let itemCur = treeView.itemAtIndex(cur).configWnd
itemCur.visible = true
itemCur.parent = main
itemCur.port = cur.row //只是为了测试是否选中了
console.log("show " + cur)
}
}
Menu{
id: menu_delete
MenuItem {
text: qsTr("删除")
onTriggered: {
console.log("删除row=" + treeView.currentRow + " col=" + treeView.currentColumn)
let index = treeView.selectionModel.currentIndex
treeView.model.removeRow(index.row, index.parent)
}
}
}
Menu{
id: menu_append
MenuItem {
text: qsTr("添加")
onTriggered: {
console.log("添加row=" + treeView.currentRow + " col=" + treeView.currentColumn)
let index = treeView.selectionModel.currentIndex
//treeView.model.insertRow(treeView.model.rowCount(index), index)
treeView.model.insertRow(0, index)
}
}
}
delegate: TreeViewDelegate{
id: control
implicitWidth: treeView.width
implicitHeight: 35
leftMargin: 5
rightMargin: 5
spacing: 5
indentation: 20
function getData(role){
let model = control.treeView.model
let index = control.treeView.index(row, column)
return model.data(index, role)
}
function setData(role, value){
let model = control.treeView.model
let index = control.treeView.index(row, column)
model.setData(index, value, role)
}
//最初的设想是每一项都关联一个界面,鼠标点击时切换过去
property var configWnd : {
// let strWnd = `import QtQuick; ConfigUI{anchors.fill: parent; visible:false}`
// return Qt.createQmlObject(strWnd, control, "")
var component = Qt.createComponent("ConfigUI.qml"); //动态创建QML对象的方式之一
return component.createObject(control, {visible:false});
}
MouseArea{
anchors.fill: parent
acceptedButtons: Qt.RightButton | Qt.LeftButton
propagateComposedEvents: true
onPressed: (mouse)=>{
let index = control.treeView.index(row, column)
console.log("id=" + control + ", row=" + row + ", col=" + column + ", index=" + index)
mouse.accepted = false
if(mouse.button === Qt.RightButton)
{
if(depth === 2)
menu_delete.popup()
else if(depth == 1)
menu_append.popup()
treeView.selectionModel.clearCurrentIndex()
treeView.selectionModel.clearSelection()
treeView.selectionModel.setCurrentIndex(index, 2)
}
}
}
contentItem: Label {
id:content_label
text:{
return getData(Qt.DisplayRole)
}
}
TableView.editDelegate: FocusScope {
width: parent.width
height: parent.height
TextField {
id: textField
x: control.contentItem.x
y: (parent.height - height) / 2
width: control.contentItem.width
text: display
focus: true
}
TableView.onCommit: {
let index = control.treeView.index(row, column)
TableView.view.model.setData(index, Qt.EditRole, textField.text)
content_label.text = textField.text
}
}
}
}
Rectangle{
id: main
color:"lightblue"
SplitView.fillHeight: true
}
}
}
#ifndef TREEMODEL_H
#define TREEMODEL_H
#include <qqml.h>
#include <QAbstractItemModel>
#include <memory>
#include <vector>
#include <map>
#include <QVariant>
using namespace std;
struct TreeNode
{
shared_ptr<TreeNode> m_pParent;
vector<shared_ptr<TreeNode>> m_vChild;
QString m_strName;
map<QString, QVariant> m_mapAttribute;
};
class TreeModel : public QAbstractItemModel
{
Q_OBJECT
QML_ELEMENT
public:
explicit TreeModel(QObject *parent = nullptr);
// Header:
QVariant headerData(int section,
Qt::Orientation orientation,
int role = Qt::DisplayRole) const override;
bool setHeaderData(int section,
Qt::Orientation orientation,
const QVariant &value,
int role = Qt::EditRole) override;
// Basic functionality:
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex &index) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
// Fetch data dynamically:
bool hasChildren(const QModelIndex &parent = QModelIndex()) const override;
bool canFetchMore(const QModelIndex &parent) const override;
void fetchMore(const QModelIndex &parent) override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
// Editable:
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;
// Add data:
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
bool insertColumns(int column, int count, const QModelIndex &parent = QModelIndex()) override;
// Remove data:
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()) override;
bool removeColumns(int column, int count, const QModelIndex &parent = QModelIndex()) override;
private:
vector<shared_ptr<TreeNode>> m_vData;
QList<QString> m_vHorHeader;
};
#endif // TREEMODEL_H
#include "treemodel.h"
TreeModel::TreeModel(QObject *parent)
: QAbstractItemModel{parent}
{
for(auto pro : {"tcp", "udp", "websocket", "http"})
{
shared_ptr<TreeNode> p = make_shared<TreeNode>();
p->m_pParent = nullptr;
p->m_strName = pro;
for(auto type : {"客户端", "服务端"})
{
shared_ptr<TreeNode> pType = make_shared<TreeNode>();
pType->m_strName = type;
pType->m_pParent = p;
for(int i = 0; i < 10; i++)
{
auto pChild = make_shared<TreeNode>();
pChild->m_strName = QString("127.0.0.1:%1").arg(8000+i);
pChild->m_pParent = pType;
pType->m_vChild.push_back(pChild);
}
p->m_vChild.push_back(pType);
}
m_vData.push_back(p);
}
}
// Header:
QVariant TreeModel::headerData(int section,
Qt::Orientation orientation,
int role) const
{
qDebug() << "headerData";
if(orientation == Qt::Orientation::Horizontal && role == Qt::DisplayRole && m_vHorHeader.size() < section && section >= 0)
return m_vHorHeader[section];
return "";
}
bool TreeModel::setHeaderData(int section,
Qt::Orientation orientation,
const QVariant &value,
int role)
{
qDebug() << "setHeaderData";
if(orientation == Qt::Orientation::Horizontal && role == Qt::DisplayRole)
{
m_vHorHeader[section] = value.toString();
emit headerDataChanged(orientation, section, section);
return true;
}
return false;
}
// Basic functionality:
QModelIndex TreeModel::index(int row, int column, const QModelIndex &parent) const
{
if(hasIndex(row, column, parent) == false)
return {};
TreeNode* p = (TreeNode*)parent.internalPointer();
if(p == nullptr)
return createIndex(row, 0, m_vData[row].get());
return createIndex(row, 0, p->m_vChild[row].get());
}
QModelIndex TreeModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return {};
TreeNode* p = (TreeNode*)index.internalPointer();
int row = 0;
if(p == nullptr)
return createIndex(-1, -1);
if(p->m_pParent == nullptr)
return createIndex(-1, -1);
for(auto& item : p->m_pParent->m_vChild)
{
if(item.get() == p)
return createIndex(row, 0, p->m_pParent.get());
row++;
}
return createIndex(-1, -1);
}
int TreeModel::rowCount(const QModelIndex &parent) const
{
if (parent.isValid() && parent.column() > 0)
return 0;
if(!parent.isValid())
return m_vData.size();
TreeNode* p = (TreeNode*)parent.internalPointer();
return p->m_vChild.size();
}
int TreeModel::columnCount(const QModelIndex &parent) const
{
return 1;
}
bool TreeModel::hasChildren(const QModelIndex &parent ) const
{
if(!parent.isValid())
return false;
TreeNode* p = (TreeNode*)parent.internalPointer();
if(p == nullptr)
return false;
return !p->m_vChild.empty();
}
bool TreeModel::canFetchMore(const QModelIndex &parent) const
{
return false;
}
void TreeModel::fetchMore(const QModelIndex &parent)
{
}
QVariant TreeModel::data(const QModelIndex &index, int role ) const
{
TreeNode* p = (TreeNode*)index.internalPointer();
if(p == nullptr)
return "";
return p->m_strName;
}
// Editable:
bool TreeModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
TreeNode* p = (TreeNode*)index.internalPointer();
p->m_strName = value.toString();
emit dataChanged(index, index, {role});
return true;
}
Qt::ItemFlags TreeModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return QAbstractItemModel::flags(index) | Qt::ItemIsEditable; // FIXME: Implement me!
}
// Add data:
bool TreeModel::insertRows(int row, int count, const QModelIndex &parent)
{
qDebug() << "insertRows(): row=" << row << ", count=" << count;
emit layoutAboutToBeChanged();
beginInsertRows(parent, row, row + count - 1);
TreeNode* p = (TreeNode*)parent.constInternalPointer();
shared_ptr<TreeNode> pParent = nullptr;
for(auto& item : p->m_pParent->m_vChild)
{
if(item.get() == p)
{
pParent = item;
break;
}
}
for(int i = 0; i < count; i++)
{
auto pNode = make_shared<TreeNode>();
pNode->m_strName = "未命名";
pNode->m_pParent = pParent;
pParent->m_vChild.insert(pParent->m_vChild.begin()+row, pNode);
}
endInsertRows();
emit layoutChanged();
return true;
}
bool TreeModel::insertColumns(int column, int count, const QModelIndex &parent)
{
return true;
}
// Remove data:
bool TreeModel::removeRows(int row, int count, const QModelIndex &parent)
{
qDebug() << "removeRows()";
emit layoutAboutToBeChanged();
beginRemoveRows(parent, row, row + count - 1);
TreeNode* p = (TreeNode*)parent.constInternalPointer();
auto it = p->m_vChild.begin() + row;
p->m_vChild.erase(it, it+count);
endRemoveRows();
emit layoutChanged();
return true;
}
bool TreeModel::removeColumns(int column, int count, const QModelIndex &parent)
{
beginRemoveColumns(parent, column, column + count - 1);
// FIXME: Implement me!
endRemoveColumns();
return true;
}