【QT】信号与槽基础:手动连接的原理与实践

信号与槽的基本概念

信号(signals)

在Qt框架中,用户与控件的每次交互都会触发相应的事件,并产生对应的信号。例如,当用户点击按钮时,按钮会发出"按钮被按下"的信号。

在QT中要注意信号的三个要素:

1.信号源:哪个控件发出的信号。

2.信号类型:不同用户操作会触发不同信号。例如:按钮点击信号、窗口关闭信号。

本质:GUI程序的核心在于实现用户交互,需要准确识别并响应各类用户操作。

3.信号处理的方式:槽(slot)函数,后续只要信号发出了,qt会自动执行槽函数。

槽(slot)

Qt 控件天生具备接收信号的能力,且单个控件可以同时响应多个不同信号。当控件接收到特定信号时,就会执行对应的响应操作,这种响应机制在 Qt 中被称为"槽"。槽函数在做的本质上对未发生的事件写应对方案,事件发生了(信号发出)就执行先前写好的方案(自动调槽函数)。举个实际的例子:设置手机闹钟------你现在定义"早上7点响铃"(写槽函数),当时间到达7点时(信号发出),系统自动执行响铃操作(调用槽函数)。

槽函数本身是一种回调函数------------回调函数是一种编程模式,指将函数A作为参数传递给函数B,由函数B在特定条件满足时调用函数A。这种机制实现了"调用权"的转移。回调函数是编程中常用的机制,例如c++的仿函数,linux中的线程入口函数。

为了对信号和槽有更深刻的理解我们来画图:

connect函数

从上图中我们可以知道,如果不把信号和槽函数关联到一起,自然槽函数是不能在信号发出时被自动调用的。qt提供了connect函数,我们可以通过connect函数将信号和槽关联到一起。

QObject

connectQObject类提供的静态成员函数。由于Qt中所有类都继承自QObject,且遵循高内聚、低耦合的设计原则,子类可以自然地继承并使用父类的成员函数。QT中的继承关系如图:

connect

选中QObject按F1在文档中查询,我们会在成员函数这一栏看到connect。

cpp 复制代码
connect(const QObject *sender,//哪个控件发的信号
 const char *signal,//信号的类型
 const QObject * receiver ,//接收信号的对象,普通槽函数必须写,Lambda表达式不用写
 const char *method, //怎么处理信号
Qt::ConnectionType type = Qt::AutoConnection) const//第四个参数后面学到在解释

使用QT创建槽函数

接下来我们来使用connect函数连接信号和槽:

widget.cpp

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QPushButton>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    QPushButton* button = new QPushButton(this);
    button->setText("关闭");
    button->move(300,200);//设置坐标

    connect(button,&QPushButton::clicked,this,&Widget::close);
}

Widget::~Widget()
{
    delete ui;
}

其他代码就用QT自动生成的不变。

click

是一个槽函数,用于编程方式触发按钮点击,调用一下相当于点击一下按钮。

clicked

是一个信号,当按钮被实际点击时发出,相当于按钮已经被触发了。

close

close是QWidget内置的槽函数,Widget继承了QWidet的槽函数;调用close可以关闭窗口。

运行

运行之后出现一下界面:

点击button之后,界面销毁。

connect的要求

信号的类型必须是发送信号的控件的。这里的button类型是QPushbutton *那么第二个参数必须是QPushButton内置的信号(或者从父类继承的信号也行)。

QT提供了哪些信号与槽

qt中有非常多的槽函数,我们不可能全部记得,需要时在文档查询即可。

在文档中查询时找不到时,要找的可能在父类中。

close这个槽就在QWidget当中:

不同版本的类型处理

cpp 复制代码
connect(const QObject *sender,//哪个控件发的信号
 const char *signal,//信号的类型
 const QObject * receiver ,//接收信号的对象,普通槽函数必须写,Lambda表达式不用写
 const char *method, //怎么处理信号
Qt::ConnectionType type = Qt::AutoConnection) const//第四个参数后面学到在解释
cpp 复制代码
    connect(button,&QPushButton::clicked,this,&Widget::close);

仔细观察connect函数的声明我们会发现,我们传入的&QPushButton::clicked和&Widget::close的类型是函数指针,并不是声明中的const char *。

旧版本
cpp 复制代码
connect(button, SIGNAL(&QPushButton::clicked), this, SLOT(&Widget::close));

