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

news2024/12/25 16:03:42

背景:

【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 signals
    QObject::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 item
        return;
    }
    QPersistentModelIndex previous = d->currentIndex;
    d->currentIndex = index; // set current before emitting selection changed below
    if (command != NoUpdate)
        select(d->currentIndex, command); // select item
    emit 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.coloradmin.cn/o/1327084.html

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

相关文章

做一个类似东郊到家系统都有哪些功能特点?

随着科技的发展&#xff0c;人们的生活方式也在不断变化&#xff0c;在快节奏的生活中&#xff0c;身心疲惫的人们需要一种快速有效的方式来缓解压力。同城预约上门按摩小程序就是为满足这种需求而诞生的。用户可以通过小程序&#xff0c;方便地预约按摩服务&#xff0c;无需浪…

力扣日记12.21【二叉树篇】98. 验证二叉搜索树

力扣日记&#xff1a;【二叉树篇】98. 验证二叉搜索树 日期&#xff1a;2023.12.21 参考&#xff1a;代码随想录、力扣 98. 验证二叉搜索树 题目描述 难度&#xff1a;中等 给你一个二叉树的根节点 root &#xff0c;判断其是否是一个有效的二叉搜索树。 有效 二叉搜索树定义…

24_28-Golang函数详解

**Golang **函数详解 主讲教师&#xff1a;&#xff08;大地&#xff09; 合作网站&#xff1a;www.itying.com** **&#xff08;IT 营&#xff09; 我的专栏&#xff1a;https://www.itying.com/category-79-b0.html 1、函数定义 :::info 函数是组织好的、可重复使用的、用…

云渲染如何使用?其实很简单,只需3步就搞定了!

很多人第一次接触云渲染&#xff0c;对云渲染不了解&#xff0c;不知道云渲染怎么用&#xff0c;其实很简单&#xff0c;只需要3步就搞定了。 第一步&#xff1a;下载并安装客户端 到炫云官网下载客户端&#xff0c;下载完直接点击安装就可以&#xff0c;炫云的效果图渲染和动…

【代码规范】统一参数校验、结果返回

统一参数校验&#xff1a; 在编写Controller层的代码时&#xff0c;时常会有这种情况出现&#xff1a; RestController RequestMapping("/user") public class UserController {Resourceprivate UserService userService;PostMapping("register")public S…

【基础知识】大数据组件YARN简述

YARN是一个分布式的资源管理系统。 YARN是Hadoop系统的核心组件&#xff0c;主要功能包括负责在Hadoop集群中的资源管理&#xff0c;负责对作业进行调度运行以及监控。 ResourceManager 负责集群的资源管理与调度&#xff0c;为运行在YARN上的各种类型作业分配资源。 非HA集…

设计模式03结构型模式

结构型模式 参考网课:黑马程序员Java设计模式详解 博客笔记 https://zgtsky.top/ 结构型模式描述如何将类或对象按某种布局组成更大的结构。它分为类结构型模式和对象结构型模式&#xff0c;前者采用继承机制来组织接口和类&#xff0c;后者釆用组合或聚合来组合对象。 由于…

通过讯飞 API 接口用 Vue 实现实时语音转写

通过讯飞 API 接口用 Vue 实现实时语音转写 项目地址 前言 本项目中实时语音能够转写的最大时间为 60 s&#xff0c; 这个数据也是由 API 提供方给限制掉的 为什么我会需要这个点击按钮以后能够实现实时语音的转写呢&#xff0c;因为被课程所迫&#xff0c;选了这个方向就必…

linux 性能优化-内存优化

CPU 管理一样&#xff0c;内存管理也是操作系统最核心的功能之一。内存主要用来存储系统和应 用程序的指令、数据、缓存等。 1.内存原理 1.1.内存映射 1.1.1.日常生活常说的内存是什么? 我的笔记本电脑内存就是 8GB 的这个内存其实是物理内存物理内存也称为主存&#xff0…

Java最全面试题专题---5、Spring面试题(1)

Spring概述&#xff08;10&#xff09; 什么是spring? Spring是一个轻量级Java开发框架&#xff0c;最早有Rod Johnson创建&#xff0c;目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题。它是一个分层的JavaSE/JavaEE full-stack&#xff08;一站式&#xff…

