处理QMenu的triggered信号时遇到的一个问题
最近,在一个Qt程序中使用QMenu类时,遇到了一个小问题,特记录下。
首先,我模仿一下问题出现的场景:
假设我在做一个高大上的XX管理系统,比如说:学生信息管理系统。在这个系统中,学生的各项信息(比如:姓名、性别、年龄、班级、总分)使用数据库来存储。为了便于老师操作学生数据记录(比如:添加、修改、删除),我使用了一个QTableWidget(嗯,如果在MFC中的话,我会使用CListCtrl/CMFCListCtrl)来显示数据库中的所有学生记录。这个QTableWidget有多列,每列对应数据库中的一项(列)信息。
现在,我想给这个QTableWidget的header view上添加一个右键快捷菜单,也就是所谓的:Context menu。通过这个Context Menu,我们可以选择让QTableWidget中哪些列显示出来,哪些列不显示。
类似上面的这种需求很普遍。比如,Win 7系统的资源管理器就提供了这种功能,一图以蔽之:

怎样在Qt中为一个窗体部件上实现context menu?我找到了一些资料:
http://www.cnblogs.com/stevenpan/archive/2013/05/29/3105419.html
http://www.stackoverflow.com/questions/9187538/qt-how-to-add-a-list-of-qactions-to-qmenu-and-handle-them-with-a-single-slot
http://www.setnode.com/blog/right-click-context-menus-with-qt/
http://wenku.baidu.com/view/ea3cec4e90c69ec3d5bb75c9.html
我的测试代码:
主函数:
// main.cpp
#include "mainwindow.h"
#include <QApplication> int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show(); return a.exec();
}
MainWindow的实现:
头文件:
// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H #include <QtWidgets/QMainWindow> class QAction;
class QMenu;
class QTableWidget; class MainWindow : public QMainWindow
{
Q_OBJECT public:
MainWindow(QWidget *parent = );
~MainWindow(); private slots:
void onShowOrHideColumn(QAction *action); private:
QTableWidget *stuInfoWidget; QMenu *mainMenu;
}; #endif // MAINWINDOW_H
实现文件:
// mainwindow.cpp
#include "mainwindow.h" #include <QtCore/QStringList>
#include <QtWidgets/QAction>
#include <QtWidgets/QHeaderView>
#include <QtWidgets/QMenu>
#include <QtWidgets/QMenuBar>
#include <QtWidgets/QTableWidget>
#include <QtWidgets/QTableWidgetItem> MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
QStringList columnNames;
columnNames << tr("Name") << tr("Sex") << tr("Age"); // 创建一个QTableWidget,共三列,分别显示:Name(姓名)、Sex(性别)、Age(年龄)
stuInfoWidget = new QTableWidget;
stuInfoWidget->setColumnCount(columnNames.size());
for (int i = ; i < columnNames.size(); ++i) {
QTableWidgetItem *headerItem = new QTableWidgetItem(columnNames[i]);
stuInfoWidget->setHorizontalHeaderItem(i, headerItem);
} // 创建一个菜单。通过这个菜单,可以选择显示/隐藏指定列。
mainMenu = menuBar()->addMenu(tr("Show or hide columns"));
for (int i = ; i < columnNames.size(); ++i) {
QAction *action = new QAction(columnNames[i], this);
// 设定菜单项是可勾选的。
action->setCheckable(true);
// 设定菜单项初始状态是已被勾选的。
action->setChecked(true);
// 将列序号设定为菜单项的data,这样在槽函数onShowOrHideColumn中,
// 可以通过调用QAction::data方法来获知要显示/隐藏的列的序号。
action->setData(i);
mainMenu->addAction(action);
}
// 将mainMenu的triggered(QAction *)信号连接到自定义槽函数
// onShowOrHideColumn(QAction *action)上。这样,当用户触发mainMenu
// 中某一菜单项时,onShowOrHideColumn(QAction *)会被自动调用。
// QAction *类型的参数action指向被触发的菜单项。
connect(mainMenu, SIGNAL(triggered(QAction *)),
this, SLOT(onShowOrHideColumn(QAction *))); // 给QTableWidget的header view添加context menu的一种方法。
QHeaderView *headerView = stuInfoWidget->horizontalHeader();
headerView->setContextMenuPolicy(Qt::ActionsContextMenu);
headerView->addActions(mainMenu->actions()); setCentralWidget(stuInfoWidget);
} MainWindow::~MainWindow()
{
} void MainWindow::onShowOrHideColumn(QAction *action)
{
// 稍后会添加该函数的实现代码。
}
Qt通过“信号-槽”机制来处理消息。“信号”可以视为Windows中的“消息”,而“槽”则可拿MFC/SDK中的消息映射函数/消息回调函数来类比。当然,Qt中的“信号-槽”机制不仅仅局限于可以接收消息的窗体部件。如果想深入了解Qt的“信号-槽”机制的实现方式,可以看看这篇博文:
http://www.woboq.com/blog/how-qt-signals-slots-work.html
接下来,该让onShowOrHideColumn做些什么了。起初,我的想法是,在槽函数中,通过action的isChecked方法来判断这个action是否已经checked。如果是,那么显示这个action所管理的列,然后调用action->setChecked(false)来取消这个action的checked状态;如果不是,则隐藏相应列,并setChecked(true)。具体代码如下:
void MainWindow::onShowOrHideColumn(QAction *action)
{
// 获取当前的checked状态。
bool isChecked = action->isChecked();
// 在构造函数中,我们创建QAction对象的时候,通过setData把
// 这个QAction的user data设置为其管理的列的序号。
// 这里,通过data方法取出这个列编号,然后调用setColumnHidden来显示/隐藏该列。
stuInfoWidget->setColumnHidden(action->data().toInt(),
isChecked);
// 设置新的checked状态。
action->setChecked(!isChecked);
}
乍一看,这段代码内容充实,主题明确,非常感人。但是,它无法满足我们所需的效果。如果您实践一下的话,会发现菜单项的checked属性始终是true,而且不管您怎么点击菜单项,都无法更改QTableWidget某一列的显示/隐藏状态。
由于在Qt文档中以及Google上都没找到有用的线索,所以我在QAction这个类的源程序文件中凡是涉及修改QAction的checked属性的代码行上都加了断点,然后进行调试跟踪(实在是一种笨方法,不过好在QAction的代码执行逻辑并不那么“晦涩”)。我发现,在一个action被触发后,QAction::activate方法会先被调用,然后才是onShowOrHideColumn这个槽函数。下面是一段摘自QAction源文件中的代码:
void QAction::activate(ActionEvent event)
{
Q_D(QAction);
if(event == Trigger) {
QPointer<QObject> guard = this;
if(d->checkable) {
// the checked action of an exclusive group cannot be unchecked
if (d->checked && (d->group && d->group->isExclusive()
&& d->group->checkedAction() == this)) {
if (!guard.isNull())
emit triggered(true);
return;
}
setChecked(!d->checked);
}
if (!guard.isNull())
emit triggered(d->checked);
} else if(event == Hover) {
emit hovered();
}
}
注意该方法中的这一句代码:
setChecked(!d->checked);
可见,一个checkable的QAction对象被触发后,其checked状态会在QAction::activate被更新。所以,我们在QMenu::triggered(QAction *action)对应的槽函数中通过action调用isChecked将得到更新后的checked状态,而非当前的状态。
如果在Qt文档中仔细查找的话,还是可以得出这样的结论的:
void QAction::activate(ActionEvent event)
Sends the relevant signals for ActionEvent event.
Action based widgets use this API to cause the QAction to emit signals as well as emitting their own.enum QAction::ActionEvent
This enum type is used when calling QAction::activate()
QAction::Trigger
this will cause the QAction::triggered() signal to be emitted.void QAction::toggle() [slot]
This is a convenience function for the checked property. Connect to it to change the checked state to its opposite state.
了解了这些后,重写这个槽函数为:
void MainWindow::onShowOrHideColumn(QAction *action)
{
// 对于一个checkable的QAction对象,如果最初其checked属性:
// 1) == true, 那么在其被触发后,其checked属性将会在
// QAction::activate方法中被更改为false。因此,随后我们
// 在这个槽函数中调用isChecked时将得到false。
// 2) 和情况1)相反。
// 而且,我们无需自己调用setChecked方法来更新checked状态,因为
// QAction::activate已经帮我们做了这步工作。
stuInfoWidget->setColumnHidden(action->data().toInt(),
!action->isChecked());
}
Qt的这种做法的确让我们少写了几行代码,不过如果不知道这一点的话,会让如我这样的新手感到困惑的。
本文的示例程序:
http://files.cnblogs.com/myd7349/StudentInfo_v1.zip
PS1:在使用Windows SDK编写Win32 GUI程序的时候,我们往往需要使用诸如CheckMenuItem、ModifyMenu、SetMenuItemInfo等这样的API来显式check或uncheck一个菜单项。
PS2:在MFC中,要更改一个菜单项的checked状态,需要为菜单项添加ON_UPDATE_COMMAND_UI消息映射,然后在消息映射函数中通过CCmdUI *类型的参数的setCheck方法来实现。
PS3:正是因为我在MFC & SDK中的编程经验,才使我写出了上面那个错误版本的槽函数。
PS4:第一次使用博客园的博客,还不会排版。见谅啊。^_^
处理QMenu的triggered信号时遇到的一个问题的更多相关文章
- 利用过采样技术提高ADC测量微弱信号时的分辨率
1. 引言 随着科学技术的发展,人们对宏观和微观世界逐步了解,越来越多领域(物理学.化学.天文学.军事雷达.地震学.生物医学等)的微弱信号需要被检测,例如:弱磁.弱光.微震动.小位移.心电.脑电等[1 ...
- QTimeLine 控制动画(一步一步的往前变化,并在每次变化时都会发出一个frameChanged信号)
QTimeLine顾名思义表示一条时间线,即一个时间序列,该时间序列会按我们实现定义好的间隔一步一步的往前变化,并在每次变化时都会发出一个frameChanged()信号.所以,我们通常会使用该类来驱 ...
- 信号和槽有一个非常精炼的C++实现,作者是Sarah Thompson,该实现只有一个头文件sigslot.h,跨平台且线程安全
关于信号和槽有一个非常精炼的C++实现,作者是Sarah Thompson,该实现只有一个头文件sigslot.h,跨平台且线程安全. 源码在:http://sigslot.cvs.sourcefor ...
- ios 仿新浪微博 UINavigationController 向左滑动时显示上一个控制器的View.
仿新浪微博 UINavigationController 向左滑动时显示上一个控制器的View. 实现原理,UINavigationController 的 self.view显示时把当前显示的vie ...
- L2TP连接尝试失败,因为安全层在初始化与远程计算机的协商时遇到了一个处理错误(转)
L2TP连接尝试失败,因为安全层在初始化与远程计算机的协商时遇到了一个处理错误 错误描述:“ L2TP连接尝试失败,因为安全层在初始化与远程计算机的协商时遇到了一个处理错误” 只有这个没有错误码. ...
- PHP进行数据库操作时遇到的一个问题
PHP进行数据库操作时遇到的一个问题 昨天在进行数据库操作时,遇到了一个问题(用的是 wampserver 环境): <?php $link = @mysqli_connect('localho ...
- 启动多个eclipse 时,因为一个另一个启动报错,
启动多个eclipse 时,因为一个另一个启动报错, 原因: 可能是 有一个 eclipse 中 的 tomcat 配置出错:preference中 tomcat 配置 context dec ...
- json 数据类型,后台在组数据时,错一个标点符号,前端都解析不出来。
json 数据类型,后台在组数据时,错一个标点符号,前端都解析不出来.
- 在Hive中执行DDL之类的SQL语句时遇到的一个问题
在Hive中执行DDL之类的SQL语句时遇到的一个问题 作者:天齐 遇到的问题如下: hive> create table ehr_base(id string); FAILED: Execut ...
随机推荐
- Jetson TX1 安装Kinect驱动
1.添加Universe源 $ sudo apt-add-repository universe $ sudo apt-get update 2.安装编译工具和依赖项 $ sudo apt-get i ...
- 20165227 实验二《Java面向对象程序设计》实验报告
2017-2018-4 20165227实验二<Java面向对象程序设计>实验报告 实验内容 初步掌握单元测试和TDD 理解并掌握面向对象三要素:封装.继承.多态 初步掌握UML建模 熟悉 ...
- Hibernate5笔记7--Hibernate缓存机制
Hibernate缓存机制: 缓存范围: 应用程序中根据缓存的范围,可以将缓存分为三类: (1)事务范围缓存(单Session,即一级缓存) 事务范围的缓存只能被当前事务访问,每个事务都有各自的缓 ...
- js原生选择class DOM元素
document.querySelector(".demo"); querySelector() 方法返回匹配指定选择器的第一个元素.如果需要返回所有的元素,使用 querySel ...
- 国内能用的NTP服务器及和标准源的偏差值
中国境内可以使用的NTP服务器的IP地址,和泰福特服务器的时间偏差值,泰福特时钟服务器实时连接天线,测试前已经连接天线超过72小时 time-a.nist.gov 129.6.15.28 NIST, ...
- 洛谷P2194HXY烧情侣
传送门啦 这个题可以说是tarjan强连通分量的裸题,但需要维护每个强连通分量的最小值,所以做法就很明确了. 我们先明确几个数组的意思: 1.首先是tarjan缩点中的几个数组: dfn[i]:i点的 ...
- 使用JS实现2048小游戏
JS实现2048小游戏源码 效果图: 代码如下,复制即可使用: (适用浏览器:360.FireFox.Chrome.Opera.傲游.搜狗.世界之窗. 不支持Safari.IE8及以下浏览器.) &l ...
- 前端程序员必知的30个Chrome扩展-[转载]
谷歌Chrome浏览器是网络上可用的最好浏览器之一,并且自2011年11月超越了Firefox浏览器之后,已经成为了互联网上占主导地位的浏览器.今天,HTML5中国与大家分享一些实用的谷歌Chrome ...
- dede导航栏目调用
1.基础调用 {dede:channel row='5' type ='top' } <li><a href="[field:typelink/]">[fi ...
- 在VirtualBox虚拟机中安装Centos操作系统怎么与本地XShell远程连接
问题: 在VirtualBox安装好了CentOS操作系统后,我们怎么才可以用XSell连接虚拟机中的CentOS呢? 答案: (1)在windows下用cmd--ipconfig查看VirtualB ...