Signal and Slots
Signal and Slots 用于对象之间通信。 它是 Qt 的核心特性之一, 并且也是Qt 与其它框架差别最大的部分。
概述
在GUI编程中, 如果我们改变了一个控件, 我们可能想其它控件知道; 换言之, 我们希望任何类型的 Object 能够彼此通信。
一些Tookits使用回调函数来实现通信,如果你想在一个函数中通告一些事件, 你需要在这个函数中使用一个函数指针。
但是这种做法有两个缺陷:
他们不是类型安全的, 我们在编译期无法获知这个指针是否正确。
这个回调函数把调用者和被调用者仅仅结合在了一起。
在Qt中, 我们有回调函数的替代办法, 使用Signal and Slots。 一个Signal 会在某个事件发生时被发射, 而slot 是一个用于响应特定signal的函数。 Qt 有很多预定义的信号 、槽, 但是我们可以通过定义 Object 的子类来加入自己的信号、槽。
类型安全: signal 和 slot 的签名(名称、参数)必须一致; 不过事实上,slot的参数是取signal的前n个(n表示signal的参数个数), 因为slot可以忽略信号附带的一些值。 因为这种签名机制兼容于 C++, 因此编译器可以发现类型错误。
发射信号的类不关注是谁接收了这个信号。
当一个Object发生变化、并且它希望这种变化为外界所知道时, 它会发出信号;但它不会理会最终是谁处理了这个信号。 这就是信息封装, 它使得 Qt Object 能够像软件组件那样被使用。
一个信号可以连接多个槽; 多个信号可以连接单个槽; 信号和信号也可以直接连接。
编译
C++ 预处理器修改、删除signal slot和 emit 关键字, 为 C++编译器提供了符合 标准 C++语法的代码。
在包含signal slot的类定义中运行moc, 一个代码源文件会产生。 它能够使用工程中的其他 object 文件进行编译和链接, 也能被其它Object文件如此使用。如果使用qmake, 这个规则会在qmake 产生的makefile中自动添加。
Signal
信号能够在任何地方发射, 但是最好在定义它的类及子类里使用。
当一个信号发出后, slot函数会立即执行, 跟一个函数调用表现相同; 此时, 这个signal-slot 是独立于event system的。 但是如果在connect时使用 Queued Connection, emit之后的代码会继续执行。 而slot会在稍后的事件循环中被调用。
如果一个信号连接多个槽, 那么这些槽函数会按照它们被连接的顺序依次执行。
如果在信号槽中使用了特殊类型, 则该信号或者槽很难与其它类型通信。 比如 QScrollBar::valueChanged() 使用了 QScrollBar::Range 作为参数, 则它只可能与QScrollBar的槽函数连接。
在一些第三方库中也会有signal /slots 关键字, 这可能会导致编译器发生 warning 或者 error。 可以使用 #undef 去掉某个 signal / slots 的定义。
Slots
除了能够在其连接的signal发射时被调用, slot 是正常的 C++成员函数。 因此, 它们在被调用时, 跟一般成员函数表现一致。 而作为 slot, 无论它的访问级别(protected, private, public), 它们能够被任意Object 调用。 也就是说, 一个任意Object的信号能够引发某一类的private slots。
同时, virtual 类型的slot在实际中也很有用。
和callback 相比, Signal and Slots 提供的灵活性也会稍微有些慢,当然这个在实际应用中是无关紧要的。 一般来说, 在没有虚函数调用的前提下, 从发出一个信号, 到槽函数调用,会比直接调用槽函数慢 10倍。 这些开销其中包括 定位连接的类, 安全的遍历所有连接(i.e. checking that subsequent receivers have not been destroyed during the emission) , 按照通用的模式准备参数。 尽管10个函数调用才及的上一个 signal-slot, 它还是比 new 和 delete 的开销要小得多。 整体开来, 它在所有的函数调用导致的开销中所占比重非常小。系统调用也导致比 signal-slot大的多的开销, 它甚至有可能直接导致多余10个的函数调用。
在一台 i586-500 机子上, 你可以对一个 receiver 发出 2,000,000 个信号, 或者对两个receiver 发出 1,200,000 个信号。
Meta Object In Signal/Slots
Meta Object Compiler 遍历声明类的文件, 并生成初始化 Meta-Object 的文件。 MetaObject 包含了所有的信号槽, 以及指向他们的指针。
MetaObject 中包含了C++ 类的一些额外的信息。比如,
类名;
调用 inherits 查询某个对象的类别所需信息;
使用 qobject_cast 所需信息;
Q_OBJECT 宏会在预处理器中被展开, 并生成几个函数, 这些函数会在moc中生成函数体。 如果发生编译错误“undefined reference to vtable for ClassName
”, 你可能忘记运行moc, 或者在链接时没有加上 moc 的输出文件。
Moc 会忽略其它的成员函数(只处理 signal slot)。
对于定义了默认参数值的signal, slot中若有这个参数, 则应该有相同的定义, 或者忽略该参数, 例如 对于QObject::destroyed(QObject * = 0)。
应该是:
connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed(Qbject*)));
connect(sender, SIGNAL(destroyed(QObject*)), this, SLOT(objectDestroyed()));
connect(sender, SIGNAL(destroyed()), this, SLOT(objectDestroyed()));
高阶用法
1. mapping signal
如果你需要发送者的信息,你可以调用 QObject::sender(), 获取指向sender 的指针。
QSignalMapper用于”有多个signal 同时连接一个槽, 而这个槽需要对不同的signal 做不同处理”的情况。
例如, 你有3个pushButton用于选择需要打开的文件: "Tax File", "Accounts File", or "Report File",
为了正确的打开文件, 可以使用QSignalMapper::setMapping把click信号映射到QSignalMapper类, 然后把click信号连接到 QSignalMapper 的map槽函数。
signalMapper = new QSignalMapper(this);
signalMapper->setMapping(taxFileButton, QString("taxfile.txt"));
signalMapper->setMapping(accountFileButton, QString("accountsfile.txt"));
signalMapper->setMapping(reportFileButton, QString("reportfile.txt"));
connect(taxFileButton, SIGNAL(clicked()),
signalMapper, SLOT (map()));
connect(accountFileButton, SIGNAL(clicked()),
signalMapper, SLOT (map()));
connect(reportFileButton, SIGNAL(clicked()),
signalMapper, SLOT (map()));
然后再把 mapped 信号连接到处理文件的readFile 槽函数上。
以下的代码也会运行, 但是由于 Qt参数归一化操作(signature normalization), 会稍微慢一些。
connect(signalMapper, SIGNAL(mapped(const QString &)),
this, SLOT(readFile(const QString &)));
2. blocked signal
QT中经常会用到blockSignals, 且是成对出现,实现时常在函数的开始阻塞信号,在函数的结尾释放信号。
3. signal spy
signal spy 是一个 variant 列表, 当添加了 需要监测的信号以后, 它会把所有监测到的信号都放到这个列表里。
使用 3rd signal/slot
可以在Qt中使用3rd的 signal/slot机制, 甚至可以在同一个工程中使用。但需要把下面一行代码加入到pro文件中:
CONFIG += no_keywords
它告诉Qt不适用 moc的关键字signals/slots/emit, 因为这些名字会在3rd中出现, 比如boost。 在使用signal/slot的地方, 可以简单的使用 Qt宏 Q_SIGNALS (or Q_SIGNAL), Q_SLOTS (or Q_SLOT), and Q_EMIT 替代。
Comments:
signature normalization: 对于 QObject::connect的 Queued Connection, Qt 会把参数信息保存起来, 以在event system中查询, 这时会调用 参数的拷贝构造函数以及其它信息。 因此参数必须是能够被 Qt's meta-object system 识别的类, 如果希望使用自定义类, 应该在connect之前调用qRegisterMetaType(), 把自定义类添加到 Qt's meta-object system。
参考 stackoverflow
What benefits does QT get with normalized signature
Qt MOC: When default and copy constructor are used?
Signal and Slots的更多相关文章
- Qt中连接到同一signal的多个slots的执行顺序问题(4.6以后按连接顺序执行)
起源 前些天忘记在哪儿讨论过这个问题,今天在csdn又看到有网友问这个问题,而其他网友却无一例外的给出了“无序”这个答案. Manual Qt的问题,当manual中有明确文字说明时,我们应该以Qt的 ...
- Qt中连接到同一signal的多个slots的执行顺序问题(现在是看连接顺序,以前是无序的)
in the order they have been connected 起源 前些天忘记在哪儿讨论过这个问题,今天在csdn又看到有网友问这个问题,而其他网友却无一例外的给出了“无序”这个答案. ...
- Qt Signal and Slot
Qt4中的信号槽 Qt4中的信号槽是通过SIGNAL,SLOT两个宏,将参数转换成字符串.Qt编译前,会从源码的头文件中提取由signal和slot声明的信号和槽的函数, 将其组成一张信号和槽对应的字 ...
- Contents Of My Blogs
C++ How To Use Goto? Preprocessing Directive std::array std::deque std::forward_list std::map std::m ...
- Qt5中的信号槽
Qt4中的信号槽 Qt4中的信号槽是通过SIGNAL,SLOT两个宏,将参数转换成字符串.Qt编译前,会从源码的头文件中提取由signal和slot声明的信号和槽的函数, 将其组成一张信号和槽对应的字 ...
- Creating Dialogs
#ifndef DIALOG_H #define DIALOG_H #include <QtWidgets> //#include <QDialog> //#include & ...
- QT 初阶 第二章 创建对话框(查找对话框实例)
最终效果图: 该对话框由三个文件组成:finddialog.h .finddialog.cpp. main.cpp 代码+注释 /*--finddialog.h--*/ #ifndef FINDDIA ...
- 词频统计_输入到文件_update
/* 输入文件见337.in.txt 输出文件见338.out.txt */ #include <iostream> #include <cctype> #include &l ...
- Qt Meta Object System-元对象系统
研一的时候开始使用Qt,感觉用Qt开发图形界面比MFC的一套框架来方便的多.后来由于项目的需要,也没有再接触Qt了.现在要重新拾起来,于是要从基础学起. Now,开始学习Qt事件处理机制. 元对象系统 ...
随机推荐
- 教你用Perl 实现Base64编码
在用脚本后台发送邮件时,需要将html的内容转换成Base64编码的形式,这样邮件客户端会自动对Base64编码的内容进行解码,还原成原来的内容. Base64.pl: #!/usr/bin/perl ...
- C#实战Microsoft Messaging Queue(MSMQ)
C#实战Microsoft Messaging Queue(MSMQ)消息队列(干货) 前言 在使用MSMQ之前,我们需要自行安装消息队列组件!(具体安装方法大家自己搜一下吧) 采用MSMQ带来的好处 ...
- SpringMVC入门笔记一
SpringMVC优势 性能比struts2好 简单 便捷 易学 和Spring无缝集成(使用spring ioc aop) 约定优于配置 能够简单进行Junit测试 ...
- Winform 让跨线程访问变得更简单
Winform 让跨线程访问变得更简单 前言 由于多线程可能导致对控件访问的不一致,导致出现问题.C#中默认是要线程安全的,即在访问控件时需要首先判断是否跨线程,如果是跨线程的直接访问,在运行时会抛出 ...
- Chrome插件i18n多语言实现
i18n(其来源是英文单词 internationalization的首末字符i和n,18为中间的字符数)是“国际化”的简称.Chrome插件框架中i18n的封装API: chrome.i18n.ge ...
- thinkpad x230i U盘启动
现在的thinkpad的笔记本真麻烦,设置个U盘启动都不好使,网上找了好多都不管用,后来打电话问的售后电话才搞定,具体步骤如下: 按F1进bios的 [Security]中最下面Secure Boot ...
- anadonca环境配置和模块安装
1.最方便的python环境配置: 下载anaconda即可,自带spyder,集成科学计算的库,自带pip,不用折腾. 想用sublime编写python并运行的话,需要自己配置编译环境,并下载插件 ...
- Python爬虫小白入门(五)PhatomJS+Selenium第二篇
一.前言 前文介绍了PhatomJS 和Selenium 的用法,工具准备完毕,我们来看看如何使用它们来改造我们之前写的小爬虫. 我们的目的是模拟页面下拉到底部,然后页面会刷出新的内容,每次会加载10 ...
- Spring MVC 笔记 —— Spring MVC 文件上传
文件上传 配置MultipartResolver <bean id="multipartResolver" class="org.springframework.w ...
- .Net程序员学用Oracle系列(2):准备测试环境
<.Net程序员学用Oracle系列:导航目录> 本文大纲 1.创建说明 1.1.为什么要创建的测试环境? 1.2.了解 Oracle 实例的默认用户 2.创建环境 2.1.创建基本环境 ...