【QT表格-6】QTableWidget的currentCellChanged实现中途撤销

背景:

【QT表格-1】QStandardItem的堆内存释放需要单独delete,还是随QStandardItemModel的remove或clear自动销毁?-CSDN博客

【QT表格-2】QTableWidget单元格结束编辑操作endEditting_qtablewidget 单元格编辑事件-CSDN博客

【QT表格-3】QTableWidget导入/导出excel通用代码,不需要安装office,不依赖任何多余环境,甚至不依赖编程语言_qt excel-CSDN博客

【QT表格-4】由QTableView/QTableWidget显示进度条和按钮,理解qt代理delegate用法_qtablewidget代理-CSDN博客

【QT表格-5】QTableView用代码设置选中状态-CSDN博客

一个主子表结构,当切换主表行时, 子表对应更新显示数据。主子表都可以编辑并保存。

当子表编辑后未保存时,如果切换主表行,应提示保存,用户可以选择“是”、“否”、“取消”。其实“是”和“否”好实现,因为都是保持顺序执行,只不过选择是否执行保存而已。但“取消”就不一样了,需要停止下面的操作。

这种情况比较多见,比如某个文本编辑器,如果编辑的内容,关闭时就应该有这样的询问。并根据用户选择进行相应操作。

按说用过vs的winform的同行应该知道,这个并不难。现在看来是因为vs提供了相当丰富的消息事件响应机制。比如关闭窗口会有一个类似CloseQuery这样的消息,只要对应写它的事件就好了。但是qt当中,思路会有很大区别。

问题:

实际上我尝试了很多方法已经就差cancel动作了。在QTableWidget::currentCellChanged槽当中判断,如果用户放弃的操作,我会重新把焦点放到previous位置,

this->setCurrentCell(previousRow, previousColumn);

这样看起来就是“回滚”了用户操作。但实际上效果是,焦点确实回去了,所有属性也回去了,比方说,currentRow或者currentItem之类,都没问题,但单元格的背景色没回去,也就是看起来还是选择了下一个位置。

这不傻了么?怎么试都不行,我猜想qt肯定是在currentCellChanged之后还干了什么事,而这个信号没有提供返回值和指针参数或者引用参数,等于没法控制。所以开始研究。

开胃菜:

以上述“窗口关闭前询问”为例,其实qt有个closeEvent函数,重写它就行了。它有个event指针参数,通过它是否accept就能控制是否继续。比如:

void MainWindow::closeEvent(QCloseEvent *event)
{const QString sTitle = "程序退出";const QString sMessage = "此操作会退出系统\n""当前未保存的数据将丢失\n""要继续吗?";if (QMessageBox::question(this, sTitle, sMessage, QMessageBox::Yes|QMessageBox::Cancel,QMessageBox::Cancel)== QMessageBox::Yes){event->accept();}else{event->ignore();}
}

就像上图这样,挺简单的。

同理,很多需要控制是否继续的做法,都类似。

回到正题,最初我的需求怎么办?我需要切换主表行时来个询问,并决定是否继续。

分析:

如果直接套用closeEvent的思路,是想不通的。因为那是继承重写的做法。而表格是某个界面中的子对象,询问的操作需要在表格外实现,怎么重写?

像这种常见的界面互动,要么直接调用函数,要么信号槽。不想随便触动函数指针的概念,我感觉应该先深入了解qt的方式。

直接调用:业务是需要表格内部,根据外界的用户选择,来决定内部的流程是否继续。理论上是表格内部调用外部。但制作表格类的时候,是不知道外界是否需要询问,或者如何询问的。貌似无解。

信号槽:界面线程的互动属于directConnection,效果很顺序执行一样。这里涉及到信号槽的一些基础概念。主要是connect函数最后一个连接参数的应用。以手册为准。

【qt信号槽-5】信号槽相关注意事项记录-CSDN博客

但是信号槽怎么互动?发出去再传回来?难道需要收发两次?显然不是好办法,毕竟繁琐,主要是用起来感觉还不是随大流的风格。

过程:

过程艰辛,最终我是下载的qt源码才知道怎么回事的。这里只说关键步骤。

对于我的需求,主要用到QTableWidget::currentCellChanged信号,目的是能根据用户选择决定是否继续,还是取消。经过研究qt源码,QTableWidget.cpp有这样一段:

void QTableWidgetPrivate::_q_emitCurrentItemChanged(const QModelIndex &current,const QModelIndex &previous)
{Q_Q(QTableWidget);QTableWidgetItem *currentItem = tableModel()->item(current);QTableWidgetItem *previousItem = tableModel()->item(previous);if (currentItem || previousItem)emit q->currentItemChanged(currentItem, previousItem);emit q->currentCellChanged(current.row(), current.column(), previous.row(), previous.column());
}

这样看着后面没干什么事,只是看到currentItemChanged比currentCellChanged要靠前触发,而且有先决条件。接着看,_q_emitCurrentItemChanged这个信号是怎么来的。

void QTableWidgetPrivate::setup()
{...// selection signalsQObject::connect(q->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),q, SLOT(_q_emitCurrentItemChanged(QModelIndex,QModelIndex)));QObject::connect(q->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),q, SIGNAL(itemSelectionChanged()));...
}

那个q->selectionModel()跟踪一下就知道,它是QAbstractItemView::selectionModel(),是个QItemSelectionModel。主要看它的currentChanged和selectionChanged这俩信号的途径。

在qitemselectionmodel.cpp中搜索currentChanged就看见原因了,确实是currentChanged发送以后,会有更新界面的代码,最后再发送selectionChanged。代码太多就不贴了。

但是还有QTableWidget::setCurrentCell,QAbstractItemView::setCurrentIndex,最终都是执行的QItemSelectionModel::setCurrentIndex。而在这里面,selectionChanged是先于currentChanged的。

当执行setCurrent时,CellChanged是最后执行的。(这点要稍后考虑,先看用户主动操作的情况。后面在“疑点”部分逐一说明。)

当用户操作界面时,selectionChanged才是最后执行的,如果要回滚界面,也要在这里。但有个很恶心的事情。看这个:

QObject::connect(q->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),  q, SIGNAL(itemSelectionChanged()));

连接时丢了两个很重要的参数,不知道qt为什么这样。(其实,qt也确实有类似的解决方案,我自己也不经意间用过,下文“疑点”会提到。)后来我想,也许提供一个没有参数的槽,更方便以后显式调用,因为不用刻意传参了,否则,如果在不容易获得入参值,而又想调用功能的情况下,就不方便了。

原因找到,解决就容易了。

方法:

我的QTableWidget自己包装了一个类,

先定义一个发往外界的查询信号:void sigRowChangeQuery(QEvent *event);。内含event指针,用于判断用户操作,很符合qt风格。这里注意,因为是界面交互,都在ui线程,所以默认是direct连接方式,所以可以接收到event的更改。

当然还有另外一个信号:void sigRowChanged(int iRow);,见名知意,通知外界行选发生。

写了槽on_currentCellChanged用于处理行选。其中:

    if (m_bIsCurrentCellChangeProtected || currentRow < 0 || currentColumn < 0)
    {
        return;
    }

    if (currentRow != previousRow)
    {
        QEvent event(QEvent::None);
        emit sigRowChangeQuery(&event);

        if (!event.isAccepted())//If the slot was canceled by the user.
        {
            m_iRow_Rollback = previousRow;
            m_iCol_Rollback = previousColumn;
            m_bIsSelectionRollback = true;
            return;
        }

        emit sigRowChanged(currentRow);
    }

用两个变量记住要回滚的位置。再写槽on_itemSelectionChanged处理界面回滚:

    if (m_bIsCurrentCellChangeProtected)
    {
        return;
    }

    if (m_bIsSelectionRollback)
    {
        m_bIsSelectionRollback = false;

        m_bIsCurrentCellChangeProtected = true;
        this->blockSignals(true);
        QTableWidget::setCurrentCell(m_iRow_Rollback, m_iCol_Rollback);
        m_bIsCurrentCellChangeProtected = false;
        this->blockSignals(false);
    }

这样就行了。

外面处理用户操作时,写槽onGridMain_RowChangeQuery(QEvent *event),根据判断再设置event的accept标志,这样的用法就顺畅多了。是不是跟closeEvent用法一样?就是要这种效果。

所以这样的做法可以延伸的其它类似的场景。

疑点1:

上文提到:

