【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处理界面回滚:

    int iRow = -1, iCol = -1;

    if (m_bIsSelectionRollback)
    {
        m_bIsSelectionRollback = false;
        iRow = m_iRow_Rollback;
        iCol = m_iCol_Rollback;
    }
    else
    {
        iRow = this->currentRow();
        iCol = this->currentColumn();
    }

    m_bIsCurrentCellChangeProtected = true;
    this->blockSignals(true);
    QTableWidget::setCurrentCell(iRow, iCol);
    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确实更方便,这就又扣题上文了。

说明:on_itemSelectionChanged其实还是靠调用setCurrentCell来实现的selection状态变化。我没有用QTableView::setSelection函数。因为看过源码,内部的选择状态,是在一个叫selectionChanged的槽函数(“疑点2”中提到)内部实现的,而这个槽根本上还是靠QItemSelectionModel::selectionChanged这个信号触发的。而QTableView::setSelection是自己硬在界面层面计算rect实现的。这个就与内部联动脱节了,实在是不好操作,我还要考虑SelectionBehavior(选择模式:行,列,等)。所以不如让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.mzph.cn/news/237155.shtml

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

相关文章

【Chrome】ERR_SSL_PROTOCOL_ERROR问题

文章目录 前言一、下载二、使用步骤总结 前言 Edge升级最新版后&#xff0c;有的https访问不了&#xff0c;报如下错误 发现新版Chrome以及Chromium内核访问nginx ssl时报错&#xff0c;顺着这个思路接着查看到大佬的结论&#xff1a;服务器nginx使用的openssl版本过低&#…

C++入门【12-C++ 数组】

C 数组 C 支持数组数据结构&#xff0c;它可以存储一个固定大小的相同类型元素的顺序集合。数组是用来存储一系列数据&#xff0c;但它往往被认为是一系列相同类型的变量。 数组的声明并不是声明一个个单独的变量&#xff0c;比如 number0、number1、...、number99&#xff0…

控制理论simulink+matlab

控制理论下的simulink和matlab使用 根轨迹LQR控制器简单使用状态观测器设计 根轨迹 z [-1]; %开环传递函数的零点 p [0 -2 -3 -4]; %开环传递函数的系统极点 k 1; %开环传递函数的系数&#xff0c;反映在比例上 g zpk(z,p,k); %生成开环传递函数%生成的传递函数如…

社交网络分析(汇总)

这里写自定义目录标题 写在最前面社交网络分析系列文章汇总目录 提纲问题一、社交网络相关定义和概念提纲问题1. 社交网络、社交网络分析&#xff1b;2. 六度分隔理论、贝肯数、顿巴数&#xff1b;3. 网络中的数学方法&#xff1a;马尔科夫过程和马尔科夫链、平均场理论、自组织…

使用JDBC对数据库进行简单操作

用Connection获得了数据库连接对象后&#xff0c;可以用Statement类型进行数据库操作。 在Statement对象中&#xff0c;有三种&#xff0c;分别是Statement&#xff0c;PrepareStatement&#xff0c;CallableStatement。 这三个的区别在于&#xff1a; Statement 用于执行不…

KubePi JWT 默认密钥权限绕过漏洞复现(CVE-2023-22463)

0x01 产品简介 KubePi 是一款简单易用的开源 Kubernetes 可视化管理面板。 0x02 漏洞概述 KubePi 存在权限绕过漏洞,攻击者可通过默认 JWT 密钥获取管理员权限控制整个平台,使用管理员权限操作核心的功能。 0x03 影响范围 KubePi <= 1.6.2 0x04 复现环境 FOFA: ti…

【Jenkins】远程API接口:Java 包装接口使用示例

jenkins-rest 库是一个面向对象的 Java 项目&#xff0c;它通过编程方式提供对 Jenkins REST API 的访问&#xff0c;以访问 Jenkins 提供的一些远程 API。它使用 jclouds 工具包构建&#xff0c;可以轻松扩展以支持更多 REST 端点。其功能集不断发展&#xff0c;用户可以通过拉…

怎么压缩过大的GIF图片?几个步骤轻松搞定!

GIF图片由于其图片格式&#xff0c;本身就会很大&#xff0c;但是微信QQ还有一些其他的社交平台对上传的表情包是有限制的&#xff0c;这个时候就需要借助一些图片处理工具对GIF进行压缩。 下面就向大家介绍三种好用的方法并展示具体的操作步骤。 一、使用嗨格式压缩大师进行压…

RouterSrv-路由功能