这是旧版 Qt 中 connect 函数的声明方式。在早期版本中,参数传递方式与现在有所不同:信号参数需要使用 SIGNAL 宏进行包装,槽函数参数则需要使用 SLOT 宏。此时,传入的函数指针会被转换为 char* 类型。

QT5以后

从 Qt 5 开始,这种写法得到了简化。不再需要使用 SIGNAL 和 SLOT 宏,connect 方法新增了重载版本。在重载版本中,第二个和第四个参数改为泛型参数,允许我们传入任意类型的函数指针。

connect函数具备参数校验功能,当参数不匹配时会触发错误提示。

在Qt5中,connect通过模板类型推导在编译阶段执行以下检查:

  1. 验证发送者是否为QObject派生类(确保支持信号槽机制);(只有QObject的派生类才支持信号与槽绑定)

  2. 检查信号函数签名合法性(确认是signals区域声明且参数类型匹配);

  3. 校验槽函数签名(确保参数类型和数量与信号兼容);

  4. 自动识别槽函数类型(普通成员函数、lambda表达式或函数指针),并适配相应的调用逻辑。

自定义槽函数

在QT5之前,自定义槽函数需要声明在类的public slots/protected slots/private slots区域中,并且必须配合SLOT宏使用。而QT5版本之后,自定义槽函数与普通成员函数已完全等同。接下来我们来自己定义槽函数;可以通过图形化的方式也可以纯代码。

纯代码

widget.h:

cpp 复制代码
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    void handleclicked();
private:
    Ui::Widget *ui;
};
#endif // WIDGET_H

选中handleclicked然后alt+enter就可以在.cpp文件中添加定义了。

widget.cpp:

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"
#include<QPushButton>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    QPushButton* button = new QPushButton(this);
    button->setText("改变标题");
    button->move(400,300);




    connect(button,&QPushButton::clicked,this,&Widget::handleclicked);




}

Widget::~Widget()
{
    delete ui;
}

void Widget::handleclicked()
{
    // 按下按钮, 修改窗口标题.
    this->setWindowTitle("按钮已经按下!");
}

运行:

点击按钮

图形化

在UI中拖出button,右键button,点击转到槽。这个窗口提供QPushbutton的所有信号,以及QPushbutton父类的信号。

widget.cpp

QT会自动生成槽函数

cpp 复制代码
#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
}

Widget::~Widget()
{
    delete ui;
}

void Widget::on_pushButton_clicked()
{
    
}
cpp 复制代码
void Widget::on_pushButton_clicked()
{
    this->setWindowTitle("按钮按下了!");
}

运行结果:

按下按钮,槽函数自动调用:

我们发现,图形化的方式并没有使用connect函数将信号与槽关联起来,这是为什么呢?QT中不仅可以通过connect关联信号与槽函数,还可以通过函数名的方式自动连接。

注意on_pushButton_clicked()这个生成的函数名on是固定前缀,pushButton是类的名字clicked是信号类型,只要函数名符合规则就能自动关联信号与槽。pushButton和clicked一起就确定了信号,槽函数就是我们正在写的on_pushButton_clicked(),QT会把它们自动关联。

相关推荐
傻乐u兔9 小时前
C语言初阶————结构体
c语言·开发语言
weixin_445054729 小时前
力扣热题52
开发语言·python
逑之9 小时前
C语言笔记2:C语言数据类型和变量
c语言·开发语言·笔记
何中应9 小时前
@Autowrited和@Resource注解的区别及使用场景
java·开发语言·spring boot·后端·spring
源代码•宸9 小时前
Golang语法进阶(Context)
开发语言·后端·算法·golang·context·withvalue·withcancel
一条咸鱼_SaltyFish9 小时前
[Day16] Bug 排查记录:若依框架二次开发中的经验与教训 contract-security-ruoyi
java·开发语言·经验分享·微服务·架构·bug·开源软件
源代码•宸9 小时前
Golang语法进阶(Sync、Select)
开发语言·经验分享·后端·算法·golang·select·pool
一勺菠萝丶9 小时前
Java 对接 PLC 实战:西门子 PLC 与永宏 PLC 通讯方式全面对比
java·开发语言·python
吴声子夜歌9 小时前
Java数据结构与算法——数论问题
java·开发语言