背景
QML TreeView 是从QtQuick.Controls 1.4开始引入的,在QtQuick.Controls 1.3里并不支持,所以不得不自定义一个TreeView。
方法
可以用QtQuick.Controls 1.3里支持的ListView来实现TreeView的功能
1 import QtQuick 2.5 2 import QtQuick.Window 2.2 3 import QtQuick.Controls 1.3 4 import QtQuick.Controls.Styles 1.3 5 import QtQml.Models 2.2 6 import QtQuick.Layouts 1.1 7 8 import IMTreeModel 1.0 //这是原来TreeView的model,在新的由ListView实现的TreeView里继续使用这个model,但需要将TreeModel转化成ListModel的形式 9 10 Rectangle{ 11 IMTreeModel{ 12 id: treeModel 13 } 14 15 property int nodeLevel : 0 16 property int appendedNodeNumberLevel0:0 17 property int appendedNodeNumberLevel1:0 18 property int appendedNodeNumberLevel2:0 19 property int appendedNodeNumberLevel3:0 20 property int appendedNodeNumberLevel4:0 21 property int totalNodeNumberLevel0:0 22 property int totalNodeNumberLevel1:0 23 property int totalNodeNumberLevel2:0 24 property int totalNodeNumberLevel3:0 25 property int totalNodeNumberLevel4:0 26 property int nodeIndex : 0 27 property var indexArray : [] 28 property var selectedIndex 29 property var selectedObjectNo : -1 30 property var selectedObject 31 property var filterIndex 32 33 // 遍历TreeModel里的所有节点,把它们从tree的形式转化成list的形式 34 function expandAll(index, model, update) 35 { 36 var nodeName = treeModel.data(index, IMTreeModel.NAME).toString(); 37 var nodeType = treeModel.data(index, IMTreeModel.TYPE).toString(); 38 var nodeValue = treeModel.data(index, IMTreeModel.VALUE).toString(); 39 40 if(nodeLevel == 0) // Root is level 0 41 { 42 if(!update) // Root is not leaf node, no value 43 { 44 listModel.append({"name":nodeName, "type":nodeType, "value":nodeValue, "level":nodeLevel, "index":nodeIndex++, "subNode":[]}) 45 indexArray.push(index) 46 } 47 48 appendedNodeNumberLevel0++ 49 if (model.hasChildren(index)) 50 { 51 nodeLevel++ 52 totalNodeNumberLevel1 = model.rowCount(index) 53 } 54 } 55 else if(nodeLevel == 1) 56 { 57 if(update) //update the node 58 { 59 if(listModel.get(appendedNodeNumberLevel0 - 1).subNode.get(appendedNodeNumberLevel1).value != nodeValue) 60 { 61 listModel.get(appendedNodeNumberLevel0 - 1).subNode.get(appendedNodeNumberLevel1).value = nodeValue 62 } 63 } 64 else //create the node 65 { 66 listModel.get(appendedNodeNumberLevel0 - 1).subNode.append({"name":nodeName, "type":nodeType, "value":nodeValue, "level":nodeLevel, "index":nodeIndex++, "subNode":[]}) 67 indexArray.push(index) 68 } 69 70 appendedNodeNumberLevel1++ 71 72 if (model.hasChildren(index)) 73 { 74 nodeLevel++ 75 totalNodeNumberLevel2 = model.rowCount(index) 76 } 77 else 78 { 79 if(totalNodeNumberLevel1 == appendedNodeNumberLevel1) 80 { 81 nodeLevel -- 82 appendedNodeNumberLevel1 = 0 83 } 84 } 85 } 86 else if(nodeLevel == 2) 87 { 88 if(update) //update the node 89 { 90 if(listModel.get(appendedNodeNumberLevel0 - 1).subNode.get(appendedNodeNumberLevel1 - 1).subNode.get(appendedNodeNumberLevel2).value != nodeValue) 91 { 92 listModel.get(appendedNodeNumberLevel0 - 1).subNode.get(appendedNodeNumberLevel1 - 1).subNode.get(appendedNodeNumberLevel2).value = nodeValue 93 } 94 } 95 else //create the node 96 { 97 listModel.get(appendedNodeNumberLevel0 - 1).subNode.get(appendedNodeNumberLevel1 - 1).subNode.append({"name":nodeName, "type":nodeType, "value":nodeValue, "level":nodeLevel, "index":nodeIndex++, "subNode":[]}) 98 indexArray.push(index) 99 } 100 appendedNodeNumberLevel2++ 101 102 if (model.hasChildren(index)) 103 { 104 nodeLevel++ 105 totalNodeNumberLevel3 = model.rowCount(index) 106 } 107 else 108 { 109 if(totalNodeNumberLevel2 == appendedNodeNumberLevel2) 110 { 111 nodeLevel -- 112 appendedNodeNumberLevel2 = 0 113 } 114 } 115 } 116 else if(nodeLevel == 3) 117 { 118 if(update) //update the node 119 { 120 if(listModel.get(appendedNodeNumberLevel0 - 1).subNode.get(appendedNodeNumberLevel1 - 1).subNode.get(appendedNodeNumberLevel2 - 1).subNode.get(appendedNodeNumberLevel3).value != nodeValue) 121 { 122 listModel.get(appendedNodeNumberLevel0 - 1).subNode.get(appendedNodeNumberLevel1 - 1).subNode.get(appendedNodeNumberLevel2 - 1).subNode.get(appendedNodeNumberLevel3).value = nodeValue 123 } 124 } 125 else //create the node 126 { 127 listModel.get(appendedNodeNumberLevel0 - 1).subNode.get(appendedNodeNumberLevel1 - 1).subNode.get(appendedNodeNumberLevel2 - 1).subNode.append({"name":nodeName, "type":nodeType, "value":nodeValue, "level":nodeLevel, "index":nodeIndex++, "subNode":[]}) 128 indexArray.push(index) 129 } 130 131 appendedNodeNumberLevel3++ 132 133 if (model.hasChildren(index)) 134 { 135 nodeLevel++ 136 totalNodeNumberLevel4 = model.rowCount(index) 137 } 138 else 139 { 140 if(totalNodeNumberLevel3 == appendedNodeNumberLevel3) 141 { 142 nodeLevel -- 143 appendedNodeNumberLevel3 = 0 144 } 145 } 146 } 147 else if(nodeLevel == 4) // Level 4 is the last level 148 { 149 if(update) //update the node 150 { 151 if(listModel.get(appendedNodeNumberLevel0 - 1).subNode.get(appendedNodeNumberLevel1 - 1).subNode.get(appendedNodeNumberLevel2 - 1).subNode.get(appendedNodeNumberLevel3 - 1).subNode.get(appendedNodeNumberLevel4).value != nodeValue) 152 { 153 listModel.get(appendedNodeNumberLevel0 - 1).subNode.get(appendedNodeNumberLevel1 - 1).subNode.get(appendedNodeNumberLevel2 - 1).subNode.get(appendedNodeNumberLevel3 - 1).subNode.get(appendedNodeNumberLevel4).value = nodeValue 154 } 155 } 156 else //create the node 157 { 158 listModel.get(appendedNodeNumberLevel0 - 1).subNode.get(appendedNodeNumberLevel1 - 1).subNode.get(appendedNodeNumberLevel2 - 1).subNode.get(appendedNodeNumberLevel3 - 1).subNode.append({"name":nodeName, "type":nodeType, "value":nodeValue, "level":nodeLevel, "index":nodeIndex++, "subNode":[]}) 159 indexArray.push(index) 160 } 161 162 appendedNodeNumberLevel4++ 163 164 //if (model.hasChildren(index)){} //not needed because it is last level which means no chidren 165 166 if(totalNodeNumberLevel4 == appendedNodeNumberLevel4) 167 { 168 nodeLevel -- 169 appendedNodeNumberLevel4 = 0 170 171 if(totalNodeNumberLevel3 == appendedNodeNumberLevel3) 172 { 173 nodeLevel -- 174 appendedNodeNumberLevel3 = 0 175 } 176 } 177 } 178 179 if (!model.hasChildren(index)) 180 return 181 182 var rows = model.rowCount(index) 183 for (var i = 0; i < rows; ++i) 184 { 185 expandAll(model.index(i, 0, index), model, update); 186 } 187 } 188 //把TreeModel转化为ListModel 189 ListModel { 190 id: listModel 191 Component.onCompleted: { 192 expandAll(treeModel.index(0, 0), treeModel, false) // false : create node tree 193 } 194 } 195 196 Timer //因为这个ListModel是由TreeModel转化而来,并不是真正的MVC形式,所以要手动实时更新ListView中的数据 197 { 198 id:modelUpdateTimer 199 interval: 1000 200 repeat: true 201 running:true 202 triggeredOnStart: true 203 204 onTriggered: 205 { 206 nodeLevel = 0 207 appendedNodeNumberLevel0 = 0 208 appendedNodeNumberLevel1 = 0 209 appendedNodeNumberLevel2 = 0 210 appendedNodeNumberLevel3 = 0 211 appendedNodeNumberLevel4 = 0 212 totalNodeNumberLevel0 = 0 213 totalNodeNumberLevel1 = 0 214 totalNodeNumberLevel2 = 0 215 totalNodeNumberLevel3 = 0 216 totalNodeNumberLevel4 = 0 217 nodeIndex = 0 218 219 expandAll(treeModel.index(0, 0), treeModel, true) // true : update node tree 220 } 221 } 222 223 //Delegate 224 Component { 225 id: listDelegate 226 227 Column { 228 id: listItem 229 Rectangle { 230 id: rectLine 231 width: listView.width 232 height: 20 233 color: index % 2 == 0 ? "white" : "#f7f5f3" 234 235 Connections{ 236 target: btnApplyFilter 237 onClicked: { 238 if(filterIndex == indexArray[index]) 239 { 240 if(selectedObjectNo != -1) 241 { 242 selectedObject.color = selectedObjectNo % 2 == 0 ? "white" : "#f7f5f3" 243 } 244 245 selectedObject = rectLine 246 selectedObjectNo = index 247 rectLine.color = '#308cc6' 248 249 selectedIndex = indexArray[model.index] 250 fldValue.text = model.value 251 btnApplyValue.enabled = (model.value != "") 252 253 filterIndex = "" 254 255 //set scrollbar position to make the selected item visible 256 var pos = rectLine.height * index 257 if(pos > rectLine.height * indexArray.length - scrView.height) 258 { 259 pos = rectLine.height * indexArray.length - scrView.height 260 } 261 else if(pos < scrView.height) 262 { 263 pos = 0 264 } 265 266 scrView.flickableItem.contentY = pos 267 } 268 } 269 } 270 271 Text { 272 id:tabName 273 width: 400 274 x : 5 + model.level * 15 275 276 anchors.leftMargin: 5 277 anchors.verticalCenter: parent.verticalCenter 278 text: (listItem.children.length > 2 ? listItem.children[1].visible ? qsTr("- ") : qsTr("+ ") : qsTr(" ")) + model.name 279 elide: Text.ElideRight 280 color: "#202020" 281 } 282 Text { 283 id:tabType 284 width: 100 285 x: 400 286 text: model.type 287 elide: Text.ElideRight 288 color: "#202020" 289 } 290 Text { 291 id:tabValue 292 width: 100 293 x: 500 294 text: model.value 295 elide: Text.ElideRight 296 color: "#202020" 297 } 298 MouseArea { 299 anchors.fill: parent 300 onClicked: { 301 for(var i = 1; i < listItem.children.length - 1; ++i) { 302 listItem.children[i].visible = !listItem.children[i].visible 303 } 304 305 if(model.name == "Root") 306 { 307 tabName.text = (listItem.children[1].visible ? qsTr("- ") : qsTr("+ ")) + model.name 308 } 309 310 if(selectedObjectNo != -1) 311 { 312 selectedObject.color = selectedObjectNo % 2 == 0 ? "white" : "#f7f5f3" 313 } 314 315 selectedObject = parent 316 selectedObjectNo = index 317 parent.color = '#308cc6' 318 319 selectedIndex = indexArray[model.index] 320 fldValue.text = model.value 321 btnApplyValue.enabled = (model.value != "") 322 } 323 } 324 } 325 326 Repeater { 327 id: repeater1 328 model: subNode 329 delegate: listDelegate 330 } 331 } 332 } 333 334 // Title of View 335 Rectangle { 336 id: recTitle 337 width: parent.width 338 height: 30 339 z:2 340 color: "#efebe7" 341 342 RowLayout { 343 anchors.left: parent.left 344 anchors.verticalCenter: parent.verticalCenter 345 346 Text { 347 text: " Name" 348 font.pixelSize: 18 349 color: "#3c3c3c" 350 Layout.preferredWidth: 390 351 } 352 353 Text { 354 text: "Type" 355 font.pixelSize: 18 356 color: "#3c3c3c" 357 Layout.preferredWidth: 100 358 } 359 360 Text { 361 text: "Value" 362 font.pixelSize: 18 363 color: "#3c3c3c" 364 } 365 } 366 } 367 368 //View 369 ScrollView{ //QtQuick.Controls 1.3里也不支持QML Scrollbar,所以在ListView外面用ScrollView包一层,这样就可以使用ScrollView自带的Scrollbar来实现ListView的滚动 370 id:scrView 371 y:recTitle.height 372 height: 520 373 width: parent.width 374 375 ListView { 376 id:listView 377 anchors.fill: parent 378 model: listModel 379 delegate: listDelegate 380 focus: true 381 clip: true 382 } 383 } 384 385 Text{ 386 id:txtValue 387 388 x:120 389 y:558 390 height: 25 391 text: "Value: " 392 horizontalAlignment:Text.AlignHCenter 393 verticalAlignment:Text.AlignVCenter 394 } 395 396 TextField{ 397 id:fldValue 398 399 x:txtValue.x + txtValue.width 400 y:txtValue.y 401 height:txtValue.height 402 } 403 404 Button{ 405 id:btnApplyValue 406 407 x:fldValue.x + fldValue.width + 2 408 y:fldValue.y 409 height:fldValue.height 410 411 text:"Apply" 412 enabled: false 413 414 onClicked:{ 415 var nodeValue = treeModel.data(selectedIndex, IMTreeModel.VALUE).toString(); 416 if(!treeModel.setData(selectedIndex, fldValue.text)) 417 { 418 fldValue.text = nodeValue // if set data failed, restore old value 419 } 420 } 421 } 422 423 Text{ 424 id:txtFilter 425 426 x:450 427 y:txtValue.y 428 height: txtValue.height 429 text: "Filter: " 430 horizontalAlignment:Text.AlignHCenter 431 verticalAlignment:Text.AlignVCenter 432 } 433 434 TextField{ 435 id:fldFilter 436 437 x:txtFilter.x + txtFilter.width 438 y:txtFilter.y 439 height:txtFilter.height 440 } 441 442 property bool bFound: false 443 444 function findIndexByName( index, model, name) 445 { 446 var nodeName = treeModel.data(index, IMTreeModel.NAME).toString(); 447 //console.log("==nodeName==", nodeName, index.row, index.column) 448 449 var UC_NODENAME = nodeName.toUpperCase() 450 var UC_NAME = name.toUpperCase() 451 452 if(UC_NODENAME.search(UC_NAME) != -1) 453 { 454 bFound = true; 455 filterIndex = index 456 return; 457 } 458 459 if (!model.hasChildren(index)) 460 { 461 return; 462 } 463 464 var rows = model.rowCount(index) 465 for (var i = 0; i < rows; i++) 466 { 467 if(bFound) 468 { 469 return; 470 } 471 472 findIndexByName(model.index(i, 0, index), model, name); 473 } 474 } 475 476 Button{ 477 id:btnApplyFilter 478 479 x:fldFilter.x + fldFilter.width + 2 480 y:fldFilter.y 481 height:fldFilter.height 482 483 text:"Apply" 484 enabled:(fldFilter.text != "") 485 486 onClicked:{ 487 bFound = false; 488 findIndexByName(treeModel.index(0, 0), treeModel, fldFilter.text) 489 } 490 } 491 }
效果图: