0 Preface
There is no TreeView control in QtQuick.Controls 2.x in Qt5, but only in QtQuick.Controls 1.x. Therefore, when we use a higher version of QtQuick.Controls, we cannot use TreeView, so we have to find a way to implement a TreeView ourselves.
1 Design ideas
The easiest way to realize TreeView is to use ListView iteratively. We can use ListView, and TreeView is actually ListView with ListView (matryoshka), so you can make a TreeView by using ListView flexibly.
1.1 TreeView data format (model)
What I take here is to read the data in JSON format and use it as the model (model data) of TreeView. The following is a simple example of model data:
[
{
"title": "第一章"
},
{
"title": "第二章",
"childrens": [
{
"title": "第二章-第一节"
},
{
"title": "第二章-第二节"
}
]
},
{
"title": "第三章"
}
]
The above is the data model that TreeView needs to read. As long as we modify this data model, we can construct any TreeView display type we want.
1.2 TreeView display style (delegate)
The delegate part is the key point. How to design this delegate is the most important thing whether the entire TreeView can be displayed correctly.
The display style of the delegate is shown in the figure above. We need to use a Component to implement the above part. The following is the simple code of the Component (the specific code will be given later):
Component {
Item {
RowLayout { // 主题(图标、标题)部分采用行布局
Image { // 图标
}
Text { // 标题
}
}
ColumnLayout { // 子项目中采用列布局
Item {
ListView { // 子项目中也应该有一个ListView,实现迭代
}
}
}
}
}
A detail that needs to be noted here is: Component height = RowLayout (theme) height + ColumnLayout (sub-item) height, so that the display will not overlap.
2 Final effect and code
2.1 Rendering
The final effect is shown in the figure above: the following is a TreeView, you can see that multi-level expansion can be performed, and the icon change part uses animation instead of switching between two pictures, which has a more dynamic effect.
2.2 Attached code
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 summary
The above method of implementing TreeView is for reference only. There are still some bugs. For example, the scroll bar next to it is not implemented, and it will exceed its boundary when it is pulled up, as shown in the following figure: