上海海宏建设集团网站,做淘宝类网站的步骤,网站建站的技术解决方案,ceo是什么职位的简称背景#xff1a;
【QT表格-1】QStandardItem的堆内存释放需要单独delete#xff0c;还是随QStandardItemModel的remove或clear自动销毁#xff1f;-CSDN博客
【QT表格-2】QTableWidget单元格结束编辑操作endEditting_qtablewidget 单元格编辑事件-CSDN博客
【QT表格-3】Q…背景
【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::setCurrentCellQAbstractItemView::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时比如setCurrentCellsetCurrentItem等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所以没往这看。
跟踪一下就知道这个虚函数继承自QAbstractItemViewQTableview。而它的触发看源码
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的顺序应该保持一致就好了。
本文完。