2023年全国网络系统管理赛项真题 模块B-Windows解析 题目 安装Remote Access服务开启路由转发,为当前实验环境提供路由功能。启用网络地址转换功能,实现内部客户端访问互联网资源。答题步骤 安装Remote Access服务开启路由转发,为当前实验环境提供路由功能。 配置网卡 加…

Day67力扣打卡

打卡记录 美丽塔 II&#xff08;前缀和 单调栈&#xff09; 链接 class Solution:def maximumSumOfHeights(self, maxHeights: List[int]) -> int:n len(maxHeights)stack collections.deque()pre, suf [0] * n, [0] * nfor i in range(n):while stack and maxHeights…

eNSP拓扑建立:RIP静态路由

实验名称&#xff1a; RIP动态路由协议 实验目的&#xff1a; 1、掌握RIPv1的配置方法 2、查看RIP路由分析过程 3、掌握测试RIP网络连通性的方法 步骤一:建立拓扑图 步骤二&#xff1a;配置PC终端的ip、子网掩码、网关。 步骤三&#xff1a;配置路由器&#xff0c;如图所示 步…

【K8s】3# 使用kuboard管理K8s集群(NFS存储安装)

文章目录 1.NFS是什么2.配置NFS服务器2.1.执行以下命令安装 nfs 服务器所需的软件包2.2.执行命令 vim /etc/exports&#xff0c;创建 exports 文件&#xff0c;文件内容如下2.3.执行以下命令&#xff0c;启动 nfs 服务2.4.检查配置是否生效 3.在客户端测试NFS3.1.执行以下命令安…

easyexcle处理复杂动态单元格合并问题,合并动态行列

GetMapping("getAddDelSummaryExport") ApiOperation("新增删除比例报表--导出") ApiImplicitParams({ApiImplicitParam(name "season", value "季节", paramType "query", dataType "String"),ApiImplicitPa…

Electron Vite打包后,部分图标未显示的解决方案

背景 这个问题&#xff0c;弄了一晚上&#xff0c;头都大了&#xff0c;找了一堆博客也没解决。主要参考这个&#xff1a;https://blog.csdn.net/m0_73845616/article/details/129741099。 下面讲一下我的解决方案。 解决方案 上面链接里的方法&#xff0c;我采用第二、三个都…

C# Onnx Yolov8 Detect 物体检测 多张图片同时推理

目录 效果 模型信息 项目 代码 下载 C# Onnx Yolov8 Detect 物体检测 多张图片同时推理 效果 模型信息 Model Properties ------------------------- date&#xff1a;2023-12-18T11:47:29.332397 description&#xff1a;Ultralytics YOLOv8n-detect model trained on …

Istio 社区周报(第一期):2023.12.11 - 12.17

欢迎来到 Istio 社区周报 Istio 社区朋友们&#xff0c;你们好&#xff01; 我很高兴呈现第一期 Istio 社区周报。作为 Istio 社区的一员&#xff0c;每周我将为您带来 Istio 的最新发展、有见地的社区讨论、专业提示和重要安全新闻内容。 祝你阅读愉快&#xff0c;并在下一期中…

第二十二章 : Spring Boot 集成定时任务(一)

第二十二章 &#xff1a; Spring Boot 集成定时任务&#xff08;一&#xff09; 前言 本章知识点&#xff1a; 介绍使用Spring Boot内置的Scheduled注解来实现定时任务-单线程和多线程&#xff1b;以及介绍Quartz定时任务调度框架&#xff1a;简单定时调度器&#xff08;Simp…

ubuntu 18.04 共享屏幕

用于windows远程ubuntu 1. sudo apt install xrdp 2. 配置 sudo vim /etc/xrdp/startwm.sh 把最下面的test和exec两行注释掉&#xff0c;添加一行 gnome-session 3.安装dconf-editor : sudo apt-get install dconf-editor 关闭require encrytion org->gnome->desktop…

TransXNet实战:使用TransXNet实现图像分类任务(一)

文章目录 摘要安装包安装timm 数据增强Cutout和MixupEMA项目结构计算mean和std生成数据集 摘要 论文提出了一种名为D-Mixer的轻量级双动态TokenMixer&#xff0c;旨在解决传统卷积的静态性质导致的表示差异和特征融合问题。D-Mixer通过应用高效的全局注意力和输入依赖的深度卷…

Py之tensorflow-addons:tensorflow-addons的简介、安装、使用方法之详细攻略

Py之tensorflow-addons&#xff1a;tensorflow-addons的简介、安装、使用方法之详细攻略 目录 tensorflow-addons的简介 tensorflow-addons的安装 tensorflow-addons的使用方法 1、使用 TensorFlow Addons 中的功能&#xff1a; tensorflow-addons的简介 TensorFlow Addon…