QT知识整合--信号与槽机制
一、信号和槽的简介
(一)观察者模式
开始信号和槽的简介之前,我们回顾一个知识:设计模式中的“观察者模式”。观察者模式是软件设计中比较常用的设计模式之一,常用于定义对象之间的一对多的依赖关系。当一个对象的状态发生变化的时候,所有依赖它的对象都会得到通知并自动更新。
观察者模式的描述包括两个必要元素,分别为主动对象和被动对象(这两个名词是我自己取得方便后面解说,并不是国际通用名词)。
在观察者模式中主动对象和被动对象是相互关联的,被动对象会在主动对象发生变动的同时自身也会做出相应的改变,这就是观察者的作用原理,或者说是观察者模式的主要思想。
我们举个例子。比如我喜欢玩游戏,我玩过一款叫做“冲啊冲”的游戏,但是我已经将近三年没有上线过了。但是有一天我在某个叫做“巴拉巴拉”的视频平台上关注过的某个游戏博主更新了,他说“冲啊冲”这个游戏里面有个女角色更新了一张黑丝皮肤,而且还是免费只要肝活动就可以获取!wc!?本lsp就不淡定了,上号!!!诶!!!过了几天这个博主又更新了一条视频说可以去一个叫“大博”的平台关注该游戏的账号后每天在圈子里签到又可以领一张jk制服的皮肤。这下爽翻了,然后我就开始了为期一个月的签到活动。
从上面的例子中我们可以看出几个关键点。
1.有两个人,一个是我,另一个是某个博主,他也是个人。代表上面提过的主动对象和被动对象。
2.我的行为和博主的行为所发生开始的时间相差不大,不管是三年更一次还是几天更一次,我的行为都是受博主视频的影响。因为我看到消息是立马上号的
3.他一发视频我就上线玩游戏。代表我和博主的关联性。
第一个关键点就如我在讲观察者模式之前说到得概念,观察者模式一般都会涉及到主动对象和被动对象两个必要元素。例子中的博主就代表着主动对象,而我就是那个被动对象。
而第二个关键点又指出了不管多长时间,只有我看到了博主发出的视频后我就会去玩游戏,这表现了一种蝴蝶效应,由主动对象发生的变化带动被动对象发生变化(但是也不要钻牛角尖说为什么非得是这个博主发视频你才玩,不管是其他博主都只会发生一个带动我想上号的行为)。反过来这个博主可以被多个用户关注,这样就可以达到多个人看到这个消息,产生出一对多的关系。
又由第三条又可以得出由主动对象发生改变带动被动对象改变的这种行为一定要是被动者被动发生改变的,要被动者因为主动者发生改变被动者才会随之改变,期间被动对象不能自主发生改变(除非这个被动对象和其他主动对象连接在一起的,这个属于其他一对多的关系了,我们不管),这个关系我们一定要理解。
总结下来再说一下就是关注者模式的内容为:一个对象的状态发生改变以后另一个对象或者另外多个对象状态都会发生改变,而且这种行为一定是即时的。
(二)信号和槽
为什么我们要讲一下观察者模式呢?因为QT中的信号和槽就是对“观察者模式”的应用。
1.什么是信号?
信号(Signals)是一种用于对象间通信的机制。
我们直接理解字面意思,信号是向他人传达一种象征或隐喻,用来传达某种特定的意义或信息让别人知道我想干什么或者想让别人干什么。而在QT中的信号也是如此,结合上面的例子信号就相当于这个博主发布的视频,告诉我们有一个活动,从而带动我们玩游戏。但是信号是需要拥有载体去触发这个信息的传递的,那这个载体就是“博主发布了本次游戏活动的视频”了。
但是这种载体我们在QT中或者计算机思维中我们称为“事件”。我们可以找几个例子看看QT中的事件吧:
//QTui设计中自带的一些按钮组件:QT本身就是强大的GUI应用程序开发框架,其中在ui设计文件中就自带这多种可供使用的按钮组件,当我们点击这些按钮的时候我们就可以理解为一种时间的发生。
//由其他函数规定一种事件来触发:比如QT中自带的定时器功能,只要我们设置的时间一到也可以作为触发的条件。比如timeout()。
//鼠标&键盘等外设输入:鼠标你可以理解为上面第一个例子,简单也是差不多,当我们按下键盘某个键的时候,跟这这个事件相连的函数就会被启动,例如快捷键。
这只是一小部分可能触发信号的事件列表。Qt提供了丰富的事件处理机制,涵盖了各种用户交互和应用程序状态变化的情况。通过连接这些事件的信号到槽函数,可以实现灵活和响应式的应用程序。
2.什么是槽?
槽(Slots)是用于处理信号传递的数据或事件。
还是看到前面介绍观察者模式中讲的例子。槽就是我,我们收取到由事件变化(博主更新视频)所传递的信息后才会去行动(上号!!!肝黑丝!!!)。这就是槽。
槽多以我们编程自定义的函数,或者QT框架中自带的也可以。
3.信号与槽机制在QT中的实现
格式:
connect(sender,SIGNAL(signal),receiver,SLOT(slot));
connect()为QT内置用于实现信号与槽机制的功能函数,使用它时我们必须要有四个必要的参数用于实现connect函数。这一段代码也代表着将发送信号的事件对象和接受信号的槽成功连接在一起。而其中的四个参数解释如下:
1.sender:发送信号的事件对象,这里为你自定义的事件对象,sender名字为一个代称。
2.SIGNAL:触发时间后对象要发送的信号发送槽, SIGNAL是要写进代码的,而其中的signal为事件对象出发后发送给槽的信号(一个函数)。
3.receiver:接受信号的槽对象。这也是个代称,一般为自定义对象名称。
4.SLOT:代表槽对象接收事件对象发出的信号后所要调用的函数,SLOT在代码中必须要写出来,slot是一个代称,为工程师自定义的函数,这种函数我们也称为槽函数。
下面举一个例子来细致讲一下上面的内容。
信号与槽机制实现过程如下:
(1)确定事件对象并实现,本次例子使用QT按钮作为对象。
在ui设计界面中为ui添加两个按钮组件作为本次传递信号的对象。在ui文件中设置的一切组件或者包括主界面本身,他们在系统中都会自动的实例化,我们在使用信号与槽机制的时候不用再写代码实例化这个组件就可以直接使用定义的名称进行使用这个按钮的对象。名称可看向下图中右边的显示,它也标明了对应的类和对象。
mainwindow.ui
(2)确定槽对象和槽函数并实现
mainwindow.h
所谓的槽对象我们可以理解为槽函数所在类的实例,而槽函数就是槽对象接受信号后自动触发的函数。
mainwindow.cpp
再来到cpp文件中,对 两个在头文件声明的槽函数进行实现。
(3)连接事件对象和槽对象(实现信号与槽机制)
前面也介绍了connect函数的作用,结合格式做分析。
首先第一个参数中的ui由头文件,也是我们创建工程时QT自动为我们生成的一个指针ui,它指向主界面的对象,这意味我们可以使用指针ui任意指向主界面的任何控件。现在的ui指向主界面中的pushbutton按钮。
第二个参数为QPushButton类中的一个信号,你可以理解我们在ui中设置的pushbutton按钮属于一个QPushButton类对象,调用类中的clicked()函数可以向槽对象传递一个信号。
第三个函数也就是我们的槽对象了,利用this指针指向MainWindow类对象,然后由MainWindow类对象来接收按钮发送来的信号。
第四个为槽对象接受后调用的槽函数,下面已经实现了。
运行结果下图:
结果为按四下按钮1,三下按钮2.
这里补充一个东西,我们设置好按钮以后可以在ui文件中右键这个按钮,点击以后弹出一个选项框找到“转到槽”,之后又会弹出一个窗口,我们选择clicked()可以快速的在程序文件和头文件中生成对应的槽函数模版。这个自动生成的槽函数跟我们直接手写的是一样的,可以根据需求自行更改唯一标识符,QT有这种功能纯属是ide带来的方便罢了。
而使用这种方式创建的槽函数已经具备信号与槽机制的功能了,也就是我们不需要使用connect显示的将事件对象和槽对象进行连接。这属于图形的一种特有便利。但这中方式自己看的时候倒是无所谓,但是给其他人看的话难免麻烦些,所以团队开发的时候一般建议还是使用connect会比较直观一点。
可以看到使用图形转到槽的功能时我们并没有使用connect连接事件对象和槽对象就可以直接实现信号与槽函数的功能。
3.自定义信号槽实例
讲之前我们先记两个关键字,“emit”和“signals”,这两个一个是用于自定义信号的发送,另一个是在头文件声明信号时需要使用它来划定他是一个信号,使用方法跟访问修饰符差不多。
先设计好讲解需要的实例框架吧,就用boss和我的一个从属关系吧,boss想我发送一个让我完成一项工作的信号,而我收到任务信号后完成这项任务并向老板汇报任务已完成。
(1)自定义信号和槽
按前面所说如果我想自定义一个信号的话,那么就需要使用signals在头文件中声明一个信号,使用方法和访问修饰符是一样的,规定一个范围。在QT中自定义信号是不具备任何功能的,或者说我们自定义的信号函数只需要声明,并不需要进行实现给他特定的功能。
这里我添加了一个me文件和一个boss文件分别代表我和boss。按照前面设计的框架,任务信号是由boss写的,所以我们在boos头文件声明任务信号boss_slot,如下:
(2)设置槽函数并连接自定义信号
槽函数为接受信号的对象自动调用的函数,这里接收信号的对象是我,所以我们在me头文件中声明一个槽函数me_task并在cpp文件中实现输出一段“任务已完成”的文字。
之后在“mainwindow”里面进行连接,写在构造函数中。
连接之前我们先要将两个类实例化,声明是在头文件中。
使用connect函数连接,发送信号的对象为boos类对象Boss,信号为boss文件中的boss_slot函数。接受对象为me类对象Me,Me接受信号后将触发me_task函数。到这步基本就算是连接成功了。
(3)emit发射信号
emit可以理解为将建立的自定义信号发送出去,与之对应相连的接受信号的对象接受这个信号后启动槽函数。emit在语法里面可有可无,但是团队开发中你要是不写emit的话最好打好注释方便他人阅读。下图中本是在头文件中声明的一个boos_task函数,由cpp文件中实现并发出boos_slot信号。发出信号以后与之连接的槽对象接收到信号自动调用对应的槽函数,用我上面注释掉额这段代码也是可以的,就是将一个按钮对象和mainwindow本身的对象连接在一起自动调用的boss_slot函数,运行结果如图: