treeview

news2024/11/16 19:32:54

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


在 Qt5 的 QtQuick.Controls 2.x 中还没有 TreeView 这个控件(在 Qt6 中出了一个继承自 TableView 的 TreeView),而且 QtQuick.Controls 1.x 中的也需要配合 C++ model 来自定义,对于一些简单的需求还要写 C++ model 就显得费事儿。

参照别人的自定义 TreeView 实现,我也使用 ListView 嵌套的方式做了一个简易的 TreeView,主要功能就是根据 json 结构来生成一个树状列表。

(2022-05-28)在最初的版本没考虑 ListView 超过 cache 范围自动销毁的问题,改版后勾选等状态放到外部数组中,释放后重新创建 delegate item 时,会从外部数组中取值进行判断该 item 是否勾选。

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

     

//PS.后面完善了下,效果如下图

代码github:https://github.com/gongjianbo/QmlTreeView

主要代码:

//TreeView.qml

import QtQuick 2.7
import QtQuick.Controls 2.7
 
//代码仅供参考,很多地方都不完善
//还有一些样式没有导出,可以根据需求自己定义
//因为ListView有回收策略,除非cacheBuffer设置很大,所以状态不能保存在delegate.item中
//需要用外部变量或者model来存储delegate.item的状态
Rectangle {
    id: control
 
    property string currentItem //当前选中item
 
    property int spacing: 10    //项之间距离
    property int indent: 5      //子项缩进距离,注意实际还有icon的距离
    property string onSrc: "qrc:/img/on.png"
    property string offSrc: "qrc:/img/off.png"
    property string checkedSrc: "qrc:/img/check.png"
    property string uncheckSrc: "qrc:/img/uncheck.png"
 
    property var checkedArray: [] //当前已勾选的items
    property bool autoExpand: true
 
    //背景
    color: Qt.rgba(2/255,19/255,23/255,128/255)
    border.color: "darkCyan"
    property alias model: list_view.model
    ListView {
        id: list_view
        anchors.fill: parent
        anchors.margins: 10
        //model: //model由外部设置,通过解析json
        property string viewFlag: ""
        delegate: list_delegate
        clip: true
        onModelChanged: {
            console.log('model change')
            checkedArray=[]; //model切换的时候把之前的选中列表清空
        }
    }
    Component {
        id: list_delegate
        Row{
            id: list_itemgroup
            spacing: 5
            property string parentFlag: ListView.view.viewFlag
            //以字符串来标记item
            //字符串内容为parent.itemFlag+model.index
            property string itemFlag: parentFlag+"-"+(model.index+1)
 
            //canvas 画项之间的连接线
            Canvas {
                id: list_canvas
                width: item_titleicon.width+10
                height: list_itemcol.height
                //开了反走样,线会模糊看起来加粗了
                antialiasing: false
                //最后一项的连接线没有尾巴
                property bool isLastItem: (model.index===list_itemgroup.ListView.view.count-1)
                onPaint: {
                    var ctx = getContext("2d")
                    var i=0
                    //ctx.setLineDash([4,2]); 遇到个大问题,不能画虚线
                    // setup the stroke
                    ctx.strokeStyle = Qt.rgba(201/255,202/255,202/255,1)
                    ctx.lineWidth=1
                    // create a path
                    ctx.beginPath()
                    //用短线段来实现虚线效果,判断里-3是防止width(4)超过判断长度
                    //此外还有5的偏移是因为我image是透明背景的,为了不污染到图标
                    //这里我是虚线长4,间隔2,加起来就是6一次循环
                    //效果勉强
                    ctx.moveTo(width/2,0) //如果第一个item虚线是从左侧拉过来,要改很多
                    for(i=0;i<list_itemrow.height/2-5-3;i+=6){
                        ctx.lineTo(width/2,i+4);
                        ctx.moveTo(width/2,i+6);
                    }
 
                    ctx.moveTo(width/2+5,list_itemrow.height/2)
                    for(i=width/2+5;i<width-3;i+=6){
                        ctx.lineTo(i+4,list_itemrow.height/2);
                        ctx.moveTo(i+6,list_itemrow.height/2);
                    }
 
                    if(!isLastItem){
                        ctx.moveTo(width/2,list_itemrow.height/2+5)
                        for(i=list_itemrow.height/2+5;i<height-3;i+=6){
                            ctx.lineTo(width/2,i+4);
                            ctx.moveTo(width/2,i+6);
                        }
                        //ctx.lineTo(10,height)
                    }
                    // stroke path
                    ctx.stroke()
                }
 
                //项图标框--可以是ractangle或者image
                Image {
                    id: item_titleicon
                    visible: false
                    //如果是centerIn的话展开之后就跑到中间去了
                    anchors.left: parent.left
                    anchors.top: parent.top
                    anchors.leftMargin: list_canvas.width/2-width/2
                    anchors.topMargin: list_itemrow.height/2-width/2
                    //根据是否有子项/是否展开加载不同的图片/颜色
                    //color: item_repeater.count
                    //      ?item_sub.visible?"white":"gray"
                    //:"black"
                    //这里没子项或者子项未展开未off,展开了为on
                    source: item_repeater.count?item_sub.visible?offSrc:onSrc:offSrc
 
                    MouseArea{
                        anchors.fill: parent
                        onClicked: {
                            if(item_repeater.count)
                                item_sub.visible=!item_sub.visible;
                        }
                    }
                }
 
                //项勾选框--可以是ractangle或者image
                Image {
                    id: item_optionicon
                    visible: false
                    width: 10
                    height: 10
                    anchors.left: parent.left
                    anchors.top: parent.top
                    anchors.leftMargin: list_canvas.width/2-width/2
                    anchors.topMargin: list_itemrow.height/2-width/2
                    property bool checked: isChecked(itemFlag)
                    //勾选框
                    //color: checked
                    //       ?"lightgreen"
                    //       :"red"
                    source: checked?checkedSrc:uncheckSrc
                    MouseArea {
                        anchors.fill: parent
                        onClicked: {
                            item_optionicon.checked=!item_optionicon.checked;
                            if(item_optionicon.checked){
                                check(itemFlag);
                            }else{
                                uncheck(itemFlag);
                            }
                            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: control.width
                    height: item_text.contentHeight+control.spacing
                    spacing: 5
 
                    Rectangle {
                        height: item_text.contentHeight+control.spacing
                        width: parent.width
                        anchors.verticalCenter: parent.verticalCenter
                        color: (control.currentItem===itemFlag)
                               ?Qt.rgba(101/255,255/255,255/255,38/255)
                               :"transparent"
 
                        Text {
                            id: item_text
                            anchors.left: parent.left
                            anchors.verticalCenter: parent.verticalCenter
                            text: modelData.text
                            font.pixelSize: 14
                            font.family: "Microsoft YaHei UI"
                            color: Qt.rgba(101/255,1,1,1)
                        }
 
                        MouseArea {
                            anchors.fill: parent
                            onClicked: {
                                control.currentItem=itemFlag;
                                console.log("selected",itemFlag)
                            }
                        }
                    }
 
                    Component.onCompleted: {
                        if(modelData.istitle){
                            item_titleicon.visible=true;
                        }else if(modelData.isoption){
                            item_optionicon.visible=true;
                        }
                    }
                }
 
                //放子项
                Column {
                    id: item_sub
                    //也可以像check一样用一个expand数组保存展开状态
                    visible: control.autoExpand
                    //上级左侧距离=小图标宽+x偏移
                    x: control.indent
                    Item {
                        width: 10
                        height: item_repeater.contentHeight
                        //需要加个item来撑开,如果用Repeator获取不到count
                        ListView {
                            id: item_repeater
                            anchors.fill: parent
                            delegate: list_delegate
                            model: modelData.subnodes
                            property string viewFlag: itemFlag
                        }
                    }
                }
 
            }
        }//end list_itemgroup
    }//end list_delegate
 
    //勾选时放入arr中
    function check(itemFlag){
        checkedArray.push(itemFlag);
    }
 
    //取消勾选时从arr移除
    function uncheck(itemFlag){
        var i = checkedArray.length;
 
        while (i--) {
            if (checkedArray[i] === itemFlag) {
                checkedArray.splice(i,1);
                break;
            }
        }
    }
 
    //判断是否check
    function isChecked(itemFlag){
        var i = checkedArray.length;
 
        while (i--) {
            if (checkedArray[i] === itemFlag) {
                return true;
            }
        }
        return false;
    }
}
//main.qml

import QtQuick 2.7
import QtQuick.Controls 2.7
import QtQuick.Window 2.7
 
Window {
    id: root_window
    visible: true
    width: 640
    height: 480
    title: qsTr("QmlTreeView By GongJianBo")
    color: Qt.rgba(3/255,26/255,35/255,1)
 
    //滚动条可以自己设置
    //ListView横向滚动条需要设置如下两个参数(如果是竖向的ListView)
    //contentWidth: 500
    //flickableDirection: Flickable.AutoFlickIfNeeded
    TreeView{
        id: item_tree
        width: parent.width/2
        anchors{
            left: parent.left
            top: parent.top
            bottom: parent.bottom
            margins: 10
        }
        //model: []
 
        //set model data
        Component.onCompleted: {
            console.log(1)
            root_window.setTestDataA();
            console.log(2)
        }
    }
 
    Column{
        anchors{
            right: parent.right
            top: parent.top
            margins: 10
        }
        spacing: 10
        Button{
            text: "ChangeModel"
            checkable: true
            //changed model data
            onClicked: {
                if(checked){
                    root_window.setTestDataB();
                }else{
                    root_window.setTestDataA();
                }
            }
        }
        Button{
            text: "AutoExpand"
            onClicked: item_tree.autoExpand=!item_tree.autoExpand
        }
    }
 
    function setTestDataA(){
        item_tree.model=JSON.parse('[
        {
            "text":"1 one",
            "istitle":true,
            "subnodes":[
                {"text":"1-1 two","istitle":true},
                {
                    "text":"1-2 two",
                    "istitle":true,
                    "subnodes":[
                        {"text":"1-2-1 three","isoption":true},
                        {"text":"1-2-2 three","isoption":true}
                    ]
                }
            ]
        },
        {
            "text":"2 one",
            "istitle":true,
            "subnodes":[
                {"text":"2-1 two","istitle":true},
                {
                    "text":"2-2 two",
                    "istitle":true,
                    "subnodes":[
                        {"text":"2-2-1 three","isoption":true},
                        {"text":"2-2-2 three","isoption":true}
                    ]
                }
            ]
        },
        {
            "text":"3 one",
            "istitle":true,
            "subnodes":[
                {"text":"3-1 two","istitle":true},
                {"text":"3-2 two","istitle":true}
            ]
        },
        {
            "text":"4 one",
            "istitle":true,
            "subnodes":[
                {"text":"4-1 two","istitle":true},
                {
                    "text":"4-2 two",
                    "istitle":true,
                    "subnodes":[
                        {"text":"4-2-1 three","isoption":true},
                        {"text":"4-2-2 three","isoption":true}
                    ]
                }
            ]
        },
        {
            "text":"5 one",
            "istitle":true,
            "subnodes":[
                {"text":"5-1 two","istitle":true},
                {
                    "text":"5-2 two",
                    "istitle":true,
                    "subnodes":[
                        {"text":"5-2-1 three","isoption":true},
                        {"text":"5-2-2 three","isoption":true}
                    ]
                }
            ]
        },
        {
            "text":"6 one",
            "istitle":true,
            "subnodes":[
                {"text":"6-1 two","istitle":true},
                {"text":"6-2 two","istitle":true}
            ]
        }
    ]')
    }
 
    function setTestDataB(){
        item_tree.model=JSON.parse('[
        {
            "text":"1 one",
            "istitle":true,
            "subnodes":[
                {
                    "text":"1-1 two",
                    "istitle":true,
                    "subnodes":[
                        {"text":"1-1-1 three","isoption":true},
                        {"text":"1-1-2 three","isoption":true}
                    ]
                },
                {
                    "text":"1-2 two",
                    "istitle":true,
                    "subnodes":[
                        {"text":"1-2-1 three","isoption":true},
                        {"text":"1-2-2 three","isoption":true}
                    ]
                }
            ]
        },
        {
            "text":"2 one",
            "istitle":true,
            "subnodes":[
                {"text":"2-1 two","istitle":true},
                {
                    "text":"2-2 two",
                    "istitle":true,
                    "subnodes":[
                        {"text":"2-2-1 three","isoption":true},
                        {"text":"2-2-2 three","isoption":true}
                    ]
                }
            ]
        },
        {"text":"3 one","istitle":true},
        {
            "text":"4 one",
            "istitle":true,
            "subnodes":[
                {"text":"4-1 two","istitle":true},
                {"text":"4-2 two","istitle":true}
            ]
        }
    ]')
    }
}
2.参考
思路参考:https://github.com/peihaowang/QmlTreeWidget

ListView 文档:https://doc.qt.io/qt-5/qml-qtquick-listview.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1419997.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Win10 双网卡实现同时上内外网

因为需要同时上内网和外网&#xff0c;但公司做了网络隔离&#xff0c;不能同时上内外网&#xff0c;所以多加了块无线网卡&#xff0c;配置双网关实现同时上内外网&#xff0c;互不影响 打开 Windows PowerShell&#xff08;管理员&#xff09;&#xff0c;输入&#xff1a;ro…

Github 2024-01-30 开源项目日报 Top10

根据Github Trendings的统计&#xff0c;今日(2024-01-30统计)共有10个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量Python项目4TypeScript项目2Jupyter Notebook项目2HTML项目1Rust项目1C项目1 稳定扩散Web UI 创建周期&…

C++核心编程:类和对象 笔记

4.类和对象 C面向对象的三大特性为:封装,继承,多态C认为万事万物都皆为对象&#xff0c;对象上有其属性和行为 例如&#xff1a; 人可以作为对象&#xff0c;属性有姓名、年龄、身高、体重...,行为有走、跑、跳、说话...车可以作为对象&#xff0c;属性有轮胎、方向盘、车灯…

万兆网络数据传输-scp加速

在万兆甚至更高的网络带宽场景下 scp 的传输效率并不如人意。毕竟 scp 是旧时代的产物&#xff0c;那时千兆网络都很罕见。以下通过修改压缩方式的方法提升数据的传输速度。同时也采用 nc &#xff0c; bbcp 和 rsync 进行了对比测试。 目录 scp采用默认方式更改压缩算法为 aes…

seata 分布式

一、下载安装seata 已经下载好的朋友可以跳过这个步骤。这里下载的是seata1.6.1这个版本。 1、进入seata官网 地址&#xff1a; https://seata.io/zh-cn/index.html 2、进入下载 3、点击下载地址 下载地址&#xff1a; https://github.com/seata/seata 二、配置seata 进入c…

vue3项目中让echarts适应div的大小变化,跟随div的大小改变图表大小

目录如下 我的项目环境如下利用element-resize-detector插件监听元素大小变化element-resize-detector插件的用法完整代码如下&#xff1a;结果如下 在做项目的时候&#xff0c;经常会使用到echarts&#xff0c;特别是在做一些大屏项目的时候。有时候我们是需要根据div的大小改…

2023年全国职业院校技能大赛(高职组)“云计算应用”赛项赛卷9

某企业根据自身业务需求&#xff0c;实施数字化转型&#xff0c;规划和建设数字化平台&#xff0c;平台聚焦“DevOps开发运维一体化”和“数据驱动产品开发”&#xff0c;拟采用开源OpenStack搭建企业内部私有云平台&#xff0c;开源Kubernetes搭建云原生服务平台&#xff0c;选…

redis—Zset有序集合

目录 前言 1.常见命令 2.使用场景 3.渐进式遍历 4.数据库管理 前言 有序集合相对于字符串、列表、哈希、集合来说会有一-些陌生。它保留了集合不能有重复成员的 特点&#xff0c;但与集合不同的是&#xff0c;有序集合中的每个元素都有-个唯- -的浮 点类型的分数(score) …

c语言常量详解 全

c语言常量详解 全 一 常量的基本概念及分类二 常量的存储方式三 c语言常量和变量的具体区别四 字面常量详解4.1 常见类型的字面常量及其示例&#xff1a;4.2 字面常量的使用情况4.3 字面常量的优点 五 const 关键字常量详解5.1 const关键字在C语言中的使用情况&#xff1a;5.2 …

山海鲸可视化大屏:引领企业洞悉市场,提升客户价值的秘密武器

随着大数据时代的到来&#xff0c;企业面临着前所未有的机遇与挑战。如何从海量数据中挖掘出有价值的信息&#xff0c;洞察市场趋势&#xff0c;提升客户价值&#xff0c;成为了企业发展的重要课题。山海鲸可视化企业客户价值分析大屏&#xff0c;为企业提供了一个全新的解决方…

利用外卖系统源码构建高效的在线订餐平台

在当今数字化时代&#xff0c;外卖服务已成为人们日常生活中不可或缺的一部分。为了满足用户需求&#xff0c;许多创业者和企业都希望搭建自己的在线订餐平台。利用现有的外卖系统源码&#xff0c;可以快速构建一个高效、安全的在线订餐平台。本文将介绍如何利用外卖系统源码来…

播报 | 天空卫士入围FreeBuf《CCSIP 2023中国网络安全产业全景图》16个细分领域

2024年1月24&#xff0c;国内安全行业门户FreeBuf旗下FreeBuf咨询正式发布《CCSIP 2023中国网络安全产业全景图》&#xff08;第六版&#xff09;。 天空卫士成功入围SASE、数据防泄露&#xff08;DLP&#xff09;、分类分级、数据安全治理(解决方案)、数据安全管控&#xff08…

Django问题报错:Cannot resolve keyword ‘name‘ into field. Choices are: course, id

笔者在进行登录注册实验用户名已经注册过的操作时报错 一、错误位置 二、问题原因 使用Student模型时参数名错误 三、解决办法 修改为与Student模型中对应的参数名,问题解决

每日一题 力扣514自由之路

514. 自由之路 题目描述&#xff1a; 电子游戏“辐射4”中&#xff0c;任务 “通向自由” 要求玩家到达名为 “Freedom Trail Ring” 的金属表盘&#xff0c;并使用表盘拼写特定关键词才能开门。 给定一个字符串 ring &#xff0c;表示刻在外环上的编码&#xff1b;给定另一…

插值(Python)

插值 插值是数学和计算机科学领域中的一种技术&#xff0c;用于在给定一些离散数据点的情况下&#xff0c;估计在这些点之间的数值。插值的目标是通过某种函数&#xff08;插值函数&#xff09;来逼近或拟合这些离散数据&#xff0c;从而使得在原始数据点之间的数值也有合理的估…

物流信息网

技术架构&#xff1a; JSPMySQL 功能描述&#xff1a; 物流信息网主要用于实现网上自主物流&#xff0c;基本功能包括&#xff1a;登录、查询、时效查询、价格查询、注册等。本系统结构如下&#xff1a; &#xff08;1&#xff09;普通用户&#xff1a; 登录&#xff1a…

GIS毕业的那10000人,你们都在做什么?

根据阳光高考网&#xff08;教育部指定高校招生平台&#xff09;的数据显示&#xff0c;截止到2022年12月31日&#xff0c;全国高校GIS&#xff08;地理信息科学&#xff09;本科毕业生人数为&#xff1a;9000-10000人。 我们还可以看到&#xff0c;地信专业的男女比例为&#…

强化学习原理python篇06(拓展)——DQN拓展

强化学习原理python篇06&#xff08;拓展&#xff09;——DQN拓展 n-steps代码结果 Double-DQN代码结果 Dueling-DQN代码结果 Ref 拓展篇参考赵世钰老师的教材和Maxim Lapan 深度学习强化学习实践&#xff08;第二版&#xff09;&#xff0c;请各位结合阅读&#xff0c;本合集只…

nginx负载均衡案例

大家好今天给大家带来nginx负载均衡实验案例,首大家先看一下我的各类版本信息。&#xff08;还有两台设备信息相同就不展示了&#xff09; 一&#xff0c;搭建nginx环境 ❶首先创建Nginx的目录并进入&#xff1a; [rootlocalhost]# mkdir /soft && mkdir /soft/nginx…

Python qt.qpa.xcb: could not connect to display解决办法

遇到问题&#xff1a;qt.qpa.xcb: could not connect to display 解决办法&#xff0c;在命令行输入&#xff1a; export DISPLAY:0 然后重新跑python程序&#xff0c;解决&#xff01; 参考博客&#xff1a;qt.qpa.xcb: could not connect to displayqt.qpa.plugin: Could …