【QT】Qt信号与槽机制详解信号和槽的本质自定义信号和槽带参数的信号和槽
文章目录
- 前言
- 一、信号的本质
- 二、槽的本质
- 三、 信号和槽的使⽤
- 3.1 连接信号和槽
- 四、使用步骤
- 4.1 通过QtCreator⽣成信号槽代码
- 五、 ⾃定义信号和槽
- 5.1 ⽰例1:信号和槽函数初步使用
- 5.2 ⽰例2 两个类使用
- 5.3 示例3 按钮使用触发信号
- 六、 带参数的信号和槽
- 6.1 ⽰例1:重载信号槽
- 6.2 ⽰例2:信号槽参数列表匹配规则
- 6.3 ⽰例3:信号的参数个数可以多于槽函数的参数个数,但是槽的参数个数不能多于信号参数个数
- 🚩总结
前言
在Qt中,⽤⼾和控件的每次交互过程称为⼀个事件。⽐如"⽤⼾点击按钮"是⼀个事件,"⽤⼾关闭窗⼝"也是⼀个事件。每个事件都会发出⼀个信号,例如⽤⼾点击按钮会发出"按钮被点击"的信号,⽤⼾关闭窗⼝会发出"窗⼝被关闭"的信号。
Qt 中的所有控件都具有接收信号的能⼒,⼀个控件还可以接收多个不同的信号。对于接收到的每个信号,控件都会做出相应的响应动作。例如,按钮所在的窗⼝接收到"按钮被点击"的信号后,会做出"关闭⾃⼰"的响应动作;再⽐如输⼊框⾃⼰接收到"输⼊框被点击"的信号后,会做出"显⽰闪烁的
光标,等待⽤⼾输⼊数据"的响应动作。在Qt中,对信号做出的响应动作就称之为槽。
信号和槽是Qt特有的消息传输机制,它能将相互独⽴的控件关联起来。⽐如,"按钮"和"窗⼝"本⾝是两个独⽴的控件,点击"按钮"并不会对"窗⼝"造成任何影响。通过信号和槽机制,可以将"按钮"和"窗⼝"关联起来,实现"点击按钮会使窗⼝关闭"的效果
一、信号的本质
信号是由于⽤⼾对窗⼝或控件进⾏了某些操作,导致窗⼝或控件产⽣了某个特定事件,这时Qt对
应的窗⼝类会发出某个信号,以此对⽤⼾的操作做出反应。因此,信号的本质就是事件。如:
- 按钮单击、双击
- 窗⼝刷新
- ⿏标移动、⿏标按下、⿏标释放
- 键盘输⼊
那么在Qt中信号是通过什么形式呈现给使⽤者的呢? - 我们对哪个窗⼝进⾏操作,哪个窗⼝就可以捕捉到这些被触发的事件。
- 对于使⽤者来说触发了⼀个事件我们就可以得到Qt框架给我们发出的某个特定信号。
- **信号的呈现形式就是函数,**也就是说某个事件产⽣了,Qt框架就会调⽤某个对应的信号函数,通知使⽤者。
在Qt中信号的发出者是某个实例化的类对象。
二、槽的本质
**槽(Slot)就是对信号响应的函数。**槽就是⼀个函数,与⼀般的C++函数是⼀样的,可以定义在类的任何位置(public、protected或private),可以具有任何参数,可以被重载,也可以被直接调⽤(但是不能有默认参数)。槽函数与⼀般的函数不同的是:槽函数可以与⼀个信号关联,当信号被发射时,关联的槽函数被⾃动执⾏。
说明
(1)信号和槽机制底层是通过函数间的相互调⽤实现的。每个信号都可以⽤函数来表⽰,称为信号函数;**每个槽也可以⽤函数表⽰,称为槽函数。**例如:"按钮被按下"这个信号可以⽤clicked()函数表⽰,"窗⼝关闭"这个槽可以⽤close()函数表⽰,假如使⽤信号和槽机制
实现:"点击按钮会关闭窗⼝"的功能,其实就是clicked()函数调⽤close()函数的效果。
(2)信号函数和槽函数通常位于某个类中,和普通的成员函数相⽐,它们的特别之处在于:
- 信号函数⽤ signals关键字修饰,槽函数⽤
publicslots
、protectedslots
或者privateslots
修饰。signals
和slots
是Qt
在C++
的基础上扩展的关键字,专⻔⽤来指明信号函数和槽函数;
信号函数只需要声明,不需要定义(实现),⽽槽函数需要定义(实现)。
信号函数的定义是
Qt
⾃动在编译程序之前⽣成的.编写Qt应⽤程序的程序猿⽆需关注
这种⾃动⽣成代码的机制称为元编程(MetaProgramming).这种操作在很多场景中都能⻅到.
三、 信号和槽的使⽤
3.1 连接信号和槽
代码如下(示例):
在Qt
中,QObject
类提供了⼀个静态成员函数connect()
,该函数专⻔⽤来关联指定的信号函数和槽
函数。
关于
QObject
QObject
是Qt内置的⽗类.Qt中提供的很多类都是直接或者间接继承⾃QObject
.
这⼀点的设定和Java是⾮常相似的.
connect()
函数原型:
// 普通成员函数作为槽
template<typename Func1, typename Func2>
QMetaObject::Connection connect(const QObject *sender, // 信号发送对象Func1 signal, // 发送的信号(函数指针)const QObject *receiver, // 信号接收对象Func2 slot, // 接收的槽函数(函数指针)Qt::ConnectionType type = Qt::AutoConnection // 连接类型
);// 静态成员函数作为槽
template<typename Func1, typename Func2>
QMetaObject::Connection connect(const QObject *sender, Func1 signal, Func2 slot, Qt::ConnectionType type = Qt::AutoConnection
);
connect (const Q0bject *sender, 、//sender:信号的发送者;const char * signal , //signal:发送的信号(信号函数);const Q0bject * receiver , //receiver:信号的接收者;const char *method , //method:接收信号的槽函数;Qt: :ConnectionType type = Qt: :AutoConnection )type:⽤于指定关联⽅式,默认的关联⽅式为Qt::AutoConnection,通常不需要⼿动设定。
**代码示例:**在窗口中设置一个按钮,当点击"按钮"时关闭"窗口".
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{QPushButton * btn = new QPushButton("关闭按钮");//创建按钮resize(800, 600); //调整窗口大小//关联信号和槽,实现点击“按钮”, 关闭“窗口”connect(btn, &QPushButton::clicked, this, &QWidget::close);}
2.2 查看内置信号和槽
系统⾃带的信号和槽通常是通过"**Qt帮助⽂档"**来查询。
如上述⽰例,要查询"按钮"的信号,在帮助⽂档中输⼊:QPushButton,
- ⾸先可以在"
Contents
"中寻找关键字signals
,
如果没有找到,继续去⽗类中查找.因此我们去他的⽗类QAbstractButton
中继续查找关键字signals
,
这⾥的clicked()
就是要找的信号。槽函数的寻找⽅式和信号⼀样,只不过它的关键字是slot
。
四、使用步骤
4.1 通过QtCreator⽣成信号槽代码
Qt Creator 可以快速帮助我们⽣成信号槽相关的代码.
**代码⽰例:**在窗⼝中设置⼀个按钮,当点击"按钮"时关闭"窗⼝".
注意:创建时要⽣成UI设计⽂件;
- 双击widget.ui⽂件,进⼊UI设计界⾯;
-
在U设计窗口中拖入一个"按钮",并且修改"按钮"的名称及字体大小等;
-
可视化生成槽函数;
当单击"转到槽…"之后,出现如下界面:对于按钮来说,当点击时发送的信号是: clicked()
,所以此处选择: clicked()
对于普通按钮来说,使用
clicked
信号即可.clicked (bool)
没有意义的.具有特殊状态的按钮(比如复选按钮)才会用到clicked ( bool)]
.
- ⾃动⽣成槽函数原型框架;
(1)在"widget.h"头文件中自动添加槽函数的声明;
说明:
⾃动⽣成槽函数的名称有⼀定的规则。槽函数的命名规则为:on_XXX_SSS,其中:
1、以"on
"开头,中间使⽤下划线连接起来;
2、"XXX
"表⽰的是对象名(控件的objectName
属性)。
3、"SSS
"表⽰的是对应的信号。
如:“on_pushButton_clicked()
”,pushButton
代表的是对象名,clicked
是对应的信号。
按照这种命名⻛格定义的槽函数,就会被Qt⾃动的和对应的信号进⾏连接.
但是咱们⽇常写代码的时候,除⾮是IDE⾃动⽣成,否则最好还是不要依赖命名规则,⽽是显式使⽤connect更好.
⼀⽅⾯显式connect可以更清晰直观的描述信号和槽的连接关系.
另⼀⽅⾯也防⽌信号或者槽的名字拼写错误导致连接失效.
(当然,是配置⼤于约定,还是约定⼤于配置,哪种更好,这样的话题业界尚存在争议.此处我个
⼈还是更建议优先考虑显式connect)
(2)在"widget.cpp"中⾃动⽣成槽函数定义.
6、在槽函数函数定义中添加要实现的功能.实现关闭窗口的效果.
五、 ⾃定义信号和槽
基本语法
在Qt中,允许⾃定义信号的发送⽅以及接收⽅,即可以⾃定义信号函数和槽函数。但是对于⾃定义的信号函数和槽函数有⼀定的书写规范。
1、⾃定义信号函数书写规范
(1)⾃定义信号函数必须写到"signals"下;
(2)返回值为void,只需要声明,不需要实现;
(3)可以有参数,也可以发⽣重载;
2、⾃定义槽函数书写规范
(1)早期的Qt版本要求槽函数必须写到"publicslots"下,但是现在⾼级版本的Qt允许写到类的"public" 作⽤域中或者全局下;
(2)返回值为void,需要声明,也需要实现;
(3)可以有参数,可以发⽣重载
3、发送信号
使⽤"emit
"关键字发送信号。"emit
"是⼀个空的宏。"emit
"其实是可选的,没有什么含义,只是为了提醒开发⼈员。
5.1 ⽰例1:信号和槽函数初步使用
1、在widget.h
中声明⾃定义的信号和槽,如图所⽰;
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();void EmitSignal();signals:void MySignal(); //信号声明public slots:void MySlots(); //槽声明private:Ui::Widget *ui;
};
#endif // WIDGET_H
2、在widget.cpp中实现槽函数,并且关联信号和槽
注意:图中的①和②的顺序不能颠倒。
原因是,⾸先关联信号和槽,⼀旦检测到信号发射之后就会⽴⻢执⾏关联的槽函数。反之,若先发射
信号,此时还没有关联槽函数,当信号发射之后槽函数不会响应.
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{//关联自定义的信号和槽connect(this, &Widget::MySignal, this, &Widget::MySlots);//发射信号EmitSignal();ui->setupUi(this);
}void Widget::MySlots()
{qDebug() <<"我的信号和槽";
}void Widget::EmitSignal()
{emit MySignal();
}Widget::~Widget()
{delete ui;
}
5.2 ⽰例2 两个类使用
⽰例2:当⽼师说"上课了",学⽣们就"回到座位,开始学习"
1、在源文件中新建两个类,一个是老师类,一个是学生类;首先选中项目名称,鼠标右键----->“addnew…”
点击"addnew…"之后,出现如下界⾯:
选择"choose
"出现如下界⾯.
注意:
在Qt中新建类时,要选择新建类的⽗类.
显然,当前项⽬中还没啥类适合做新类的⽗类,同时新的类也不是⼀个"窗⼝"或者"控件".这种情况⼀般选择QObject作为基类
这样做的好处是这个新类的对象可以搭配Qt的对象树机制.便于对象的正确释放
选择"下一步",出现如下界面:
对于"学⽣类"以上述同样的⽅式进⾏添加,添加完成之后,项⽬⽬录新增⽂件如下:
在teacher.h中声明信号函数:
#ifndef TEACHER_H
#define TEACHER_H#include <QObject>class Teacher : public QObject
{Q_OBJECT
public:explicit Teacher(QObject *parent = nullptr);signals:void MySignal(); //自定义信号函数声明
};#endif // TEACHER_H
在student.h中声明槽函数:
#ifndef STUDENT_H
#define STUDENT_H#include <QObject>class Student : public QObject
{Q_OBJECT
public:explicit Student(QObject *parent = nullptr);signals:public slots:void StartStudy(); //自定义槽函数
};#endif // STUDENT_H
在widget.h中实例化"⽼师类对象"和"学⽣类对象";
#ifndef WIDGET_H
#define WIDGET_H#include <QWidget>
#include "student.h"
#include "teacher.h"QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACEclass Widget : public QWidget
{Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();void EmitSignal();
private:Teacher *tch;Student *stu;Ui::Widget *ui;
};
#endif // WIDGET_H
在student.cpp中实现槽函数:
#include "student.h"
#include <QDebug>Student::Student(QObject *parent): QObject{parent}
{}void Student::StartStudy()
{qDebug() << "回到座位,继续学习";
}
在widget.cpp中连接⾃定义信号和槽;
#include "widget.h"
#include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{this->tch = new Teacher(this);this->stu = new Student(this);connect(tch, &Teacher::MySignal, stu, &Student::StartStudy);EmitSignal();ui->setupUi(this);
}void Widget::EmitSignal()
{emit tch->MySignal();
}Widget::~Widget()
{delete ui;
}
运⾏结果如下图⽰:
5.3 示例3 按钮使用触发信号
示例3:老师点击"按钮"触发学生上课;
#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{this->tch = new Teacher(this);this->stu = new Student(this);QPushButton * btn = new QPushButton("上课了", this);resize(800, 600);btn->move(100, 100);connect(tch, &Teacher::MySignal, stu, &Student::StartStudy);connect(btn, &QPushButton::clicked, tch, &Teacher::MySignal);EmitSignal();ui->setupUi(this);
}void Widget::EmitSignal()
{emit tch->MySignal();
}Widget::~Widget()
{delete ui;
}
运⾏结果如下图⽰:
六、 带参数的信号和槽
Qt 的信号和槽也⽀持带有参数,同时也可以⽀持重载.
此处我们要求,信号函数的参数列表要和对应连接的槽函数参数列表⼀致
此时信号触发,调⽤到槽函数的时候,信号函数中的实参就能够被传递到槽函数的形参当中.
通过这样的机制,就可以让信号给槽传递数据了.
6.1 ⽰例1:重载信号槽
(1)在"widget.h"头⽂件中声明重载的信号函数以及重载的槽函数;如下图所⽰:
(2)在"Widget.cpp"⽂件实现重载槽函数以及连接信号和槽。
注意:在定义函数指针时要指明函数指针的作⽤域。
(3)执⾏结果如下图所⽰
6.2 ⽰例2:信号槽参数列表匹配规则
1、在"widget.h"头⽂件中声明信号和槽函数;
2、在"widget.cpp"⽂件中实现槽函数以及连接信号和槽;
其实信号的参数个数可以多于槽函数的参数个数,但是槽的参数个数不能多于信号参数个数.
但是实际开发中最好还是保持参数个数也能匹配⼀致.
6.3 ⽰例3:信号的参数个数可以多于槽函数的参数个数,但是槽的参数个数不能多于信号参数个数
1、在"widget.h"头⽂件中声明信号和槽函数;
2、在"widget.cpp"⽂件中实现槽函数以及连接信号和槽;