当执行setCurrent时(比如setCurrentCell,setCurrentItem等),CellChanged是最后执行的。因为最终都是调用的QItemSelectionModel::setCurrentIndex:

void QItemSelectionModel::setCurrentIndex(const QModelIndex &index, QItemSelectionModel::SelectionFlags command)
{Q_D(QItemSelectionModel);if (!d->model) {qWarning("QItemSelectionModel: Setting the current index when no model has been set will result in a no-op.");return;}if (index == d->currentIndex) {if (command != NoUpdate)select(index, command); // select itemreturn;}QPersistentModelIndex previous = d->currentIndex;d->currentIndex = index; // set current before emitting selection changed belowif (command != NoUpdate)select(d->currentIndex, command); // select itememit currentChanged(d->currentIndex, previous);if (d->currentIndex.row() != previous.row() ||d->currentIndex.parent() != previous.parent())emit currentRowChanged(d->currentIndex, previous);if (d->currentIndex.column() != previous.column() ||d->currentIndex.parent() != previous.parent())emit currentColumnChanged(d->currentIndex, previous);
}

所以,使用代码设置当前位置时,情况跟用户点击是不一样的。qt会先设置selection,再触发cellchanged。

当然setCurrentCell和setCurrentItem函数,还提供了一个重载,带一个参数QItemSelectionModel::SelectionFlags,用于指定要不要更改selection。所以,在必要的地方setCurrentCell时,指定不更改selection,之后再显式调用一下on_itemSelectionChanged,相当于强制让selection设置在cellchanged之后。而调用无参的on_itemSelectionChanged确实更方便,这就又扣题上文了。

问题1:乍一听是不是很有道理?其实还有个坑,setCurrent的时候如果指定了不更改selection,后期更新?不存在的!因为表格支持的模式很多,比如多选,一旦你自己控制,就要考虑十分周全,所以那个参数还是不要用,宁可之后必要的时候,在set一次selection。只不过界面上看起来是,选择状态先改过去了,等一撤销又回来了。视觉上不爽,以及效率不最高。但权衡利弊,值得。

问题2:on_itemSelectionChanged其实还是靠调用setCurrentCell来实现的selection状态变化。我没有用QTableView::setSelection函数。因为看过源码,内部的选择状态,是在一个叫selectionChanged的槽函数(“疑点2”中提到)内部实现的,而这个槽根本上还是靠QItemSelectionModel::selectionChanged这个信号触发的。而QTableView::setSelection是自己硬在界面层面计算rect实现的。这个就与内部联动脱节了,实在是不好操作,我还要考虑SelectionBehavior(选择模式:行,列,等)。所以不如让qt自带的功能实现更方便。

综合上述两点,你还觉得有自己控制selection的必要吗?反正我是认怂了,还是尽量用qt自带的功能实现。

注意:因为on_itemSelectionChanged里面调用了setCurrentCell,如果不加标记还会触发currentCellchanged,那就死循环了,所以要考虑周全。

疑点2:

上文提到:

QObject::connect(q->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),  q, SIGNAL(itemSelectionChanged()));

连接时丢了两个很重要的参数。且不提“疑点1”提到的方便调用问题,但其实qt有类似的解决方案。注意看,这个叫大壮的男人,点开了qt手册,他竟然发现了这么个玩意:

QTableWidget::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected)

但其实以前也重写过这个虚函数,这次的问题,因为一开始没想到selection,所以没往这看。

跟踪一下就知道,这个虚函数继承自QAbstractItemView>QTableview。而它的触发,看源码:

void QAbstractItemView::setSelectionModel(QItemSelectionModel *selectionModel)
{...if (d->selectionModel) {connect(d->selectionModel, SIGNAL(selectionChanged(QItemSelection,QItemSelection)),this, SLOT(selectionChanged(QItemSelection,QItemSelection)));connect(d->selectionModel, SIGNAL(currentChanged(QModelIndex,QModelIndex)),this, SLOT(currentChanged(QModelIndex,QModelIndex)));selectionChanged(d->selectionModel->selection(), oldSelection);currentChanged(d->selectionModel->currentIndex(), oldCurrentIndex);}
}

还是从d->selectionModel的selectionChanged信号过来的,而d->selectionModel是QItemSelectionModel,所以,来源还是QItemSelectionModel::selectionChanged这个信号。这就和上文的方法对上了。

