QML自定义一个TreeView,使用ListView递归

    xiaoxiao2025-01-19  11

    0.前言

    在QtQuick.Controls 2.x中还没有TreeView这个控件,而且1.x中的也需要配合cpp来自定义,感觉很不方便,好不容易再网上找到了完全QML自定义的,不过很多地方都不符合自己需求,于是自己写了一个。

    主要功能就是根据json来生成一个树状列表。

    1.展示

    先看看ui效果图-1和自己的demo图-2。三级目录,其中最后一级是勾选框。我用黑色表示无子项,白色有子项已展开,灰色有子项未展开,绿色已勾选,红色未勾选。实际使用时会替换为image。

         

     

    //PS.后面完善了下,效果如下图,代码github:https://github.com/gongjianbo/QmlTreeView

    下面是初版的代码(用的Qt5.9,应该改下控件版本低版本Qt也可以用):

    //TreeView.qml

    import QtQuick 2.7 import QtQuick.Controls 2.1 Item{ id:tree_root property alias model: list_view.model property int currentItem: 0 //当前选中item property variant checkedArray: [] //当前已勾选的items //背景 Rectangle{ id:background_rect anchors.fill: parent color: "green" border.width: 1 border.color: "darkgreen" } //主要是为了可以横向滚动,listview字节有点问题 Flickable { anchors.fill: parent contentWidth: 500 clip: true ListView{ id:list_view //通过+1来给每个item一个唯一的index //可以配合root的currentItem来做高亮 property int itemCount: 0 anchors.fill: parent anchors.margins: 1 //model: //model由外部设置,通过解析json delegate: list_delegate //clip: true //这里不用加 onModelChanged: { checkedArray=[]; } } //end list_view Component{ id:list_delegate Row{ id:list_itemgroup //canvas 画项之间的连接线 Canvas{ id:list_canvas width: 20 height: list_itemcol.height //最后一项的连接线没有尾巴 property bool isLastItem: (index==parent.ListView.view.count-1) onPaint: { var ctx = getContext("2d") // setup the stroke ctx.strokeStyle = "red" // create a path ctx.beginPath() ctx.moveTo(10,0) ctx.lineTo(10,list_itemrow.height/2) // ctx.moveTo(10,0) ctx.lineTo(20,list_itemrow.height/2) if(!isLastItem){ ctx.moveTo(10,list_itemrow.height/2) ctx.lineTo(10,height) } // stroke path ctx.stroke() } //项图标框 Rectangle{ id:item_titleicon visible: false width: 10 height: 10 anchors.left: parent.left anchors.top: parent.top anchors.leftMargin: list_canvas.width/4 anchors.topMargin: list_itemrow.height/4 //anchors.verticalCenter: parent.verticalCenter //可以替换为image,根据是否有子项/是否展开加载不同的图片 color: item_repeater.count ?item_sub.visible?"white":"gray" :"black" MouseArea{ anchors.fill: parent onClicked: { if(item_repeater.count) item_sub.visible=!item_sub.visible; } } } //项勾选框 Rectangle{ id:item_optionicon visible: false width: 10 height: 10 anchors.left: parent.left anchors.top: parent.top anchors.leftMargin: list_canvas.width/4 anchors.topMargin: list_itemrow.height/4 //anchors.verticalCenter: parent.verticalCenter property bool checked: false //可以替换为image,勾选框 color: checked ?"lightgreen" :"red" MouseArea{ anchors.fill: parent onClicked: { item_optionicon.checked=!item_optionicon.checked; if(item_optionicon.checked){ check(list_itemrow.itemIndex); }else{ uncheck(list_itemrow.itemIndex); } var str="checked "; for(var i in checkedArray) str+=String(checkedArray[i])+" "; console.log(str) } } } } //项内容:包含一行item和子项的listview Column{ id:list_itemcol //这一项的内容,这里只加了一个text Row { id:list_itemrow width: list_view.width height: item_text.implicitHeight+6 spacing: 5 property int itemIndex; Rectangle{ height: item_text.implicitHeight+6 width: parent.width anchors.verticalCenter: parent.verticalCenter color: (currentItem===list_itemrow.itemIndex) ?"cyan" :"orange" Text { id:item_text anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter text: modelData.text font.pixelSize: 20 font.family: "SimSun" } MouseArea{ anchors.fill: parent onClicked: { currentItem=list_itemrow.itemIndex; console.log("selected",list_itemrow.itemIndex) } } } Component.onCompleted: { list_itemrow.itemIndex=list_view.itemCount; list_view.itemCount+=1; if(modelData.istitle) item_titleicon.visible=true; else if(modelData.isoption) item_optionicon.visible=true; } } //放子项 Column{ id:item_sub visible: false //上级左侧距离=小图标宽+x偏移 x:10 Item { width: 10 height: item_repeater.contentHeight //需要加个item来撑开,如果用Repeator获取不到count ListView{ id:item_repeater anchors.fill: parent delegate: list_delegate model:modelData.subnodes } } } } } //end list_itemgroup } //end list_delegate } //end Flickable //勾选时放入arr中 function check(index){ checkedArray.push(index); } //取消勾选时从arr移除 function uncheck(index){ var i = checkedArray.length; while (i--) { if (checkedArray[i] === index) { checkedArray.splice(i,1); break; } } } }

    //main.qml

    import QtQuick 2.9 import QtQuick.Window 2.2 Window { visible: true width: 640 height: 480 title: qsTr("Hello World") TreeView{ id:tree width: 300 anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom //model: [] Component.onCompleted: { //console.log(model) model=JSON.parse('[ { "text":"1", "istitle":true, "subnodes":[ {"text":"1-1","istitle":true}, { "text":"1-2", "istitle":true, "subnodes":[ {"text":"1-2-1","isoption":true}, {"text":"1-2-2","isoption":true} ] } ] }, { "text":"2", "istitle":true, "subnodes":[ {"text":"2-1","istitle":true}, { "text":"2-2", "istitle":true, "subnodes":[ {"text":"2-2-1","isoption":true}, {"text":"2-2-2","isoption":true} ] } ] }, {"text":"3","istitle":true}, { "text":"4", "istitle":true, "subnodes":[ {"text":"4-1","istitle":true}, {"text":"4-2","istitle":true} ] } ]') } } }

    2.参考

     思路参考:https://github.com/peihaowang/QmlTreeWidget

     

     

    最新回复(0)