0.前言

Qt5 QML Controls1.4 中的 TreeView 存在诸多问题,比如节点连接的虚线不好实现,currentIndex 的设置和 changed 信号的触发等。我想主要的原因在于 TreeView 是派生自 BasicTableView,而 TableView 内部又是由 ListView 实现的。

正好项目用到了 TreeView,就踩一踩坑,发现 currentIndex 的很多行为是反直觉的,和 QWidgets 的 QTreeView 逻辑都不一样。比如没法直接设置 currentIndex,又比如收起或删除子节点后,currentIndex 不是指向根节点,而是内部 ListView 的下一行节点。如果时间充裕,建议还是自己实现一个 TreeView。

本文主要总结下遇到的 currentIndex 相关的一些问题,因为目前主要用单选,所以处理的场景也是单选的。都是些零碎的小问题,后面可能会补充一些,以及修复 Demo 的 Bug。

开发环境:Win10 + MSVC2019 + Qt5.15.2 64bit

Demo 链接:https://github.com/gongjianbo/MyTestCode/tree/master/Qml/TestQml_20221120_TreeSelection

1.探索问题

当展开收起,或者增删节点的时候,如果选中项在操作节点所在行的下面,TreeView 的 currentIndexChanged 会触发两次,第一次触发时会是一个错误的值。查看源码可以看到 currentIndex 的实现如下:

    readonly property var currentIndex: modelAdaptor.updateCount, modelAdaptor.mapRowToModelIndex(__currentRow)property alias __currentRow: listView.currentIndex__model: TreeModelAdaptor {id: modelAdaptormodel: root.model// Hack to force re-evaluation of the currentIndex bindingproperty int updateCount: 0onModelReset: updateCount++onRowsInserted: updateCount++onRowsRemoved: updateCount++onExpanded: root.expanded(index)onCollapsed: root.collapsed(index)}

Adaptor 处理 Tree 和内部 List 的映射,内部 ListView 只持有展开显示的节点内容,展开收起对其来说和增删是一样的。currentIndex 是绑定到一个逗号表达式,当展开收起或者增删节点时,updateCount++ 触发 currentIndex 变更,但是此时 ListView 的 currentIndex 还没更新,所以会得到一个错误的 currentIndex,等 ListView 刷新了才能计算出一个正确的 currentIndex,这就解释了为什么会触发两次 currentIndexChanged。

还有个较大的问题是,收起或删除选中节点,默认是取内部 ListView 的下一行,如果是删除展开子树的末尾项,不会自动选中前面的兄弟节点,而是往下跑到另一个子树去了。解决思路就是收起和删除时先把 currentIndex 设置为操作之后的 index。

既然 TreeView 的 currentIndex 更新有问题,我们引入 ItemSelectionModel 来处理选择。引入 ItemSelectionModel 后,发现 selection 的 currentIndex 和 TreeView 的 currentIndex 有时候会不一致,高亮优先显示 selection 的,但是按键优先用 view 的,两个 index 不一致的时候行为就很怪异。解决方式也是提前设置操作之后的 index,不使用默认的行为。

ItemSelectionModel 有个问题,TreeModel reset 数据的时候,不会触发 currentIndexChanged,从源码来看是因为某些原因他把信号给阻塞了:

void QItemSelectionModel::reset()
{const QSignalBlocker blocker(this);clear();
}

通过前面的一系列问题,我们很多地方都需要自己预置 currentIndex,TreeView 没有没有设置的接口,但是可以通过设置内部 ListView 的 currentIndex 来实现,而 ItemSelectionModel 提供了 setCurrentIndex 的接口。要实现代码选中某个节点,ListView 当前行和 TreeView 的 index 需要转换。Adaptor 有一个公开的函数 mapRowToModelIndex 可以将行转为 index,但是 index 转行的接口没有注册为 QML 可访问,迫不得已得把 Adaptor 的源码复制粘贴一份,导出 itemIndex 接口。

    //选中function selectIndex(index) {if (index.valid) {__listView.forceActiveFocus()__listView.currentIndex = modelAdaptor.itemIndex(index)mouseArea.mouseSelect(index, Qt.NoModifier, false)}}//清除选中function clearSelect() {__listView.forceActiveFocus()__listView.currentIndex = -1if (selection) {selection.clear()}}

自定义 Adaptor 后 TreeView 和 TreeViewStyle 等都得 copy 源码自定义一下,因为 QML 很多东西不是多态性的,不是直接派生个新组件重写某个属性就完了,而且 Controls1.x 的组件耦合性又很强,需要把相关的都修改一下,可以参照前言中我的 Demo。

2.修改片段

自定义 Apdator 后,除了公开 itemIndex 接口,还有个重要的任务就是控制收起和删除节点后,当前节点不要只往后面跑,优先考虑兄弟节点和根节点。

    __model: BasicTreeModelAdaptor {id: modelAdaptormodel: root.model// Hack to force re-evaluation of the currentIndex bindingproperty int updateCount: 0onModelReset: updateCount++onRowsInserted: updateCount++onRowsRemoved: updateCount++onExpanded: root.expanded(index)onCollapsed: root.collapsed(index)onRowsAboutToBeRemoved: function(parent, first, last) {//Adaptor的row是仅可见的row//删除or收起某一行则触发row remove//如果隐藏的选中行,默认是到了下一行节点,无论这个节点是不是在同一个子树//console.log(first, last, root.__listView.currentIndex, root.currentIndex)if (first < 0 || root.__listView.currentIndex < first ||root.__listView.currentIndex > last)returnvar temp = root.currentIndexif (!temp.valid)return//如果是收起则前往收起节点,如果是删除,有兄弟从后往前找兄弟,没兄弟就找上一层节点//因为adaptor是个listview的model,所以参数是row,要转换成tree的index//先找下一个节点是不是兄弟,或者前面没节点了var next_index = modelAdaptor.mapRowToModelIndex(last + 1)if (first === 0 || next_index.valid && next_index.parent === temp.parent) {__listView.forceActiveFocus()__listView.currentIndex = last + 1mouseArea.mouseSelect(next_index, Qt.NoModifier, false)return}//不然就去上一个节点是否是祖先或者兄弟var prev_index = modelAdaptor.mapRowToModelIndex(first - 1)//console.log('--',temp, prev_index, temp.parent, prev_index.parent)if (!prev_index.valid)return//current和prev都往上一层找,所以需要两层循环while (temp.valid) {//console.log('--',temp, prev_index, temp.parent, prev_index.parent)var prev_temp = prev_indexwhile (prev_temp.valid) {//console.log('++',temp, prev_temp, temp.parent, prev_temp.parent)if (temp.parent === prev_temp || temp.parent === prev_temp.parent) {__listView.forceActiveFocus()__listView.currentIndex = modelAdaptor.itemIndex(prev_temp)mouseArea.mouseSelect(prev_temp, Qt.NoModifier, false)return;}prev_temp = prev_temp.parent}temp = temp.parent}//可能还有其他没考虑到的情况console.log('other row remove', first, last, root.__listView.currentIndex)}}

双击节点可以增加展开/收起逻辑。

        onDoubleClicked: {var clickIndex = __listView.indexAt(0, mouseY + __listView.contentY)if (clickIndex > -1) {var modelIndex = modelAdaptor.mapRowToModelIndex(clickIndex)//增加双击展开收起if (!branchDecorationContains(mouse.x, mouse.y)) {if (modelAdaptor.isExpanded(modelIndex))modelAdaptor.collapse(modelIndex)elsemodelAdaptor.expand(modelIndex)}if (!root.__activateItemOnSingleClick)root.activated(modelIndex)root.doubleClicked(modelIndex)}}

Qt5 QML TreeView currentIndex当前选中项的一些问题相关推荐

  1. c# winform TreeView与ListView的项互相拖动的应用[转载]

    转载modede 很久没写教程了,停顿了有两年之久了.今天我们来讨论讨论 winform 中TreeView与ListView,通观两者很相似,只是一个是树形结构,一个是列表结构. 今天自己创建的群里 ...

  2. jquery 获取一组元素的选中项 - 函数、jquery获取复选框值、jquery获取单选按钮值...

    做表单提交时,如果现在还在用form提交,用户体验很差,所以一般使用ajax提交. 其中需要获取每个表单输入元素的值,获取的时候像文本框这些还好说,Jquery提供了 .val() 方法,获取很方便, ...

  3. [开发笔记]-jQuery获取radio选中项的值

    <title></title><script src="js/jquery-1.7.2.min.js"></script><s ...

  4. ASP.NET MVC中为DropDownListFor设置选中项的方法

    在MVC中,当涉及到强类型编辑页,如果有select元素,需要根据当前Model的某个属性值,让Select的某项选中.本篇只整理思路,不涉及完整代码. □ 思路 往前台视图传的类型是List< ...

  5. [开发笔记]-winfom ListBox控件选中项上下移动排序

    实现ListBox控件选中项上下移动重新排序功能 效果图: 移动后效果: 代码: /// <summary>/// 上移选中项/// </summary>/// <par ...

  6. ListView控件获取选中项的内容 c# 114867417

    ListView控件获取选中项的内容 c# 114867417 引入控件 定义列 基本功能 整行选中 打开整行选中 true 获取选中项的数据

  7. Jquery怎么获取select选中项 自定义属性的值

    Jquery如何获取select选中项 自定义属性的值? HTML code <select id="ddl" οnchange="ddl_change(this) ...

  8. Android ListView选中项居中放大(使用上下键控制,非触屏)

    最近有一个功能机项目(不支持触屏)需要实现ListView选中项停在中间放大的效果,网络上的大多是用手滑动屏幕的效果,只能自己写一个. 效果实现了,但是还有小问题.实现原理主要是 1调用setSele ...

  9. jquery操作select下拉框的各种方法,获取选中项的值或文本,根据指定的值或文本选中select的option项等...

    简介jquery里对select进行各种操作的方法,如联动.取值.根据值或文本来选中指定的select下拉框指定的option选项,读取select选中项的值和文本等. 这一章,站长总结一下jquer ...

最新文章

  1. 趁ofo退出美市场 Uber不计成本发展共享单车
  2. Devstack 配置文件说明手册
  3. python最高版本-Python学习路线图(2020年最新版)
  4. php在页面循环输出标签,自定义页面循环
  5. android fragment传递数据,Android 两个Fragment之间传递数据实例详解
  6. 阿里云自研数据库支撑双11,助力电商客户订单峰值突破每秒20万笔
  7. 工作流实战_07_flowable 流程定义查看流程图和xml
  8. STM32串口寄存器操作(转)
  9. 同学,要开学了,你的导师也很焦虑
  10. 神经网络的 Delta 学习规则(learning rule)
  11. Windows核心编程_Visual Studio2019找不到MFC项目
  12. 【UVA12169】不爽的裁判
  13. matlab画直方图_直方图规定化+暗通道去雾 python
  14. winrar加密分析
  15. 用Python实现双色球随机选号
  16. Java合并PDF文件方式
  17. 服务器托管过程中勒索病毒的预防
  18. android4.4呼叫转移,安卓呼叫转移
  19. Android人脸检测功能和检测特效
  20. wating for network configuration unity恢复

热门文章

  1. 阿里云 apt软件云。ubuntu16 17适用
  2. 新能源电动车充电隐患不得不防,这些常识要知道
  3. 从初学时整理的jq资料
  4. 海龟作图python小猪佩奇_python海龟做图20秒完成小猪佩奇,附源码!
  5. peewee操作mysql_Peewee 使用
  6. MAC获取公钥的步骤
  7. java递归红与黑答案,递归--红与黑
  8. 仿 手机QQ 登录、注册、找回密码、好友列表、QQ状态等功能的实现
  9. 做量化交易的第一步,Python爬取股票数据
  10. 2020最新开发及环境搭建类经典面试题