## 13.2 信号与槽
信号和槽机制是 Qt 的核心机制之一,要掌握 Qt 编程就需要对信号和槽有所了解。信号和槽是一种高级接口,它们被应用于对象之间的通信,它们是 Qt 的核心特性,也是 Qt不同于其它同类工具包的重要地方之一。
在我们所了解的其它 GUI 工具包中,窗口小部件(widget)都有一个回调函数用于响应 它们触发的动作,这个回调函数通常是一个指向某个函数的指针。在 Qt 中用信号和槽取代 了上述机制。
1\.信号(signal)
当对象的状态发生改变时,信号被某一个对象发射( emit)。只有定义过这个信号的类或者其派生类能够发射这个信号。当一个信号被发射时,与其相关联的槽将被执行,就象一个正常的函数调用一样。信号-槽机制独立于任何 GUI 事件循环。只有当所有的槽正确返 回以后,发射函数(emit)才返回。
如果存在多个槽与某个信号相关联,那么,当这个信号被发射时,这些槽将会一个接 一个地被执行,但是它们执行的顺序将会是不确定的,并且我们不能指定它们执行的顺序。
信号的声明是在头文件中进行的,并且 moc 工具会注意不要将信号定义在实现文件 中。Qt 用 signals 关键字标识信号声明区,随后即可声明自己的信号。 例如,下面定义了 几个信号:
```
signals:
void yourSignal();
void yourSignal(int x);
```
在上面的语句中,signals 是 Qt 的关键字。接下来的一行 void yourSignal(); 定义了信号 yourSignal,这个信号没有携带参数;接下来的一行 void yourSignal(int x);定义 了信号 yourSignal(int x),但是它携带一个整形参数,这种情形 类似于重载。
注意,信号和槽函数的声明一般位于头文件中,同时在类声明的开始位置必须加上 Q_OBJECT 语句,这条语句是不可缺少的,它将告诉编译器在编译之前必须先应用 moc 工具 进行扩展。关键字 signals 指出随后开始信号的声明,这里 signals 用的是复数形式而非 单数,siganls 没有 public、private、protected 等属性,这点不同于 slots。另外, signals、slots 关键字是 QT 自己定义的,不是 C++中的关键字。
还有,信号的声明类似于函数的声明而非变量的声明,左边要有类型,右边要有括 号,如果要向槽中传递参数的话,在括号中指定每个形式参数的类型,当然,形式参数的个 数可以多于一个。
从形式上讲,信号的声明与普通的 C++函数是一样的,但是信号没有定义函数实现。另 外,信号的返回 类型都是 void,而 C++函数的返回值可以有丰富的类型。
注意,signal 代码会由 moc 自动生成,moc 将其转化为标准的 C++语句,C++预处理 器会认为自己处理的是标准 C++源文件。所以大家不要在自己的 C++实现文件实现 signal。
2\.槽(slot)
槽是普通的 C++成员函数,可以被正常调用,不同之处是它们可以与信号( signal)相 关联。当与其关联的信号被发射时,这个槽就会被调用。槽可以有参数,但槽的参数不能有 缺省值。
槽也和普通成员函数一样有访问权限。槽的访问权限决定了谁可以和它相连。 通常, 槽也分为三种类型,即 public slots、private slots 和 protected slots。
public slots:在这个代码区段内声明的槽意味着任何对象都可将信号与之相连接。 这对于组件编程来说非常有用:你生成了许多对象,它们互相并不知道,把它们的信号和槽 连接起来,这样信息就可以正确地传递,并且就像一个小孩子喜欢玩耍的铁路轨道上的火车 模型,把它打开然后让它跑起来。
protected slots:在这个代码区段内声明的槽意味着当前类及其子类可以将信号与之 相关联。这些槽只是类的实现的一部分,而不是它和外界的接口。
private slots:在这个代码区段内声明的槽意味着只有类自己可以将信号与之相关 联。这就是说这些槽和这个类是非常紧密的,甚至它的子类都没有获得连接权利这样的信 任。
通常,我们使用 public 和 private 声明槽是比较常见的,建议尽量不要使用 protected 关键字来修饰槽的属性。此外,槽也能够声明为虚函数。
槽的声明也是在头文件中进行的。例如,下面声明了几个槽:
```
public slots:
void yourSlot();
void yourSlot(int x);
```
注意,关键字 slots 指出随后开始槽的声明,这里 slots 用的也是复数形式。
3\.信号与槽的关联
槽和普通的 C++成员函数几乎是一样的-可以是虚函数;可以被重载;可以是共有的、 保护的或是私有的,并且也可以被其它 C++成员函数直接调用;还有,它们的参数可以是任 意类型。唯一不同的是:槽还可以和信号连接在一起,在这种情况下,每当发射这个信号的 时候,就会自动调用这个槽。
connect()语句看起来会是如下的样子:
```
connect(sender,SIGNAL(signal),receiver,SLOT(slot));
```
这里的 sender 和 receiver 是指向 QObject 的指针,signal 和 slot 是不带参数的函数 名。实际上,SIGNAL()宏和 SLOT()会把它们的参数转换成相应的字符串。
到目前为止,在已经看到的实例中,我们已经把不同的信号和不同的槽连接在了一 起。但这里还需要考虑一些其他的可能性。
(1) 一个信号可以连接多个槽
```
connect(slider,SIGNAL(valueChanged(int)),spinBox,SLOT(setValue(int)));
connect(slider,SIGNAL(valueChanged(int)),this,SLOT(updateStatusBarIndicator(int)));
```
在发射这个信号的时候,会以不确定的顺序一个接一个的调用这些槽。
(2) 多个信号可以连接同一个槽
```
connect()
```
无论发射的是哪一个信号,都会调用这个槽。
(3) 一个信号可以与另外一个信号相连接
```
connect(lineEdit,SIGNAL(textChanged(const Qstring &)),this,SIGNAL(updateRecord(const Qstring &)));
```
当发射第一个信号时,也会发射第二个信号。除此之外,信号与信号之间的连接和信 号与槽之间的连接是难以区分的。
(4) 连接可以被移除
```
disconnect(lcd,SIGNAL(overflow()),this,SLOT(handleMathError()));
```
这种情况较少用到,因为当删除对象时, Qt 会自动移除和这个对象相关的所有连接。
(5) 要把信号成功连接到槽(或者连接到另外一个信号),它们的参数必须具有相同的顺序 和相同的类型
```
connect(ftp,SIGNAL(rawCommandReply(int,const QString&)),this,SLOT(processReply(int,const QString &)));
```
(6) 如果信号的参数比它所连接的槽的参数多,那么多余的参数将会被简单的忽略掉
```
connect(ftp,SIGNAL(rawCommandReply(int,const Qstring &)),this,SLOT(checkErrorCode(int)));
```
还有,如果参数类型不匹配,或者如果信号或槽不存在,则当应用程序使用调试模式 构建后,Qt 会在运行时发出警告。与之相类似的是,如果在信号和槽的名字中包含了参数 名,Qt 也会发出警告。
信号和槽机制本身是在 QObject 中实现的,并不只局限于图形用户界面编程中。这种 机制可以用于任何 QObject 的子类中。
当指定信号 signal 时必须使用 Qt 的宏 SIGNAL(),当指定槽函数时必须使用宏 SLOT()。如果发射者与接收者属于同一个对象的话,那么在 connect 调用中接收者参数可 以省略。
例如,下面定义了两个对象:标签对象 label 和滚动条对象 scroll,并将 valueChanged()信号与标签对象的 setNum()相关联,另外信号还携带了一个整形参数,这样标签总是显示滚动条所处位置的值。
```
QLabel *label = new QLabel;
QScrollBar *scroll = new QScrollBar;
QObject::connect( scroll, SIGNAL(valueChanged(int)),
label, SLOT(setNum(int)) );
```
4\.信号和槽连接示例
以下是 QObject 子类的示例:
```
class BankAccount : public QObject
{
Q_OBJECT
public:
BankAccount() { curBalance = 0; }
int balance() const { return curBalance; }
public slots:
void setBalance(int newBalance);
signals:
void balanceChanged(int newBalance);
private:
int currentBalance;
};
```
与多数 C++ 类的风格类似,BankAccount 类拥有构造函数、balance() “读取”函数 和 setBalance() “设置”函数。它还拥有 balanceChanged() 信号,帐户余额更改时将 发出此信号。发出信号时,与它相连 的槽将被执行。
Set 函数是在公共槽区中声明的,因此它是一个槽。槽既可以作为成员函数,与其他 任何函数一样调用,也可以与信号相连。以下是 setBalance() 槽的实现过程:
```
void BankAccount::setBalance(int newBalance)
{
if (newBalance != currentBalance)
{
currentBalance = newBalance;
emit balanceChanged(currentBalance);
}
}
```
语句 emit balanceChanged(currentBalance);将发出 balanceChanged() 信号,并使 用当前新余额作为其参数。
关键字 emit 类似于“signals”和“slots”,由 Qt 提供,并由 C++ 预处理器转换成标准 C++ 语句。
以下示例说明如何连接两个 BankAccount 对象:
```
BankAccount x, y;
connect(&x, SIGNAL(balanceChanged(int)), &y, SLOT(setBalance(int)));
x.setBalance(2450);
```
当 x 中的余额设置为 2450 时,系统将发出 balanceChanged() 信号。y 中的 setBalance() 槽收到此信号后,将 y 中的余额设置为 2450。一个对象的信号可以与多个 不同槽相连,多个信号也可以与特定对象中的某一个槽相连。参数类型相同的信号和槽可以 互相连接。槽的参数个数可以少于信号的参数个数,这时多余的参数将被忽略。
5\.需要注意的问题
信号与槽机制是比较灵活的,但有些局限性我们必须了解,这样在实际的使用过程中才能够做到有的放矢,避免产生一些错误。下面就介绍一下这方面的情况。
(1) 信号与槽的效率是非常高的,但是同真正的回调函数比较起来,由于增加了灵活 性,因此在速度上还是有所损失,当然这种损失相对来说是比较小的,通过在一台 i586- 133 的机器上测试是 10 微秒(运行 Linux),可见这种机制所提供的简洁性、灵活性还是 值得的。但如果我们要追求高效率的话,比如在实时系统中就要尽可能的少用这种机制。
(2) 信号与槽机制与普通函数的调用一样,如果使用不当的话,在程序执行时也有可能 产生死循环。因此,在定义槽函数时一定要注意避免间接形成无限循环,即在槽中再次发射 所接收到的同样信号。
(3) 如果一个信号与多个槽相关联的话,那么,当这个信号被发射时,与之相关的槽被 激活的顺序将是随机的,并且我们不能指定该顺序。
(4) 宏定义不能用在 signal 和 slot 的参数中。
(5) 构造函数不能用在 signals 或者 slots 声明区域内。
(6) 函数指针不能作为信号或槽的参数。
(7) 信号与槽不能有缺省参数。
(8) 信号与槽也不能携带模板类参数。
6\.小结
从 QObject 或其子类(例如 Qwidget)派生的类都能够使用信号和槽机制。这种机制本身 是在 QObject 中实现的,并不只局限于图形用户界面编程中:当对象的状态得到改变时, 它可以某种方式将信号发射(emit)出去,但它并不了解是谁在接收这个信号。槽被用于接收 信号,事实上槽是普通的对象成员函数。槽也并不了解是否有任何信号与自己相连接。而 且,对象并不了解具体的通信机制。这实际上是 “封装”概念的生动体现,信号与槽机制 确保了 Qt 中的对象被当作软件的组件来使用,体现了“软件构件化”的思想。
- 第 1 章 走近 Qt
- 1.1 Qt 简介
- 1.2 Qt 纪事概览
- 1.3 Qt 套件的组成(以 Qt4.5 为准)
- 1.4 Qt 的授权
- 1.5 Qt 的产品
- 1.6 Qt 的服务与支持
- 1.7 Qt 的最新进展
- 1.8为什么选择 Qt
- 1.9 问题与解答
- 1.10 总结与提高
- 第 2 章 Qt 的安装与配置
- 2.1 获取 Qt
- 2.2 协议说明
- 2.3 安装 Qt
- 2.4 配置 Qt4 环境
- 2.5 问题与解答
- 2.6 总结与提高
- 第 3 章 Qt 编程基础
- 3.1 标准 C++精讲
- 3.2 Windows 编程基础
- 3.3 Linux 编程基础
- 3.4 Mac 编程基础
- 3.5 问题与解答
- 3.6 总结与提高
- 第 4 章 Qt 4 集成开发环境
- 4.1 常见的 Qt IDE
- 4.2 Qt Creator
- 4.3 Eclipse
- 4.5 问题与解答
- 4.6 总结与提高
- 第 5 章 使用 Qt 基本 GUI 工具
- 5.1 使用 Qt Designer 进行 GUI 设计
- 5.2 使用 Qt Assistant 获取在线文档与帮助
- 5.3 使用 Qt Demo 学习 Qt 应用程序开发
- 5.4 问题与解答
- 5.5 总结与提高
- 第 6 章 Qt 4 程序开发方法和流程
- 6.1 开发方法
- 6.2 Hello Qt
- 6.3 几个重要的知识点
- 6.4 问题与解答
- 6.5 总结与提高
- 第 7 章 对话框
- 7.1 QDialog 类
- 7.2 子类化 QDialog
- 7.3 快速设计对话框
- 7.4 常见内建(built in)对话框的使用
- 7.5 模态对话框与非模态对话框
- 7.6 问题与解答
- 7.7 总结与提高
- 第 8 章 主窗口
- 8.1 主窗口框架
- 8.2 创建主窗口的方法和流程
- 8.3 代码创建主窗口
- 8.4 使用 Qt Designer 创建主窗口
- 8.5 中心窗口部件专题
- 8.6 Qt4 资源系统专题
- 8.7 锚接窗口
- 8.8 多文档
- 8.9 问题与解答
- 8.10 总结与提高
- 第 9 章 Qt 样式表与应用程序观感
- 9.1 应用程序的观感
- 9.2 QStyle 类的使用
- 9.3 样式表概述
- 9.4 使用样式表
- 9.5 问题与解答
- 9.6 总结与提高
- 第 10 章 在程序中使用.ui 文件
- 10.1 uic 的使用
- 10.2 Ui_YourFormName.h 文件的组成
- 10.3 编译时加入处理.ui 文件的方法
- 10.4 运行时加入处理.ui 文件的方法
- 10.5 信号与槽的自动连接
- 10.6 问题与解答
- 10.7 总结与提高 本章主要讲解了以下内容:
- 第 11 章 布局管理
- 11.1 基本概念和方法
- 11.2在 Qt Designer 中使用布局
- 11.3 基本布局实践
- 11.4 堆栈布局
- 11.5 分裂器布局
- 11.6 自定义布局管理器
- 11.7 布局管理经验总结
- 11.8 问题与解答
- 11.9 总结与提高
- 第 12 章 使用 Qt Creator
- 12.1 Qt Creator 概览
- 12.2 Qt Creator 的组成
- 12.3 快捷键和常用技巧
- 12.4 Qt Creator 构建系统的设置
- 12.5 处理项目间依赖关系( Dependencies )
- 12.6 Qt 多版本共存时的管理
- 12.7 使用定位器在代码间快速导航
- 12.8 如何创建一个项目
- 12.9 实例讲解
- 12.10 使用 Qt Creator 调试程序
- 12.11 问题与解答
- 12.12 总结与提高
- 第 13 章 Qt 核心机制与原理
- 13.1 Qt 对标准 C++的扩展
- 13.2 信号与槽
- 13.3 元对象系统
- 13.4 Qt 的架构
- 13.5 Qt 的事件模型
- 13.6 构建 Qt 应用程序
- 13.7 总结与提高
- 附录 A qmake 使用指南
- A.1 qmake 简介
- A.2 使用 qmake
- 附录 B make 命令
- B.1 命令解释
- B.2 使用 make 自动构建
- 附录 C Qt 资源
- C.1Qt 官方资源
- C.2 Qt 开发社区