【Qt教程03】Qt中的信号和槽

【Qt教程03】Qt中的信号和槽

原创作者:郑同学的笔记
原创地址: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中的标准信号不能满足我们的需求,可以在程序中进行信号的自定义,当自定义信号对应的事件产生之后,认为的将这个信号发射出去即可(其实就是调用一下这个信号函数)。

下边给大家阐述一下, 自定义信号的要求和注意事项:

    1. 信号是类的成员函数
    1. 返回值必须是 void 类型
    1. 参数可以随意指定, 信号也支持重载
    1. 信号需要使用 signals 关键字进行声明, 使用方法类似于public等关键字
    1. 信号函数只需要声明, 不需要定义(没有函数体实现)
    1. 在程序中发射自定义信号: 发送信号的本质就是调用信号函数
    • 习惯性在信号函数前加关键字: emit, 但是可以省略不写
    • emit只是显示的声明一下信号要被发射了, 没有特殊含义
    • 底层 emit == #define emit

3、自定义槽函数

槽函数就是信号的处理动作,在Qt中槽函数可以作为普通的成员函数来使用。如果标准槽函数提供的功能满足不了需求,可以自己定义槽函数进行某些特殊功能的实现。自定义槽函数和自定义的普通函数写法是一样的。

    1. 返回值必须是 void 类型
    1. 槽也是函数, 因此也支持重载
    1. 槽函数的参数个数只能比信号函数少,不能比信号函数多;
    1. 槽函数的参数是用来接收信号传递的数据的, 信号传递的数据就是信号的参数
      Qt中槽函数的类型是多样的
    • Qt中的槽函数可以是类的成员函数、全局函数、静态函数、Lambda表达式(匿名函数)
    1. 槽函数可以使用关键字进行声明: 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();
}

输出

参考

1.爱编程的大丙------Qt 教程

2.C语言中文网------Qt 教程

相关推荐
清灵xmf3 分钟前
为什么 Vue3 封装 Table 组件丢失 expose 方法呢?
开发语言·前端·javascript·封装·eltable
神仙别闹14 分钟前
基于JAVA实现的(GUI)坦克大战游戏
java·开发语言·游戏
凡人的AI工具箱24 分钟前
15分钟学 Go 第 54 天 :项目总结与经验分享
开发语言·人工智能·后端·算法·golang
小春学渗透27 分钟前
DAY110代码审计-PHP框架开发篇&ThinkPHP&版本缺陷&不安全写法&路由访问&利用链
开发语言·安全·web安全·php
奈葵29 分钟前
C语言字符函数和字符串函数
c语言·开发语言
OKkankan36 分钟前
单链表算法题(数据结构)
c语言·数据结构·数据库·c++·算法
robin_suli40 分钟前
Java多线程八股(一), 锁策略,synchronized锁策略详解
java·开发语言·八股
手握风云-41 分钟前
零基础Java第十八期:图书管理系统
java·开发语言
ZZZ_O^O1 小时前
动态规划-背包问题——[模版]完全背包问题
c++·学习·算法·leetcode·动态规划
斗-匕1 小时前
Java 语言的强大特性
java·开发语言·python