但是,利用selectionChanged这个虚函数,会否能做个更“优”解呢?我想目前是没有必要了。先这样,想到再补充。

心得:

个人感觉,qt源码中关于cellchanged和selection的顺序,应该保持一致就好了。

本文完。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

了解树和学习二叉树

1.树 1.1 概念 树是一种 非线性 的数据结构&#xff0c;它是由 n &#xff08; n>0 &#xff09;个有限结点组成一个具有层次关系的集合。 把它叫做树是因为它看 起来像一棵倒挂的树&#xff0c;也就是说它是根朝上&#xff0c;而叶朝下的 。 注意&#xff1a;树形结构中…

使用 uiautomatorviewer 获取元素的定位信息

1. 使用 adb 连接设备&#xff08;真机或模拟器&#xff09; 连接夜神模拟器&#xff1a;adb connect 127.0.0.1:62001 连接MuMu模拟器&#xff1a;adb connect 127.0.0.1:7555 2. 打开 uiautomatorviewer 在 android-sdk --> tools 目录&#xff0c;找到 uiautomatorvie…

momentum2靶机

文章妙语 遇事不决&#xff0c;可问春风&#xff1b; 春风不语&#xff0c;遵循己心。 文章目录 文章妙语前言一、信息收集1.IP地址扫描2.端口扫描3.目录扫描 二&#xff0c;漏洞发现分析代码bp爆破1.生成字典2.生成恶意shell.php2.抓包 三&#xff0c;漏洞利用1.反弹shell 四…

第一次记录QPSK,BSPK,MPSK,QAM—MATLAB实现

最近有偶然的机会学习了一次QPSK防止以后忘记又得找资料&#xff0c;这里就详细的记录一下 基于 QPSK 的通信系统如图 1 所示&#xff0c;QPSK 调制是目前最常用的一种卫星数字和数 字集群信号调制方式&#xff0c;它具有较高的频谱利用率、较强的抗干扰性、在电路上实现也较为…

使用阿里云性能测试工具 JMeter 场景压测 RocketMQ 最佳实践

作者&#xff1a;森元 需求背景 新业务上线前&#xff0c;我们通常需要对系统的不同中间件进行压测&#xff0c;找到当前配置下中间件承受流量的上限&#xff0c;从而确定上游链路的限流规则&#xff0c;保护系统不因突发流量而崩溃。阿里云 PTS 的 JMeter 压测可以支持用户上…

用户管理第2节课-idea 2023.2 后端一删除表,从零开始---【本人】

一、清空model文件夹下&#xff0c;所有文件 1.1.1效果如下&#xff1a; 1.1代码内容 package com.daisy.usercenter.model;import lombok.Data;Data public class User {private Long id;private String name;private Integer age;private String email; }二、清空mapper文件…

单调栈分类、封装和总结

作者推荐 map|动态规划|单调栈|LeetCode975:奇偶跳 通过枚举最小&#xff08;最大&#xff09;值不重复、不遗漏枚举所有子数组 C算法&#xff1a;美丽塔O(n)解法单调栈左右寻找第一个小于maxHeight[i]的left,right&#xff0c;[left,right]直接的高度都是maxHeight[i] 可以…

[kubernetes]控制平面ETCD

什么是ETCD CoreOS基于Raft开发的分布式key-value存储&#xff0c;可用于服务发现、共享配置以及一致性保障&#xff08;如数据库选主、分布式锁等&#xff09;etcd像是专门为集群环境的服务发现和注册而设计&#xff0c;它提供了数据TTL失效、数据改变监视、多值、目录监听、…

docker安装ES:7.8和Kibana:7.8

本文适用于centos7,快速入手练习es语法 前置&#xff1a;安装docker教程docker、docker-component安装-CSDN博客 1.安装es 9200为启动端口&#xff0c;9300为集群端口 docker pull elasticsearch:7.8.0mkdir -p /mydata/elasticsearch/pluginsmkdir -p /mydata/elasticsear…

debian10安装配置vim+gtags

sudo apt install global gtags --version gtags //生成gtag gtags-cscope //查看gtags gtags与leaderf配合使用 参考: 【VIM】【LeaderF】【Gtags】打造全定制化的IDE开发环境&#xff01; - 知乎

Vue CLI 设置 publicPath:打包后的应用可部署在任意路径