联合体(C语言)

小伙伴们又来学习知识啦~&#xff0c;今天我要给大家介绍一下联合体的使用&#xff0c;话不多说&#xff0c;我们开始今天的正题吧&#xff01; 联合体的介绍 C语言的联合体&#xff08;union&#xff09;是一种特殊的数据类型&#xff0c;它可以在同一内存空间中存储不同的数…

龙迅LT86102UXE HDMI一分二HDMI,支持音频剥离,支持4K60HZ

描述&#xff1a; 龙迅 LT86102UXE HDMI2.0 分路器具有符合 HDMI2.0/1.4 规范的 1&#xff1a;2 分路器、最大 6Gbps 高速数据速率、自适应均衡 RX 输入和预加重 TX 输出&#xff08;用于支持长电缆应用&#xff09;、内部 TX 通道交换以实现灵活的 PCB 布线。 LT86102UXE HDM…

机器学习之线性回归(Linear Regression)附代码

概念 线性回归(Linear Regression)是机器学习中的一种基本的监督学习算法,用于建立输入变量(特征)与输出变量(目标)之间的线性关系。它假设输入变量与输出变量之间存在线性关系,并试图找到最佳拟合线来描述这种关系。 在简单线性回归中,只涉及两个变量:一个是自变量…

Ubuntu 常用命令之 passwd 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 在Ubuntu系统中&#xff0c;passwd命令用于更改用户的密码。系统管理员可以使用此命令更改任何用户的密码&#xff0c;而普通用户只能更改自己的密码。 passwd命令的参数如下 -l, --lock&#xff1a;锁定密码&#xff0c;使账户…

Apache+PHP环境配置 手动配置

准备工作&#xff0c;在G盘新建一个WAMP目录 1.获取Apache 打开下载地址Apache VS17 binaries and modules download&#xff0c;下载 httpd-2.4.58-win64-VS17.zip 将下载好的httpd-2.4.58-win64-VS17.zip拷贝到G:\WAMP目录下并解压到当前目录&#xff0c;得到Apache24目录 …

石墨匣钵,预计到2025年将达到3.973亿美元

石墨匣钵是陶瓷和耐火材料工业用于烧制和烧结各种材料的高温容器。在陶瓷产品需求增加和耐火材料行业增长的推动下&#xff0c;全球石墨匣钵市场在过去几年出现了显着增长。2020 年全球石墨匣钵市场价值为 3.039 亿美元&#xff0c;预计到 2025 年将达到 3.973 亿美元&#xff…

找不到x3daudio1_7.dll文件的解决方法分享

今天我想和大家分享的是关于“找不到X3DAudio1_7.dll无法继续执行代码问题”的解决方法&#xff0c;以及X3DAudio1_7.dll丢失是什么意思&#xff0c;它的作用是什么&#xff0c;以及丢失对计算机有什么影响。 首先&#xff0c;我们来解释一下X3DAudio1_7.dll是什么。X3DAudio1…

Python Hook钩子函数详解

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Python编程中&#xff0c;Hook钩子函数是一种重要的编程机制&#xff0c;允许程序在运行时的特定点执行自定义代码&#xff0c;以修改或扩展程序的行为。本文将深入介绍Python中Hook钩子函数的基本概念、使用场…

Axure RP 8 for Mac/win中文版:打造完美交互式原型设计体验

Axure RP 8&#xff0c;一款引领潮流的交互式原型设计工具&#xff0c;为设计师提供了无限的可能性&#xff0c;让他们能够创造出逼真的原型&#xff0c;从而更好地展示和测试他们的设计。 Axure RP 8拥有丰富的功能和工具&#xff0c;让设计师可以轻松地创建出复杂的交互式原…

程序员的50大JVM面试问题及答案

文章目录 1.JDK、JRE、JVM关系&#xff1f;2.启动程序如何查看加载了哪些类&#xff0c;以及加载顺序&#xff1f;3. class字节码文件10个主要组成部分?4.画一下jvm内存结构图&#xff1f;5.程序计数器6.Java虚拟机栈7.本地方法栈8.Java堆9.方法区10.运行时常量池&#xff1f;…