(一)信号和槽概述
在Qt中,每次用户与控件互动的过程都被称为一个事件。举例来说,"用户点击按钮"是一个事件,"用户关闭窗口"也是一个事件。每个事件都会触发相应的信号,例如,当用户点击按钮时,会发出"按钮被点击"的信号;当用户关闭窗口时,会发出"窗口被关闭"的信号。这种事件和信号的机制使得在Qt应用程序中能够方便地处理用户交互和相应的行为。
在Qt中,每个控件都具备接收信号的能力,并且一个控件可以同时接收多个不同的信号。每当控件接收到信号时,它会执行相应的响应动作。举例来说,当窗口中的按钮接收到"按钮被点击"的信号时,它会执行"关闭自身"的响应动作;又如,当文本输入框接收到"文本框被点击"的信号时,它会执行"显示闪烁的光标,等待用户输入数据"的响应动作。在Qt中,对信号所做出的响应动作被称为槽函数。
信号和槽是Qt独有的消息传递机制,它能够连接不同的控件并使它们相互影响。举例来说,"按钮"和"窗口"是两个独立的控件,单纯点击按钮并不会对窗口产生任何影响。通过信号和槽机制,可以将按钮和窗口连接起来,实现"点击按钮会导致窗口关闭"的效果。
1.1 信号的本质
信号是由用户对窗口或控件进行操作所触发的特定事件,这些事件会导致相应的窗口类或控件类发出特定的信号,从而对用户的操作作出响应。因此,从本质上讲,信号就是事件的一种体现。
例如:
- 按钮单击、双击
- 窗⼝刷新
- ⿏标移动、⿏标按下、⿏标释放
- 键盘输⼊
那么在Qt中信号是通过什么形式呈现给使⽤者的呢?
- 我们对哪个窗⼝进⾏操作,哪个窗⼝就可以捕捉到这些被触发的事件。
- 对于使⽤者来说触发了⼀个事件我们就可以得到Qt框架给我们发出的某个特定信号。
- 信号的呈现形式就是函数,也就是说某个事件产⽣了,Qt框架就会调⽤某个对应的信号函数,通知使⽤者。
在Qt中信号的发出者是某个实例化的类对象。
1.2 槽的本质
槽(Slot)是对信号进行响应的函数。槽函数与一般的C++函数类似,可以定义在类的任何位置(public、protected或private),可以具有任意参数,可以被重载,也可以直接调用(但不能有默认参数)。
- 与一般函数不同的是:槽函数可以与一个信号相关联。当信号被发射时,与之关联的槽函数会自动执行。
1)信号和槽机制在底层通过函数间的相互调用来实现。每个信号可以用函数表示,称为信号函数;每个槽也可以用函数表示,称为槽函数。例如,"按钮被按下"这个信号可以用clicked()函数表示,而"窗口关闭"这个槽可以用close()函数表示。因此,使用信号和槽机制实现"点击按钮会关闭窗口"的功能实质上是clicked()函数调用close()函数的效果。
2)信号函数和槽函数通常位于某个类中。相较于普通的成员函数,它们的特殊之处在于:
- 信号函数用signals关键字修饰,槽函数用public slots、protected slots或private slots修饰。signals和slots是Qt在C++基础上扩展的关键字,专门用于指明信号函数和槽函数;
- 信号函数只需声明,无需定义(实现),而槽函数需要定义(实现)。
- 信号函数的定义是Qt⾃动在编译程序之前⽣成的.编写Qt应⽤程序的程序猿⽆需关注.
- 这种⾃动⽣成代码的机制称为元编程(Meta Programming).这种操作在很多场景中都能⻅到.
(二)信号和槽的使用
2.1 信号和槽的连接
信号与槽的连接是Qt中用于建立信号与槽关联关系的重要机制。通过连接,一个信号可以触发一个或多个槽函数的执行,实现对象之间的通信。
在Qt中,QObject类(QObjectQt内置的⽗类.Qt中提供的很多类都是直接或者间接继承⾃QObject)提供了⼀个静态成员函数connect(),该函数专⻔⽤来关联指定的信号函数和槽函数。
connect()函数原型:
connect (const QObject *sender, const char * signal , const QObject * receiver , const char * method , Qt::ConnectionType type = Qt::AutoConnection )
参数说明:
- sender:信号的发送者;
- signal:发送的信号(信号函数);
- receiver:信号的接收者;
- method:接收信号的槽函数;
- type:⽤于指定关联⽅式,默认的关联⽅式为Qt::AutoConnection,通常不需要⼿动设定。
2.2 查看内置信号和槽
1、系统⾃带的信号和槽通常是通过"Qt帮助⽂档"来查询。
2、如上述⽰例,要查询"按钮"的信号,在帮助⽂档中输⼊:QPushButton
- ⾸先可以在"Contents"中寻找关键字signals,
- 如果没有找到,继续去⽗类中查找.因此我们去他的⽗类QAbstractButton中继续查找关键字signals
这⾥的clicked()就是要找的信号。槽函数的寻找⽅式和信号⼀样,只不过它的关键字是slot。
2.3 通过Qt Creator⽣成信号槽代码
Qt Creator可以快速帮助我们⽣成信号槽相关的代码。
代码示例:在窗⼝中设置⼀个按钮,当点击"按钮"时关闭"窗⼝".
1、新建项⽬,如下图为新建完成之后所包含的所有⽂件(创建时要⽣成UI设计⽂件)
2、双击widget.ui⽂件,进⼊UI设计界⾯;
3、在UI设计窗⼝中拖⼊⼀个"按钮",并且修改"按钮"的名称及字体大小等;
4、可视化⽣成槽函数;
- 当单击"转到槽..."之后,出现如下界⾯:对于按钮来说,当点击时发送的信号是:clicked(),所以此处选择:clicked()
- 对于普通按钮来说,使⽤ clicked 信号即可.clicked(bool) 没有意义的.具有特殊状态的按钮(⽐如复选按钮)才会⽤到 clicked(bool) .
5、⾃动⽣成槽函数原型框架;
(1)在"widget.h"头⽂件中⾃动添加槽函数的声明;
【解释说明】
⾃动⽣成槽函数的名称有⼀定的规则。槽函数的命名规则为:on_XXX_SSS,其中:
- 1、以"on"开头,中间使⽤下划线连接起来;
- 2、"XXX"表⽰的是对象名(控件的 objectName 属性)。
- 3、"SSS"表⽰的是对应的信号。
如:"on_pushButton_clicked()",pushButton代表的是对象名,clicked是对应的信号。
(2)在"widget.cpp"中⾃动⽣成槽函数定义.
6、在槽函数函数定义中添加要实现的功能.实现关闭窗⼝的效果(即当我们运行代码点击关闭按钮之后窗口就会自动关闭)
(三)自定义信号和槽
3.1 基本语法
在Qt中,可以自定义信号和槽函数以满足特定需求。然而,自定义的信号函数和槽函数应该遵循一定的书写规范。
1、⾃定义信号函数书写规范
- ⾃定义信号函数必须写到"signals"下;
- 返回值为void,只需要声明,不需要实现;
- 可以有参数,也可以发⽣重载;
2、⾃定义槽函数书写规范
- 早期的Qt版本要求槽函数必须写到"publicslots"下,但是现在⾼级版本的Qt允许写到类的"public"作⽤域中或者全局下;
- 返回值为void,需要声明,也需要实现;
- 可以有参数,可以发⽣重载;
3、发送信号
- 使⽤"emit"关键字发送信号。"emit"是⼀个空的宏。"emit"其实是可选的,没有什么含义,只是为了提醒开发⼈员。
⽰例1:
1、在widget.h中声明⾃定义的信号和槽,如图所⽰;
2、在widget.cpp中实现槽函数,并且关联信号和槽
注意:图中的①和②的顺序不能颠倒
原因是,⾸先关联信号和槽,⼀旦检测到信号发射之后就会⽴⻢执⾏关联的槽函数。反之,若先发射信号,此时还没有关联槽函数,当信号发射之后槽函数不会响应.
3.2 带参数的信号和槽
Qt的信号和槽也⽀持带有参数,同时也可以⽀持重载.
此处我们要求,信号函数的参数列表要和对应连接的槽函数参数列表⼀致.
此时信号触发,调⽤到槽函数的时候,信号函数中的实参就能够被传递到槽函数的形参当中.
(💡 通过这样的机制,就可以让信号给槽传递数据了.)
示例:重载信号槽
(1)在"widget.h"头⽂件中声明重载的信号函数以及重载的槽函数;如下图所⽰:
(2)在"Widget.cpp"⽂件实现重载槽函数以及连接信号和槽。
- 需要注意的是:在定义函数指针时要指明函数指针的作⽤域。
(3)执⾏结果如下图所⽰:
(四)信号与槽的连接⽅式
4.1 ⼀对⼀
主要有两种形式,分别是:⼀个信号连接⼀个槽和⼀个信号连接⼀个信号。
(1)⼀个信号连接⼀个槽
代码⽰例:
1、在"widget.h"中声明信号和槽以及信号发射函数;
2、在"widget.cpp"中实现槽函数,信号发射函数以及连接信号和槽;
(2)⼀个信号连接另⼀个信号
代码示例:
在上述⽰例的基础上,在"widget.cpp"⽂件中添加如下代码:
4.2 ⼀对多
⼀个信号连接多个槽
⽰例:
(1)在"widget.h"头⽂件中声明⼀个信号和三个槽;
(2)在"widget.cpp"⽂件中实现槽函数以及连接信号和槽;
4.3 多对⼀
多个信号连接⼀个槽函数
⽰例:
(1)在"widget.h"头⽂件中声明两个信号以及⼀个槽;
(2)在"widget.cpp"⽂件中实现槽函数以及连接信号和槽;
(五)信号和槽的其他说明
5.1 信号与槽的断开
在Qt中,可以使用 disconnect 函数来断开信号与槽之间的连接。disconnect 函数允许你在运行时动态地断开连接,以便停止两个对象之间的信号和槽的关联。(disconnect的⽤法和connect基本⼀致.)
代码示例:
- 需要注意的是,断开连接时,必须提供与建立连接时相同的参数,包括信号函数和槽函数的指针或函数指针。
5.2 Qt4版本信号与槽的连接
Qt4中的connect⽤法和Qt5相⽐是更复杂的.需要搭配 SIGNAL 和 SLOT 宏来完成。⽽且缺少必要的函数类型的检查,使代码更容易出错。
代码示例:
(1)在"widget.h"头⽂件中声明信号和槽
(2)在"widget.cpp"⽂件中实现槽函数以及连接信号与槽;
Qt4版本信号与槽连接的优缺点:
- 优点:参数直观;
- 缺点:参数类型不做检测;
代码示例:
5.3 使⽤Lambda表达式定义槽函数
Qt5在Qt4的基础上提⾼了信号与槽的灵活性,允许使⽤任意函数作为槽函数。
但如果想⽅便的编写槽函数,⽐如在编写函数时连函数名都不想定义,则可以通过Lambda表达式来达到这个⽬的。
在Qt中,可以使用Lambda表达式来定义槽函数,这使得代码更加简洁和可读。Lambda表达式是C++11引入的一种匿名函数形式,允许我们在需要函数的地方内联定义函数。
下面是一个使用Lambda表达式定义槽函数的示例:
// 假设有一个QPushButton对象 btn connect(btn, &QPushButton::clicked, [=]() { qDebug() << "按钮被点击了"; // 在这里写下按钮被点击时需要执行的操作 });
【解释说明】
- 在这个示例中,我们将按钮的clicked信号与一个Lambda表达式连接起来。
- Lambda表达式作为槽函数,使用了方括号[]来捕获外部变量,这里使用了捕获所有外部变量的方式[=]。
- Lambda表达式中的代码将会在按钮被点击时执行。
5.4 信号与槽的优缺点
优点:松散耦合
- 信号发送者不需要知道发出的信号被哪个对象的槽函数接收,槽函数也不需要知道哪些信号关联了⾃⼰,Qt的信号槽机制保证了信号与槽函数的调⽤。⽀持信号槽机制的类或者⽗类必须继承于QObject类。
缺点:效率较低
- 与回调函数相⽐,信号和槽稍微慢⼀些,因为它们提供了更⾼的灵活性,尽管在实际应⽤程序中差别不⼤。
- 通过信号调⽤的槽函数⽐直接调⽤的速度慢约10倍(这是定位信号的接收对象所需的开销;遍历所有关联;编组/解组传递的参数;多线程时,信号可能需要排队),这种调⽤速度对性能要求不是⾮常⾼的场景是可以忽略的,是可以满⾜绝⼤部分场景。
总结
在Qt中,信号与槽是一种强大的通信机制,用于在对象之间进行异步通信。以下是关于信号与槽的简要小结:
- 信号:
- 信号是Qt中特有的概念,是一种特殊的成员函数,用于通知其他对象发生了某种特定的事件。
- 信号由
signals:
关键字声明,在类的声明部分中定义。 - 信号函数通常不包含实际的实现,只是用来发出信号。
- 信号函数可以有参数,参数的类型必须是Qt元对象系统支持的数据类型。
- 槽:
- 槽是用于响应信号的函数,可以执行特定的操作以响应信号的发生。
- 槽函数由
slots:
关键字声明,在类的声明部分中定义。 - 槽函数可以是普通成员函数、静态成员函数或者Lambda表达式。
- 槽函数的参数类型必须与连接的信号的参数类型匹配。
- 连接:
- 连接是指建立信号与槽之间的关联,使得当信号被发出时,相关的槽函数会被调用。
- 连接通过
QObject::connect()
函数来实现,可以连接两个QObject对象之间的信号和槽。 - 在连接时,需要指定发送信号的对象、信号函数、接收信号的对象以及槽函数。
- 连接还可以使用Qt 5中引入的新语法,使得连接更加类型安全。
- 自定义信号与槽:
- Qt允许自定义信号和槽函数,以满足特定需求。
- 自定义的信号函数和槽函数应该遵循一定的书写规范,例如在类的声明部分中使用
signals:
和slots:
关键字声明。 - 自定义的信号函数和槽函数可以有参数,参数的类型必须是Qt元对象系统支持的数据类型。
使用信号与槽机制可以实现对象之间的松耦合通信,使得代码更加模块化、可维护和可扩展。