【Qt教程03】Qt中的信号和槽
- 一、信号和槽概述
- 二、标准信号槽使用
- 三、自定义信号槽
- [四、Qt的connect 和std::bind](#四、Qt的connect 和std::bind)
-
- 1、std::bind
- [2、Qt 的 connect](#2、Qt 的 connect)
- 3、补充完整上面的connect例子
原创作者:郑同学的笔记
原创地址:https://zhengjunxue.blog.csdn.net/article/details/136629598
qq技术交流群:921273910
- 信号和槽是 Qt 特有的消息传输机制,它能将相互独立的控件关联起来。
- 举个简单的例子,按钮和窗口本是两个独立的控件,点击按钮并不会对窗口造成任何影响。通过信号和槽机制,我们可以将按钮和窗口关联起来,实现"点击按钮会使窗口关闭"的效果。
一、信号和槽概述
在 Qt 中,用户和控件的每次交互过程称为一个事件,比如"用户点击按钮"是一个事件,"用户关闭窗口"也是一个事件。每个事件都会发出一个信号,
例如用户点击按钮会发出"按钮被点击"的信号,用户关闭窗口会发出"窗口被关闭"的信号。
Qt 中的所有控件都具有接收信号的能力,一个控件还可以接收多个不同的信号。对于接收到的每个信号,控件都会做出相应的响应动作。
例如,按钮所在的窗口接收到"按钮被点击"的信号后,会做出"关闭自己"的响应动作;再比如输入框自己接收到"输入框被点击"的信号后,会做出"显示闪烁的光标,等待用户输入数据"的响应动作。
在 Qt 中,对信号做出的响应动作就称为槽。
1、信号的本质
信号是由于用户对窗口或控件进行了某些操作,导致窗口或控件产生了某个特定事件,这时候Qt对应的窗口类会发出某个信号,以此对用户的挑选做出反应。
因此根据上述的描述我们得到一个结论 -- 信号的本质就是事件,比如:
- 按钮单击、双击
- 窗口刷新
- 鼠标移动、鼠标按下、鼠标释放
- 键盘输入
2、槽的本质
在Qt中槽函数是一类特殊的功能的函数,在编码过程中也可以作为类的普通成员函数来使用。之所以称之为槽函数是因为它们还有一个职责就是对Qt框架中产生的信号进行处理。
二、标准信号槽使用
每个信号都可以用函数来表示,称为信号函数;每个槽也可以用函数表示,称为槽函数。
例如,"按钮被按下"这个信号可以用 clicked() 函数表示,"窗口关闭"这个槽可以用 close() 函数表示
1、demo
创建一个不含 ui 文件的 Qt Widgets Application 项目,只保留 main.cpp 源文件,删除 mainwindow.h 和 mainwindow.cpp 文件。在 main.cpp 文件中编写如下代码:
cpp
//main.cpp
#include <QApplication>
#include <QWidget>
#include <QPushButton>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//添加窗口
QWidget widget;
//定义一个按钮,它位于 widget 窗口中
QPushButton But("按钮控件",&widget);
//设置按钮的位置和尺寸
But.setGeometry(100,100,100,50);
//信号与槽,实现当用户点击按钮时,widget 窗口最大化
QObject::connect(&But,&QPushButton::clicked,&widget,&QWidget::showMaximized);
//让 widget 窗口显示
widget.show();
return a.exec();
}
如上图所示,由于使用了 conect() 函数将 But 的 clicked() 信号函数和 widget 的 close() 槽函数关联起来,所以生成了"点击按钮后主窗口最大化"的效果。
2、信号函数
Qt 的各个控件类都提供了一些常用的信号函数和槽函数。例如 QPushButton 类提供了 4 个信号函数和 5 个 public slots 属性的槽函数,可以满足大部分场景的需要。
- 2.1 QPushButton 类的使用手册,如下图所示。
- 2.2 在 Contents 部分可以看到,QPushButton 类只提供了一些Public Slots属性的槽函数,没有提供信号函数。对于 QPushButton 类按钮,除了可以使用自己类提供的槽函数,还可以使用从父类继承过来的信号函数和槽函数。由图 2 可知,QPushButton 的父类是 QAbstractButton,点击 QAbstractButton 就可以直接跳转到此类的使用手册,如下图所示:
3、槽函数
本demo中使用的槽函数QWidget::showMaximized
4、connect()函数实现信号和槽
在Qt中信号和槽函数都是独立的个体,本身没有任何联系,但是由于某种特性需求我们可以将二者连接到一起。在Qt中我们需要使用QOjbect类中的connect函数进二者的关联。
cpp
//信号与槽,实现当用户点击按钮时,widget 窗口最大化
QObject::connect(&But,&QPushButton::clicked,&widget,&QWidget::showMaximized);
connect() 是 QObject 类中的一个静态成员函数,专门用来关联指定的信号函数和槽函数。
5、使用注意
connect()操作一般写在窗口的构造函数中, 相当于在事件产生之前在qt框架中先进行注册, 这样在程序运行过程中假设产生了按钮的点击事件, 框架就会调用信号接收者对象对应的槽函数了, 如果信号不产生, 槽函数也就一直不会被调用。
6、扩展
在 Qt5 版本之前,connect() 函数最常用的语法格式是:
cpp
QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection)
各个参数的含义分别是:
- sender:指定信号的发送者;
- signal:指定信号函数,信号函数必须用 SIGNAL() 宏括起来;
- reveiver:指定信号的接收者;
- method:指定接收信号的槽函数,槽函数必须用 SLOT() 宏括起来;
- type 用于指定关联方式,默认的关联方式为 Qt::AutoConnection,通常不需要手动设定。
用 connect() 函数将 But 按钮的 clicked() 信号函数和 widget 窗口的 close() 槽函数关联起来,实现代码如下:
cpp
connect(&But, SIGNAL(clicked()), &widget, SLOT(close()));
如此就实现了"按下按钮会关闭窗口"的功能。
Qt5 版本中,connect() 函数引入了新的用法,常用的语法格式是:
cpp
QObject::connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type = Qt::AutoConnection)
和旧版本相比,新版的 connect() 函数改进了指定信号函数和槽函数的方式,不再使用 SIGNAL() 和 SLOT() 宏。
例如,用新版 connect() 函数关联 But 按钮的 clicked() 信号函数和 widget 窗口的 close() 槽函数,实现代码为:
cpp
connect(&But, &QPushButton::clicked, &widget, &QWidget::close);
可以看到,新版 connect() 函数指定信号函数和槽函数的语法格式是&+函数所在类+函数名。
一个 connect() 函数只能关联一个信号函数和一个槽函数,程序中可以包含多个 connect() 函数,能实现以下几种效果:
关联多个信号函数和多个槽函数;
一个信号函数可以关联多个槽函数,当信号发出时,与之关联的槽函数会一个接一个地执行,但它们执行的顺序是随机的,无法人为指定哪个先执行、哪个后执行;
多个信号函数可以关联同一个槽函数,无论哪个信号发出,槽函数都会执行。
三、自定义信号槽
Qt框架提供的信号槽在某些特定场景下是无法满足我们的项目需求的,因此我们还设计自己需要的的信号和槽,同样还是使用connect()对自定义的信号槽进行连接。
如果想要在QT类中自定义信号槽, 需要满足一些条件, 并且有些事项也需要注意:
要编写新的类并且让其继承Qt的某些标准类
这个新的子类必须从QObject类或者是QObject子类进行派生
在定义类的头文件中加入 Q_OBJECT 宏
1、demo
cpp
//main.cpp
#include <QApplication>
#include <QWidget>
#include <QDebug>
class MyWidget:public QWidget{
//Q_OBJECT 是一个宏,添加它才能正常使用 Qt 的信号和槽机制
Q_OBJECT
//信号函数
signals:
void MySignal(QString mess1,QString mess2);
public:
//发射信号的函数
void emitSignal(){
emit MySignal(message1,message2);
}
//普通类成员函数
void recSlot1(QString mess){
qDebug() << "执行 recSlot1() 成员函数,输出" << mess;
}
//槽函数
public slots:
void recSlot2(QString mess1,QString mess2){
qDebug() << "执行 recSlot2() 槽函数,输出"<< mess1 << " " << mess2;
}
public:
QString message1;
QString message2;
};
//全局函数
void recSlot3(){
qDebug() << "执行 recSlot3() 全局函数";
}
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//创建主窗口
MyWidget mywidget;
mywidget.message1 = "Qt 教程";
mywidget.message2 = "槽函数";
//类的成员函数作为槽函数
QObject::connect(&mywidget,&MyWidget::MySignal,&mywidget,&MyWidget::recSlot1);
//信号函数和槽函数相连接
QObject::connect(&mywidget,&MyWidget::MySignal,&mywidget,&MyWidget::recSlot2);
//全局函数作为槽函数
QObject::connect(&mywidget,&MyWidget::MySignal,&recSlot3);
mywidget.show();
//发射 Signal 信号
mywidget.emitSignal();
return a.exec();
}
//MyWidget类的定义应该放到 .h 文件中,本例中将其写到 main.cpp 中,程序最后需要添加 #include "当前源文件名.moc" 语句,否则无法通过编译。
#include "main.moc"
执行程序,会弹出一个 myWidget 空白窗口,同时输出以下信息:
执行 recSlot1() 成员函数,输出 "Qt 教程"
执行 recSlot2() 槽函数,输出 "Qt 教程" "槽函数"
执行 recSlot3() 全局函数
recSlot1() 是 MyWidget 类内部的 public 成员函数,可以当做槽函数使用;recSlot2() 位于 MyWidget 类的内部,修饰它的关键字是 public slots。slots 和 emit 一样,是 Qt 扩展的一个关键字,专门用来修饰槽函数。也就是说,recSlot2() 是 MyWidget 类中的槽函数。
recSlot3() 是全局函数,可以当做槽函数使用。
slots 关键字可以和 public、protected、private 搭配使用,它们的区别是:public slots:该关键字修饰的槽函数,既可以在当前类及其子类的成员函数中调用,也可以在类外部的其它函数(比如 main() 函数)中调用;
protected slots:该关键字修饰的槽函数,仅允许在当前类及其子类的成员函数内调用,不能在类外部的其它函数内调用;
private slots:该关键字修饰的槽函数,只允许在当前类的成员函数内调用,不能在子类中调用,也不能在类外部的其它函数内调用。
2、自定义信号函数
在Qt中信号的本质是事件, 但是在框架中也是以函数的形式存在的, 只不过信号对应的函数只有声明, 没有定义。如果Qt中的标准信号不能满足我们的需求,可以在程序中进行信号的自定义,当自定义信号对应的事件产生之后,认为的将这个信号发射出去即可(其实就是调用一下这个信号函数)。
下边给大家阐述一下, 自定义信号的要求和注意事项:
-
- 信号是类的成员函数
-
- 返回值必须是 void 类型
-
- 参数可以随意指定, 信号也支持重载
-
- 信号需要使用 signals 关键字进行声明, 使用方法类似于public等关键字
-
- 信号函数只需要声明, 不需要定义(没有函数体实现)
-
- 在程序中发射自定义信号: 发送信号的本质就是调用信号函数
- 习惯性在信号函数前加关键字: emit, 但是可以省略不写
- emit只是显示的声明一下信号要被发射了, 没有特殊含义
- 底层 emit == #define emit
3、自定义槽函数
槽函数就是信号的处理动作,在Qt中槽函数可以作为普通的成员函数来使用。如果标准槽函数提供的功能满足不了需求,可以自己定义槽函数进行某些特殊功能的实现。自定义槽函数和自定义的普通函数写法是一样的。
-
- 返回值必须是 void 类型
-
- 槽也是函数, 因此也支持重载
-
- 槽函数的参数个数只能比信号函数少,不能比信号函数多;
-
- 槽函数的参数是用来接收信号传递的数据的, 信号传递的数据就是信号的参数
Qt中槽函数的类型是多样的
- Qt中的槽函数可以是类的成员函数、全局函数、静态函数、Lambda表达式(匿名函数)
- 槽函数的参数是用来接收信号传递的数据的, 信号传递的数据就是信号的参数
-
- 槽函数可以使用关键字进行声明: slots (Qt5中slots可以省略不写)
- public slots:
- private slots: --> 这样的槽函数不能在类外部被调用
- protected slots: --> 这样的槽函数不能在类外部被调用
四、Qt的connect 和std::bind
std::bind 和 Qt 的 connect 功能虽然都涉及到函数的调用机制,但它们服务于不同的上下文和目的。下面将分别介绍它们的定义、用途、区别和联系。
1、std::bind
std::bind 是 C++11 引入的一个函数适配器,它位于 头文件中。std::bind 的主要作用是将可调用对象与其参数绑定起来,形成一个新的可调用对象。这对于调整函数调用的参数顺序、固定某些参数值或者适配不同的函数调用接口非常有用。
例如,如果你有一个接受两个参数的函数 f(x, y),你可以使用 std::bind 将 y 固定为一个特定值,创建一个新的只需要一个参数的函数。
cpp
#include <functional>
#include <iostream>
void f(int a, int b) {
std::cout << a + b << std::endl;
}
int main() {
auto new_func = std::bind(f, std::placeholders::_1, 10);
new_func(5); // 相当于调用 f(5, 10)
}
2、Qt 的 connect
Qt 的 connect 方法是信号与槽(Signals & Slots)机制的核心,是 Qt 特有的一种用于对象间通信的机制。在 Qt 应用程序中,如果一个对象需要知道另一个对象的状态变化或事件发生,可以通过 connect 将一个对象的信号与另一个对象的槽函数连接起来。当信号被发射时,与之相连的槽函数会被自动调用。
cpp
// 假设有一个按钮(QPushButton)和一个标签(QLabel)
QPushButton *button = new QPushButton("Press me");
QLabel *label = new QLabel;
// 使用 connect 连接按钮的 clicked 信号与标签的 setText 槽
QObject::connect(button, &QPushButton::clicked, [=](){
label->setText("Button was pressed");
});
-
区别
用途和上下文:std::bind 主要用于函数适配,即调整函数参数、固定参数等,是标准 C++ 的一部分;而 Qt 的 connect 用于实现对象间的通信,是 Qt 框架特有的功能。
工作机制:std::bind 通过生成新的可调用对象来适配函数调用;connect 则是通过信号和槽机制,在对象间建立动态的通信链接。
适用范围:std::bind 更加通用,可以用在任何 C++ 程序中;connect 只适用于 Qt 应用程序开发。
-
联系
尽管 std::bind 和 Qt 的 connect 服务于不同的目的,它们都与函数调用的灵活性和动态绑定有关。在某些情况下,它们可以互相补充。例如,你可能会使用 std::bind 来适配一个槽函数的签名,以便它可以被 Qt 的 connect 所连接。
3、补充完整上面的connect例子
cpp
#include <QApplication>
#include <QWidget>
#include <QPushButton>
#include <QLabel>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
//添加窗口
QWidget widget;
// 假设有一个按钮(QPushButton)和一个标签(QLabel)
QPushButton *button = new QPushButton("Press me",&widget);
//设置按钮的位置和尺寸
button->setGeometry(100,200,100,50);
QLabel *label = new QLabel("new QLabel",&widget);
label->setGeometry(50,50,200,50);
// 使用 connect 连接按钮的 clicked 信号与标签的 setText 槽
QObject::connect(button, &QPushButton::clicked, [=](){
label->setText("Button was pressed");
});
//让 widget 窗口显示
widget.show();
return a.exec();
}
输出
参考