一、引言
前面一些章节其实已经在使用信号和槽了,但是作为Qt中最重要的机制也是Qt区别与其他开发平台的重要核心特性,还是非常有必要单独介绍。
二、信号和槽的概念
2.1、概述
信号和槽是Qt特有的信息传输机制,是Qt设计程序的重要基础,它可以让互不干扰的对象建立一种联系。
信号和槽用于对象间的通信,在一个图形界面程序中,当一个部件中发生变化时,通常需要通知其他对象,在Qt中当采用特定事件发生时会发射(注意此处用的是发射(emit)而不是发送,老猿估计是因为这里的信号不是一对一发送的,而是可以一对多发射)一个信号来通知需要通知的对象,需要关注的对象就会调用信号连接的槽函数执行响应操作。
从QObject或其子类(如QWidget)继承的所有类都可以包含信号和插槽。
2.2、信号(signal)
当对象以其他对象可能感兴趣的方式改变其状态时,它们会发出信号。
Qt中的信号本质上是一个公有函数(即方法),信号只需声明,不能对其进行定义,声明函数时不能有返回值。由于信号是某个对象的公有方法,信号可以从任何地方通过emit语句发出,与直接调用信号对应函数类似,只是在函数调用语句前多了个emit。
发射信号的语句:emit 信号函数(信号参数)
Qt建议只从定义信号的类及其子类的类发出信号,Qt中的部件(又称为控件、组件)都有一些预定义的信号,如按钮的clicked()信号。信号发出方不知道也不关心是否有对象在接收它发出的信号
2.3、槽(slot)
槽可以用来接收信号,但槽也是部件派生类的正常成员函数,槽本质上是某个类的方法(包括虚方法),用来调用以响应特定信号,非虚函数的槽函数也可以正常调用,与普通成员方法的唯一的区别是信号可以连接到它们。
由于槽是普通成员方法,所以当直接调用时,它们遵循正常的成员方法调用规则。但是,作为槽,它们可以由任何组件通过信号连接调用,不管槽函数的访问级别是公开还是私有,信号的发射者无需知道哪些对象会执行槽函数响应它发射的信号(老猿认为由于Python的封装机制,这点在PyQt中意义不大)。
就像一个对象不知道是否有任何东西接收到它的信号一样,槽也不知道是否有任何信号连接到它。
2.4、处理信号
处理信号通常的做法是对可视部件进行子类化并添加对应的槽,以便处理感兴趣的信号。
当信号发
出时,通常与之相连的槽像正常的函数调用一样会立即执行,所有槽都返回后,将执行信号发出的emit语句后面的代码。但在信号和槽连接使用队列连接(connect函数的type参数值为Qt.QueuedConnection,使用Qt Designer定义的信号和槽连接通过PyUIC生成的代码不会使用该值)时情况略有不同,在这种情况下,emit关键字后面的代码将立即继续,槽将在稍后执行。
如果多个槽连接到一个信号,则当信号发射时,槽函数将按照建立连接的顺序依次执行。
与其他平台用于对象间通信的回调函数机制相比,信号和槽的机制稍微慢一些,发射与某些槽相连的信号,比用槽函数直接调用响应慢约十倍(不考虑槽函数本身的执行时间,只是从信号发射到槽函数开始执行的时间),但这种机制提供的灵活性增加了。
注意:
槽函数可能与多个对象的多个信号连接,有时代码需要判断信号是哪个对象发送的,此时可以使用在槽函数中使用sender()函数获取信号的发送对象来进行不同的处理。具体可参考:《PyQt学习随笔:槽函数获取信号发送对象的方法》
二、信号和槽的特点
2.1、信号和槽的参数必须匹配
Qt的signal s和slots机制确保,如果将一个信号连接到一个slot,该slot将在正确的时间使用信号的参数调用。在Qt中,信号和插槽可以接受任意数量的任何类型的参数,但老猿认为在PyQt中可能存在一些限制。
信号和槽的参数必须匹配体现在以下方面:
- 信号和槽的签名(英文原文是signature,老猿理解签名就是所带的参数)必须匹配,槽的参数可以少于信号的参数数量,因为槽函数可以忽略额外的参数,槽的参数不能多余信号的参数,因为若槽的参数更多,则多余的参数不能接收到信号传递过来的值,若在槽中使用了这些多余的无值的参数,就会产生错误;
- 信号和槽的参数类型必须匹配,在Qt Designer中进行信号和槽编辑时定义槽函数时必须带参数类型;
- 由于Qt是C++语言的平台,信号的参数类型都是C++类型的,可能存在部分参数类型Python无法使用的情况,这种情况老猿在学习中遇到过但没记录下来,也没有专门研究,但绝大多数类型特别是标准的C++类型可以支持;
- Python没有指针以及引用类型,这两种信号带的参数在信号和槽函数编辑时槽函数参数必须是指针以及引用类型,但在派生类实现槽函数时将其当成去掉指针或引用的类型。例如,
QAbstractButton *
类型在实现槽函数时应该作为QAbstractButton
类型处理。
2.2、信号和槽是松耦合的
这种松耦合表现在发出信号的对象既不知道也不关心哪个槽接收信号,槽也不知道有哪些信号连接到它。从某种程度上讲,信号和槽是类似电视节目和观众之间的关系。电视节目播放类似于信号发射,观众观看节目类似于槽,观众选择自己感兴趣的节目观看就是建立信号与槽的连接。
2.3、信号和槽之间是多对多的关系
- 可将多个信号连接到同一个槽
- 可将同一个信号连接到多个槽
- 可以将一个信号直接连接到另一个信号(这将在第一个信号发出时立即发出第二个信号)
下图是官网文档给出的信号和槽的对应关系示例:
2.4、信号和槽支持预定义和自定义匹配
因Qt在其类库中预定义了很多信号和槽,因此在Qt中可以仅使用Qt类库中预定义的信号和槽,也可以只使用Qt类库中预定义的信号而使用自已的槽,也可以使用Qt类库中预定义的槽来响应自已定义的信号,当然,槽和信号也都可以使用自定义的( 关于自定义信号本节不进行进一步探讨)。
2.5、可以断开信号和槽之间的连接
在信号和槽连接之后,connect方法会返回一个QMetaObjec.Connection类型的连接句柄,通过该句柄调用 disconnect()就可以断开信号和槽的连接。
三、Qt Designer中信号和槽操作
3.1、概述
在Qt Designer中可以自定义信号,但本节只介绍使用已有信号的操作方法。
3.2、槽函数的定义
要定义一个新的槽方法,通过Edit->Edit Signals/Slots或F4快捷键进入信号和槽编辑界面,如图:
进入编辑界面后双击要新增槽函数的部件或者从发射信号的部件开始使用鼠标画连接线,线的终点为槽函数所在部件(一般在窗口对象),释放鼠标后调出配置界面,如图为从pushButton发出信号到窗口的槽函数建立连接的案例:
点击上图中蓝色标记的Edit按钮,如图:
点击上图中的加号就可以增加新的槽函数。
注意:
- 在此定义槽函数需要输入槽函数名和各参数类型,不能输入参数名
- 参数类型只能是C++的通用类型和Qt支持的类型,不能是Python独有的类型
- 对已有的自定义槽函数可以通过鼠标双击进行编辑
3.3、信号和槽函数连接方法1
进入信号和槽的编辑界面后,可以通过鼠标在编辑界面右侧的信号列表中选择对应信号,在槽函数列表中选择对应槽函数,点击OK保存退出,就完成了信号和槽函数的连接建立。如图:
注意:
- 上图黄色荧光笔标记部分,如果要选择Qt对应部件预定义的槽函数,必须对该选项进行勾选
- 在进行信号和槽编辑的时候,需要注意如果窗口使用了布局部件全被覆盖了窗口表面区域,可能导致无法使用本方法进行信号和槽的连接编辑,请参考:《PyQt(Python+Qt)学习随笔:Designer中不能编辑信号和槽的问题》
- 在Qt Designer中如果带不同参数的同名信号如clicked()和clicked(bool)信号连接到名字相同但参数不同的槽函数会导致二者最终会执行同一个槽函数,具体请参考:《PyQt(Python+Qt)学习随笔:clicked和clicked(bool)信号连接同名函数出现的问题》
3.4、信号和槽函数连接方法2
除了3.3部分介绍的信号和槽函数的连接方法之外,还可以在Designer右侧的信号和槽函数编辑界面进行信号和槽函数连接。如图黄色荧光笔标记窗口:
点击下面蓝色标记的加号,新增一条可以编辑记录,选择每个字段鼠标双击进行编辑,如图:
退出编辑状态后相关连接自动保存。
这种方式与第一种方式相比,有如下不同:
- 第一种方式直接使用连接线类似绘制线条一样操作,相比第二种方式操作简单、连接显示直观,但当界面线条过多杂乱时反而不如第二种方式操作方便
- 第二种方式可以将一些不可见的对象如Action对象在编辑界面进行操作,而第一种方式则不行
小结
本节详细介绍了Qt中信号和槽的概念,同时详细阐述了PyQt中与Qt中使用的一些差别,并详细介绍了Qt Designer中的操作方法,有助于大家深入理解信号和槽的概念,并熟练使用。