Qt5 QML TreeView currentIndex当前选中项的一些问题
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当前选中项的一些问题相关推荐
- c# winform TreeView与ListView的项互相拖动的应用[转载]
转载modede 很久没写教程了,停顿了有两年之久了.今天我们来讨论讨论 winform 中TreeView与ListView,通观两者很相似,只是一个是树形结构,一个是列表结构. 今天自己创建的群里 ...
- jquery 获取一组元素的选中项 - 函数、jquery获取复选框值、jquery获取单选按钮值...
做表单提交时,如果现在还在用form提交,用户体验很差,所以一般使用ajax提交. 其中需要获取每个表单输入元素的值,获取的时候像文本框这些还好说,Jquery提供了 .val() 方法,获取很方便, ...
- [开发笔记]-jQuery获取radio选中项的值
<title></title><script src="js/jquery-1.7.2.min.js"></script><s ...
- ASP.NET MVC中为DropDownListFor设置选中项的方法
在MVC中,当涉及到强类型编辑页,如果有select元素,需要根据当前Model的某个属性值,让Select的某项选中.本篇只整理思路,不涉及完整代码. □ 思路 往前台视图传的类型是List< ...
- [开发笔记]-winfom ListBox控件选中项上下移动排序
实现ListBox控件选中项上下移动重新排序功能 效果图: 移动后效果: 代码: /// <summary>/// 上移选中项/// </summary>/// <par ...
- ListView控件获取选中项的内容 c# 114867417
ListView控件获取选中项的内容 c# 114867417 引入控件 定义列 基本功能 整行选中 打开整行选中 true 获取选中项的数据
- Jquery怎么获取select选中项 自定义属性的值
Jquery如何获取select选中项 自定义属性的值? HTML code <select id="ddl" οnchange="ddl_change(this) ...
- Android ListView选中项居中放大(使用上下键控制,非触屏)
最近有一个功能机项目(不支持触屏)需要实现ListView选中项停在中间放大的效果,网络上的大多是用手滑动屏幕的效果,只能自己写一个. 效果实现了,但是还有小问题.实现原理主要是 1调用setSele ...
- jquery操作select下拉框的各种方法,获取选中项的值或文本,根据指定的值或文本选中select的option项等...
简介jquery里对select进行各种操作的方法,如联动.取值.根据值或文本来选中指定的select下拉框指定的option选项,读取select选中项的值和文本等. 这一章,站长总结一下jquer ...
最新文章
- 趁ofo退出美市场 Uber不计成本发展共享单车
- Devstack 配置文件说明手册
- python最高版本-Python学习路线图(2020年最新版)
- php在页面循环输出标签,自定义页面循环
- android fragment传递数据,Android 两个Fragment之间传递数据实例详解
- 阿里云自研数据库支撑双11,助力电商客户订单峰值突破每秒20万笔
- 工作流实战_07_flowable 流程定义查看流程图和xml
- STM32串口寄存器操作(转)
- 同学,要开学了,你的导师也很焦虑
- 神经网络的 Delta 学习规则(learning rule)
- Windows核心编程_Visual Studio2019找不到MFC项目
- 【UVA12169】不爽的裁判
- matlab画直方图_直方图规定化+暗通道去雾 python
- winrar加密分析
- 用Python实现双色球随机选号
- Java合并PDF文件方式
- 服务器托管过程中勒索病毒的预防
- android4.4呼叫转移,安卓呼叫转移
- Android人脸检测功能和检测特效
- wating for network configuration unity恢复