前言 领导要重新部署多个应用环境&#xff0c;且不受路径层级影响。 于是找到了 Vue CLI 配置 publicpath 配置说明 下图所示&#xff1a; / &#xff1a;默认值&#xff0c;应用部署在根路径上&#xff1b;./&#xff1a;注意前面加了一个点&#xff0c;应用可部署在任意路…

【Earth Engine】协同Sentinel-1/2使用随机森林回归实现高分辨率相对财富(贫困)制图

目录 1 简介与摘要2 思路3 效果预览4 代码思路5 完整代码6 后记 1 简介与摘要 最近在做一些课题&#xff0c;需要使用Sentinel-1/2进行机器学习制图。 然后想着总结一下相关数据和方法&#xff0c;就花半小时写了个代码。 然后再花半小时写下这篇博客记录一下。 因为基于多次拍…

【Python小游戏】某程序员自制《苹果大赛》,赶紧来抢~“免费的平安夜苹果,你说是不是最甜的鸭?”(附源码)

导语 很久不见&#xff0c;我是木木子鸭~2023发生了太多事情啦&#xff0c;我将重新启航&#xff0c;开启新的一页。 希望不管是文章还是各种小程序都能够帮到大家&#xff0c;大家也要继续支持我哦~我将继续努力更新&#xff01; ——祝你祝我 在这个冬天—— 爱与好运同在 …

车云TCP链路偶现链接失联问题排查

一、问题分析 1.1 车云tcp长连接分析排查 在15:37:32.039上线&#xff0c; 在 16:07:26.527下线&#xff0c;车云长连接通道稳定&#xff0c;且该期间心跳数据正常。 1.2 云向驾仓推送数据分析 在15:37:42 进行车辆接管后&#xff0c;该车辆下线&#xff0c;且无法在上线&am…

AtomHub 开源容器镜像中心开放公测,国内服务稳定下载

由开放原子开源基金会主导&#xff0c;华为、浪潮、DaoCloud、谐云、青云、飓风引擎以及 OpenSDV 开源联盟、openEuler 社区、OpenCloudOS 社区等成员单位共同发起建设的 AtomHub 可信镜像中心正式开放公测。AtomHub 秉承共建、共治、共享的理念&#xff0c;旨在为开源组织和开…

Java中使用JTS实现WKB数据写入、转换字符串、读取

场景 Java中使用JTS实现WKT字符串读取转换线、查找LineString的list中距离最近的线、LineString做缓冲区扩展并计算点在缓冲区内的方位角&#xff1a; Java中使用JTS实现WKT字符串读取转换线、查找LineString的list中距离最近的线、LineString做缓冲区扩展并计算点在缓冲区内…

户用光伏设计有哪些特点?

随着科技的发展和人们对可再生能源的追求&#xff0c;户用光伏设计已经逐渐成为一种新型的能源解决方案。它不仅有助于降低能源成本&#xff0c;而且对环境保护有着积极的影响。那么&#xff0c;户用光伏设计究竟有哪些特点呢&#xff1f; 首先&#xff0c;户用光伏设计的核心在…

c++动态内存与智能指针

前言 静态内存&#xff1a;用于保存局部静态变量、类内的静态数据成员以及全局变量栈&#xff1a;用于保存函数内部的非static变量堆&#xff1a;存储动态分配的对象&#xff08;程序运行时分配的对象&#xff09; 静态内存和栈内存的对象由编译器自动创建和销毁 而堆区的动态…

分布式搜索elasticsearch概念

什么是elasticsearch&#xff1f; elasticsearch是一款非常强大的开源搜索引擎&#xff0c;可以帮助我们从海量数据中快速找到需要的内容 目录 elasticsearch的场景 elasticsearch的发展 Lucene篇 Elasticsearch篇 elasticsearch的安装 elasticsearch的场景 elasticsear…

显示器屏幕oled的性能、使用场景、维护

OLED显示器屏幕具有许多独特的性能和使用场景&#xff0c;以下是关于OLED显示器屏幕的性能、使用场景和维护的详细介绍&#xff1a; 一、性能 色彩鲜艳&#xff1a;OLED显示器屏幕能够呈现出更加鲜艳的色彩&#xff0c;色彩饱和度高&#xff0c;色彩还原性好&#xff0c;可以给…