0 前言
在Qt5中的QtQuick.Controls 2.x中没有TreeView这个控件,而只在QtQuick.Controls 1.x中拥有这个控件。因此当我们使用高版本的QtQuick.Controls时,无法使用TreeView,因此我们必须得想办法自己实现一个TreeView。
1 设计思路
实现TreeView的最容易想到的思路就是迭代使用ListView,ListView我们是可以使用的,而TreeView实际上就是ListView套ListView(套娃),因此灵活使用ListView就能制作一个TreeView。
1.1 TreeView的数据格式(model)
我这里采取的是读取JSON格式的数据用来作为TreeView的model(模型数据),下面是一个简单的模型数据例子:
[
{
"title": "第一章"
},
{
"title": "第二章",
"childrens": [
{
"title": "第二章-第一节"
},
{
"title": "第二章-第二节"
}
]
},
{
"title": "第三章"
}
]
上面就是TreeView需要读取的数据模型,只要我们修改这个数据模型,就可以构造出我们想要的任意的TreeView显示类型。
1.2 TreeView的显示样式(delegate)
delegate这部分是重点,如何设计这个delegate是整个TreeView能否正确显示的重中之重。
delegate的显示样式如上图所示,我们要使用一个Component来实现上面这个部分,下面是Component的简易代码(具体代码后面会给出):
Component {
Item {
RowLayout { // 主题(图标、标题)部分采用行布局
Image { // 图标
}
Text { // 标题
}
}
ColumnLayout { // 子项目中采用列布局
Item {
ListView { // 子项目中也应该有一个ListView,实现迭代
}
}
}
}
}
这里需要注意的一个细节是:Component的高度 = RowLayout(主题)的高度 + ColumnLayout(子项目)的高度,这样显示出来才不会重叠。
2 最终效果和代码
2.1 效果图
最终实现的效果如上图所示:下面就是一个TreeView,可以看到可以进行多级展开,并且图标变化部分使用了动画,而不是两种图片切换的方式,这样更有动态变化的效果。
2.2 附代码
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.3
Item {
id: root
width: 360
height: 300
// 使用别名model,并为listView提供数据模型
property alias model: listView.model
// 设置背景颜色
Rectangle {
id: bg
anchors.fill: parent
color: "#353535"
}
ListView {
id: listView
anchors.fill: parent
delegate: listDelegate
}
// 定义数据显示方式
Component {
id: listDelegate
Item {
id: item
width: parent.width
height: itemTitle.height + itemChildren.height
property bool isSpread: false // 当前节点是否展开
function clickedEvent() { // 鼠标点击时需要完成的事件
console.log(modelData.title, "on clicked")
itemChildren.visible = !itemChildren.visible
if (item.isSpread) {
iconAnimation.from = 90
iconAnimation.to = 0
iconAnimation.start()
item.isSpread = !item.isSpread
} else {
iconAnimation.from = 0
iconAnimation.to = 90
iconAnimation.start()
item.isSpread = !item.isSpread
}
}
// 主题(图标、标题)使用行布局
RowLayout {
id: itemTitle
anchors.left: parent.left
spacing: 5
// 图标
Image {
id: itemICON
source: "qrc:/CaretRight.png"
Layout.preferredWidth: 25
Layout.preferredHeight: 25
RotationAnimation {
id: iconAnimation
target: itemICON
duration: 100
}
MouseArea {
anchors.fill: parent
onClicked: {
item.clickedEvent()
}
}
}
// 标题
Text {
text: qsTr(modelData.title)
font {
pixelSize: 18
}
color: "white"
Layout.fillWidth: true
MouseArea {
anchors.fill: parent
onClicked: {
item.clickedEvent()
}
}
}
}
// 子项使用列布局
ColumnLayout {
id: itemChildren
visible: modelData.hasChildren && item.isSpread // 有子项目并且展开显示
anchors.left: parent.left
anchors.top: itemTitle.bottom
anchors.leftMargin: 25
height: itemChildren.visible ? itemChildrenListView.contentHeight : 0
spacing: 5
Item {
width: parent.width - 20
height: itemChildrenListView.contentHeight
ListView {
id: itemChildrenListView
anchors.fill: parent
delegate: listDelegate
model: modelData.childrens
}
}
}
}
}
}
import QtQuick 2.15
import QtQuick.Controls 2.15
Item {
id: item1
width: 800
height: 700
// 用来设置背景颜色
Rectangle {
id: rect_bg
anchors.fill: parent
color: "#252525"
}
// 主题:帮助
Text {
id: text_help
width: 70
height: 30
text: qsTr("帮助")
color: "#ffffff"
anchors.left: parent.left
anchors.top: parent.top
font.pixelSize: 18
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
font.bold: true
anchors.topMargin: 10
anchors.leftMargin: 10
}
// 按钮行布局
Row {
id: row
width: 500
height: 40
anchors.left: parent.left
anchors.top: text_help.bottom
spacing: 30
anchors.topMargin: 10
anchors.leftMargin: 10
Button {
id: btn_userMannual
width: 150
height: parent.height
text: qsTr("在线用户手册")
background: Rectangle {
color: "#177ddc"
}
}
Button {
id: btn_communicateUs
width: 150
height: parent.height
text: qsTr("联系我们")
background: Rectangle {
color: "#177ddc"
}
}
}
// 主题:常见问题
Text {
id: text_commonQs
width: 70
height: 30
text: qsTr("常见问题")
color: "#ffffff"
anchors.left: parent.left
anchors.top: row.bottom
font.pixelSize: 18
horizontalAlignment: Text.AlignLeft
verticalAlignment: Text.AlignVCenter
font.bold: true
anchors.topMargin: 10
anchors.leftMargin: 10
}
// 实现的TreeView
MTreeView {
id: treeView
width: 780
height: 500
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.top: text_commonQs.bottom
anchors.margins: 10
Component.onCompleted: {
setTreeViewModelData()
}
}
// 该函数用于TreeView设置需要测试的模型数据(model)
function setTreeViewModelData() {
treeView.model = JSON.parse('[
{
"title": "第一章",
"hasChildren": true
},
{
"title": "第二章",
"hasChildren": true,
"childrens": [
{
"title": "第二章-第一节",
"hasChildren": false
},
{
"title": "第二章-第二节",
"hasChildren": true,
"childrens": [
{
"title": "第二章-第二节-第一段",
"hasChildren": false
},
{
"title": "第二章-第二节-第二段",
"hasChildren": false
}
]
}
]
},
{
"title": "第三章",
"hasChildren": true
}
]')
}
}
import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15
Window {
width: 640
height: 480
visible: true
MPage {
anchors.fill: parent
}
}
3 总结
上述这种实现TreeView的方式仅供参考,目前仍然存在一些Bug,例如旁边的滚动条没有实现,并且在往上拉的时候会超过它的边界,如下图所示: