QT学习日记 | 信号与槽

目录

前言

一、初始信号与槽

1、信号与槽的本质

2、信号与槽的使用

3、内置信号、内置槽函数与自定义信号、自定义槽函数

(1)文档查询

(2)自定义信号与内置槽函数的使用

4、信号与槽函数关联关系

5、带参数的信号与槽函数

6、信号与槽函数的重载

二、深剖信号与槽函数细节

1、宏函数 SIGNAL 与 SLOT

2、为啥通过 ui 指针可以操作界面中拖动创建的控件


前言

        本文主要学习QT最重要的机制之一 —— 信号与槽;从认识信号与槽到熟练掌握、深刻理解信号与槽相关细节;

一、初始信号与槽

        不少朋友在听到信号这一词可能会想到 Linux 中的信号机制,本文中的信号与 Linux 中的信号没有任何关系,但是两者之间又非常的相似;当某一时间到来时,操作系统给当前进程发送一个信号,当进程收到这个信号后会调用指定的处理方法来处理这个信号,关于信号更多细节可以关注下面这篇文章;

Linux | 信号-CSDN博客

1、信号与槽的本质

信号本质:在 Qt 中,所谓信号就是某个事件,当某个控件发生了某个事件时,可以根据这个事件做出指定动作;这里的事件就是信号,比如 Qt 中的按钮QPushButton,这个控件被点击,这个点击就是一个事件;

槽本质:在 Qt 中,所谓槽的本质就是收到信号时,我们对应需要进行处理这个信号的动作,还是以上述按钮为例,我们希望用户点击按钮时,窗口的标题会发生改变,这里窗口标题发生改变就是槽函数所处理的动作;

2、信号与槽的使用

        我们通过 connect 来将某个信号与槽函数来进行绑定,从而达到某个信号发出时,指定的槽函数会被调用;connect 函数如下所示;

QT学习日记 | 信号与槽_第1张图片

QMetaObject::Connection QObject::connect

(

const QObject *sender,

const char *signal,

const QObject *receiver,

const char *method,

Qt::ConnectionType type = Qt::AutoConnection

)

        这样拆解不知道看起来是否更加清晰,该函数有五个参数,其中第五个参数一般为缺省即可,因此我们仅仅需要关注前四个参数;

参数一:该参数类型为QObject类的指针,为信号的发出者的地址;

参数二:该参数为 const char* ,该参数为对象发出的信号;

参数三:该参数类型也为QObject类指针,为信号的接收者,也是处理信号的对象;

参数四:该参数为 const char* ,为接收者所要处理的动作;

注意:

1、第一个参数和第三个参数为QObject类,该类为 Qt 提供的一个基类,Qt 为我们提供的所有类都继承自该类,该类可以说是 Qt 所有内置类的 "祖先类";联想一下C++中切片,也就是说派生类可以赋值给基类;

2、参数二和参数四为const char*类型,而实际上,我们传入的信号和槽函数都是一个函数指针,因此在使用时,我们需要使用SIGNAL和SLOTS将对应的函数指针转换成const char*类型;

需求设计:我们在ui页面中拖出一个按钮组件,我们期望按下这个按钮指定的窗口也关闭;

1、我们先创建一个基于Widget类的窗口项目;

QT学习日记 | 信号与槽_第2张图片

2、我们点击widget.ui文件,绘制一个按钮,并设置按钮文本

QT学习日记 | 信号与槽_第3张图片

3、信号与槽函数关联

        注意到这一步时,我们有两种方法,两种方法各有优劣,实际开发中,哪种方法方便用哪种即可;

3.1、代码添加连接

1)切换到 widget.h 文件,写下槽函数声明

QT学习日记 | 信号与槽_第4张图片

        注意这里的函数声明中,有一个我们陌生的关键字,slots,这个是 Qt 自己设置的关键字,同样,我们还可以与 protected、private组合;这个关键字表示声明的是槽函数;在 Qt5 后提出可以省略该关键字;

2)编写槽函数

QT学习日记 | 信号与槽_第5张图片

        这里教大家一个小技巧,我们可以直接选中我们函数声明,按住键盘 alt + enter,可以直接一键生成函数定义;生成的函数定义当然放在widget.cc文件,声明与定义分离,这一点以后就不在重复;

QT学习日记 | 信号与槽_第6张图片

        这里再次补充一下,qDebug是 Qt 为我们提供的打印类,我们使用这个来替代cout;

3)建立连接

QT学习日记 | 信号与槽_第7张图片

注意:这里我们第二个参数和第四个参数并没有使用宏函数SIGNAL()与SLOTS(),这一点我们后面说明; 

参数一:这里我们用ui界面绘制的这个按钮,因此我们在ui这个指针下寻找按钮对象;

参数二:这里clicked表示点击信号,这里点击表示鼠标按下并抬起;同样还有如下信号;

QT学习日记 | 信号与槽_第8张图片

        关于这里的状态切换以及点击带参数这两个可能大家不大理解,这里暂不做介绍,后面讲解复选框自然会介绍;

参数三:处理信号的对象,也就是接收者;

参数四:信号的处理动作;(这里动作也就是槽函数,我们可以使用 Qt 为我们提供的,也可以使用自己写的,这里用自己写的,填写我们刚写下的槽函数地址即可)

        此时,我们想要的功能便实现了,可以点击运行项目,点击按钮后会关闭窗口,并且还会打印内容;接下来介绍第二种方法,我们重新创建一个新的项目;

3.2、自动添加链接

1)还是在ui界面绘制一个按钮,步骤就不再赘述,与上述方法中步骤一相同

2)右击按钮,选择转到槽

QT学习日记 | 信号与槽_第9张图片

        此时会弹出如下窗口,实际上,这里就是选择按钮发出的信号,等同于我们调用connect时前两个参数的填写;这里前五个信号,我们在上述都讲过的,这里不再赘述;我们直接选择clicked;

QT学习日记 | 信号与槽_第10张图片

        此时会在widget.cpp文件自动生成函数定义,且这个函数定义名字是有讲究的,我们不能修改这个槽函数的名字,否则会关联失败,具体原因,后面会详细介绍; 

QT学习日记 | 信号与槽_第11张图片

3)编写槽函数

QT学习日记 | 信号与槽_第12张图片

        到这一步,我们连接就完毕了,所有工作完成;我们可以直接运行程序查看效果;

3、内置信号、内置槽函数与自定义信号、自定义槽函数

        上述案例中,我们使用了内置信号clicked,与自定义槽函数handler,实际上, Qt 也为我们提供了一些内置信号与槽函数,我们可以通过Qt Creator的帮助文档中查询;下面我们以 QpushButton 为例;

(1)文档查询

点击 Qt Creator 左侧帮助一栏;

QT学习日记 | 信号与槽_第13张图片

在索引中输入指定目标,这里输入 QPushButton;

QT学习日记 | 信号与槽_第14张图片

该类相关接口菜单如下(右边那个,用红色框圈住了菜单中常用的);

QT学习日记 | 信号与槽_第15张图片

        这里我们发现,我们并没有看到信号,实际上由于继承这一特性,信号很可能在父类,我们可以通过如下方式找到其父类;

QT学习日记 | 信号与槽_第16张图片

        我们点击找到其父类,我们在父类的属性菜单中,很容易找到了信号与槽函数字段;

QT学习日记 | 信号与槽_第17张图片

        如下所示,这信号不就是我们刚刚讲过的信号吗?我们便可以通过这样的方式进行文档查询;

QT学习日记 | 信号与槽_第18张图片

(2)自定义信号与内置槽函数的使用

        之前那个关闭窗口,我们是使用内置信号 clicked 与自定义槽函数 handler 来实现的;下面我们再使用 自定义信号与内置槽函数 close 再来实现一次;我们首先再次创建一个新项目;

        还是一样,ui界面绘制按钮,如下所示;

QT学习日记 | 信号与槽_第19张图片

        widget.h文件声明信号;

QT学习日记 | 信号与槽_第20张图片

注意:信号仅需声明,无需定义,且无返回值;

        我们在使用connect关联函数;

QT学习日记 | 信号与槽_第21张图片

        这就完了吗?并没有,这里我们仅仅只是进行了信号关联,我们并没有发送这个信号;我们可以使用 emit关键字来发送信号;我们再返回ui页面,右击按钮,转到槽,选择clicked;然后我们再这个槽函数发送信号;

QT学习日记 | 信号与槽_第22张图片

        编译运行程序,此时我们便可以点击按钮,发出自定义信号,接着窗口就会便关闭;

QT学习日记 | 信号与槽_第23张图片

4、信号与槽函数关联关系

一对一:一个信号对应一个槽函数;

一对多:一个信号对应多个槽函数;

多对多:多个信号对应多个槽函数;

        之前,我们都是采用一对一的方式,接下来,我们再来试试一对多;我们再创建一个空项目,继承自widget类;

        绘制出一个按钮,如下所示;

QT学习日记 | 信号与槽_第24张图片

        在widget声明一个信号与两个槽函数;

QT学习日记 | 信号与槽_第25张图片

        实现这两个槽函数,槽函数啥都不干,完成一个打印自己函数名的工作即可;

QT学习日记 | 信号与槽_第26张图片

        为这两个槽函数建立连接,都连接到 mySignal 这一个信号上;

QT学习日记 | 信号与槽_第27张图片

        我们想点击按钮,执行这两个槽函数,因此我们为按钮的点击信号设置一个槽函数;来到设计师界面(ui界面),右击按钮,转到槽,选择clicked信号;并编写点击按钮对应槽函数;

QT学习日记 | 信号与槽_第28张图片

此时我们编译运行程序,我们每次点击按钮都会发送要给 mySignal 信号,而这个信号与 handler1 与 handler2 槽函数都进行了关联,这两个槽函数都会被执行,因此一次点击就会有两个打印结果;

QT学习日记 | 信号与槽_第29张图片

        同样多对多的方式我就不一一演示了;有兴趣的可以自己下去做实验;

拓展:我们不仅可以信号与槽函数进行关联,我们还以信号与信号关联,一个信号被触发,他会触发另一个信号;

5、带参数的信号与槽函数

        我们的信号与槽函数可以带参,通过参数,将信号的参数传递给槽函数;不过我们必须遵守如下规则;

1、参数类型匹配

2、信号的参数个数要大于等于槽函数参数

        我们声明一个信号,两个槽函数,如下所示;

QT学习日记 | 信号与槽_第30张图片

        接下来,我们在.cpp文件中对这两个槽函数进行定义;

QT学习日记 | 信号与槽_第31张图片

        我们在构造函数中连接并发送这个信号;

QT学习日记 | 信号与槽_第32张图片

        接着运行程序,如下所示;

QT学习日记 | 信号与槽_第33张图片

6、信号与槽函数的重载

        信号与槽函数可以进行重载,只不过在重载后,进行connect时,需要显示指定connect哪一个重载函数;

        依然,我们创建一个信号,用这个信号连接两个重载槽函数;首先创建新项目,声明如下信号与槽函数;

QT学习日记 | 信号与槽_第34张图片

        我们在对声明槽函数进行定义,如下所示;

QT学习日记 | 信号与槽_第35张图片

        最后对槽函数进行连接,主要是这里连接的代码,由于函数重载,我们如果直接连接,不知道连接到哪一个槽函数,因此我们需要先显示指定出类型,然后再连接;如下代码所示;

QT学习日记 | 信号与槽_第36张图片

        尤其是红色框内的代码逻辑,函数指针那块代码可能会有些绕;

二、深剖信号与槽函数细节

1、宏函数 SIGNAL 与 SLOT

        前面我们说过connect第二个和第四个参数是 const char* 类型,需要用宏 SIGNAL 与 SLOT 将函数指针转换成 const char* 类型;可我们上述所有代码都没有使用这两个宏函数;这是因为在 Qt5 中,提供了这个函数的另一个版本;

QT学习日记 | 信号与槽_第37张图片

template

QMetaObject::Connection QObject::connect

(

const QObject *sender,

PointerToMemberFunction signal,
const QObject *receiver,

PointerToMemberFunction method,

Qt::ConnectionType type = Qt::AutoConnection

)

        这个PointerToMemberFunction是一个模板,这里涉及C++中萃取技术;因此在 Qt5 中,我们通常不用使用这两个宏函数;

2、为啥通过 ui 指针可以操作界面中拖动创建的控件

        首先我们来看 ui 指针到底是什么类型,这个指针声明在 widget.h 文件中;

QT学习日记 | 信号与槽_第38张图片

        它的类型是Ui命名空间里的Widget类指针;注意,我们这个 ui 指针所在类的类名也是Widget,而这个Widget并不在Ui这个命名空间内;那么Ui命名空间的Widget在哪里声明的呢?

        实际上,Qt 采用元编程的技术,所谓元编程,就是用代码生成代码,我们的 Qt 代码首先经过 qmake 编译器,编译出C++代码,再拿这个C++代码生成最后可执行程序;

        如何证明?

        我们编译程序后,会在同级目录下生成一个Build文件夹,这个文件夹就会存放qmake编译生成的代码;

QT学习日记 | 信号与槽_第39张图片

        以HelloWorld项目为例;我们点金对应生成的Build文件夹;

QT学习日记 | 信号与槽_第40张图片

        这个ui_widget.h 文件便是我们 设计师页面,生成的头文件,我们点进这个文件;

QT学习日记 | 信号与槽_第41张图片

        Ui命名空间内的Widget便是继承于Ui_Widget类;

        我们通过拖动控件的方式来构建一个Label标签;如下所示;

QT学习日记 | 信号与槽_第42张图片

        我们重新编译,接着来看Build文件夹下的 Ui_Widget.h 文件;

QT学习日记 | 信号与槽_第43张图片

        这时,我们的 Ui_Widget.h 文件多了一个QLable控件;同样的代码,我们给这个QLabel 改个名字;我们再观察这个文件;我们把这个QLabel控件的名字改为 label_1234;

QT学习日记 | 信号与槽_第44张图片

        我们观察 Ui_Widget.h 文件,发现QLabel标签的名字果然发生了改变;

QT学习日记 | 信号与槽_第45张图片

        综上所述,我们通过ui指针本质就是 Ui_Widget 类型的指针,而这个类是 widget.ui 文件经过qmake编译后,生成了一个Ui_Widget.h 的文件,而这个文件内有 Ui_Widget 这个类;

QT学习日记 | 信号与槽_第46张图片

        而我们 Widget.h 文件中声明的 ui 指针正是Ui命名空间中的 Widget 类,而该类中又有我们通过 Widget.ui 文件中拖动生成的各类控件;故我们可以通过 ui 指针来操作界面中拖动创建的控件;

你可能感兴趣的:(QT,学习)