深度剖析《Qt信号与槽机制》

深度剖析《Qt信号与槽机制》

《Qt高级开发工程师》岗位必备技术栈

----【基础知识入门】

每节课有对应的录播视频、课堂源码及全套学习资料(需要加天狼老师的QQ:726920220)。


信号和槽是 Qt 特有的消息传输机制,它能将相互独立的控件关联起来。

举个简单的例子,按钮和窗口本是两个独立的控件,点击按钮并不会对窗口造成任何影响。通过信号和槽机制,我们可以将按钮和窗口关联起来,实现“点击按钮会使窗口关闭”的效果。

一、信号和槽

在 Qt 中,用户和控件的每次交互过程称为一个事件,比如“用户点击按钮”是一个事件,“用户关闭窗口”也是一个事件。每个事件都会发出一个信号,例如用户点击按钮会发出“按钮被点击”的信号,用户关闭窗口会发出“窗口被关闭”的信号。

Qt 中的所有控件都具有接收信号的能力,一个控件还可以接收多个不同的信号。对于接收到的每个信号,控件都会做出相应的响应动作。例如,按钮所在的窗口接收到“按钮被点击”的信号后,会做出“关闭自己”的响应动作;再比如输入框自己接收到“输入框被点击”的信号后,会做出“显示闪烁的光标,等待用户输入数据”的响应动作。在 Qt 中,对信号做出的响应动作就称为槽。

信号和槽机制底层是通过函数间的相互调用实现的。每个信号都可以用函数来表示,称为信号函数;每个槽也可以用函数表示,称为槽函数。例如,“按钮被按下”这个信号可以用 clicked() 函数表示,“窗口关闭”这个槽可以用 close() 函数表示,信号和槽机制实现“点击按钮会关闭窗口”的功能,其实就是 clicked() 函数调用 close() 函数的效果。

信号函数和槽函数通常位于某个类中,和普通的成员函数相比,它们的特别之处在于:

信号函数用 signals 关键字修饰,槽函数用 public slots、protected slots 或者 private slots 修饰。signals 和 slots 是 Qt 在 C++ 的基础上扩展的关键字,专门用来指明信号函数和槽函数;

信号函数只需要声明,不需要定义(实现),而槽函数需要定义(实现)。

为了提高程序员的开发效率,Qt 的各个控件类都提供了一些常用的信号函数和槽函数。例如 QPushButton 类提供了 4 个信号函数和 5 个 public slots 属性的槽函数,可以满足大部分场景的需要。

实际开发中,可以使用 Qt 提供的信号函数和槽函数,也可以根据需要自定义信号函数和槽函数,我们会做详细介绍。

Qt Creator 提供了很强大的 Qt GUI 开发手册,很容易就能查到某个控件类中包含哪些信号函数和槽函数。

举个例子,查看 QPushButton 类中信号函数和槽函数的过程是:

在程序中引入头文件,双击选中“QPushButton”并按 “Fn+F1” 快捷键,就会弹出 QPushButton 类的使用手册,如下图所示。

在 Contents 部分可以看到,QPushButton 类只提供了一些Public Slots属性的槽函数,没有提供信号函数。对于 QPushButton 类按钮,除了可以使用自己类提供的槽函数,还可以使用从父类继承过来的信号函数和槽函数。由上图可知,QPushButton 的父类是 QAbstractButton,点击 QAbstractButton 就可以直接跳转到此类的使用手册,如下图所示:

QAbstractButton 类中既有 Signals 信号函数,也有 Public Slots 槽函数,这里不再一一列举,感兴趣的读者可以自行查看。

注意,并非所有的控件之间都能通过信号和槽关联起来,信号和槽机制只适用于满足以下条件的控件:

控件类必须直接或者间接继承自 QObject 类。Qt 提供的控件类都满足这一条件,这里提供一张 Qt常用类的继承关系的高清图片,感兴趣的读者可以简单了解一下。

控件类中必须包含 private 属性的 Q_OBJECT 宏。

将某个信号函数和某个槽函数关联起来,需要借助 QObject 类提供的

二、connect()函数

connect()函数实现信号和槽

connect() 是 QObject 类中的一个静态成员函数,专门用来关联指定的信号函数和槽函数。

关联某个信号函数和槽函数,需要搞清楚以下 4 个问题:

信号发送者是谁?

哪个是信号函数?

信号的接收者是谁?

哪个是接收信号的槽函数?

仍以实现“按下按钮后窗口关闭”为例,先创建一个窗口和一个按钮,如下所示:

QWidget widget;

//定义一个按钮,它位于 widget 窗口中

QPushButton But("按钮控件",&widget);

信号发送者是 But 按钮对象,要发送的信号是“按钮被点击”,可以用 QPushButton 类提供的 clicked() 信号函数表示;信号的接收者是 widget 主窗口对象,“窗口关闭”作为信号对应的槽,可以用 QWidget 类提供的 close() 函数表示。

在 Qt5 版本之前,connect() 函数最常用的语法格式是:

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() 槽函数关联起来,实现代码如下:

connect(&But, SIGNAL(clicked()), &widget, SLOT(close()));

如此就实现了“按下按钮会关闭窗口”的功能。

Qt5 版本中,connect() 函数引入了新的用法,常用的语法格式是:

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() 槽函数,实现代码为:

connect(&But, &QPushButton::clicked, &widget, &QWidget::close);

可以看到,新版 connect() 函数指定信号函数和槽函数的语法格式是&+函数所在类+函数名。

一个 connect() 函数只能关联一个信号函数和一个槽函数,程序中可以包含多个 connect() 函数,能实现以下几种效果:

关联多个信号函数和多个槽函数;

一个信号函数可以关联多个槽函数,当信号发出时,与之关联的槽函数会一个接一个地执行,但它们执行的顺序是随机的,无法人为指定哪个先执行、哪个后执行;

多个信号函数可以关联同一个槽函数,无论哪个信号发出,槽函数都会执行。

此外,connect() 函数的 method 参数还可以指定一个信号函数,也就是说,信号之间也可以相互关联,这样当信号发出时,会随之发出另一个信号。

三、信号和槽机制【实战案例分析】

创建一个不含 ui 文件的 Qt Widgets Application 项目,在main.cpp源文件添加如下代码。在 main.cpp 文件中编写如下代码:

#include "mainwindow.h"
#include <QApplication>
#pragma execution_character_set("utf-8")
#include <QWidget>
#include <QPushButton>
int main(int argc, char *argv[])
    QApplication a(argc, argv);
    //添加窗口
    QWidget widget;
    // 设置窗口尺寸
    widget.resize(500,300);
    // 设置标题名称
    widget.setWindowTitle("第一个Qt程序设计");
    //定义一个按钮,它位于 widget 窗口中
    QPushButton qbutton("命令按钮控件",&widget);
    //设置按钮的位置和尺寸
    qbutton.setGeometry(10,10,150,30);
    //信号与槽,实现当用户点击按钮时,widget 窗口关闭
    QObject::connect(&qbutton,&QPushButton::clicked,&widget,&QWidget::close);
    //让 widget 窗口显示
    widget.show();
    return a.exec();
}

运行结果为:

四、信号与槽拓展

一个信号可以连接多个槽函数, 发送一个信号有多个处理动作:

  • 需要写多个connect连接
  • 信号的接收者可以是一个对象, 也可以是多个对象

一个槽函数可以连接多个信号, 多个不同的信号, 处理动作是相同的:

  • 写多个connect就可以

信号可以连接信号:

信号接收者可以不出来接收的信号, 继续发出新的信号 -> 传递了数据, 并没有进行处理:

【程序源码】

头文件:

#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
class MainWindow : public QMainWindow
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
public slots:
    void TextFunc1();
    void TextFunc2();
#endif // MAINWINDOW_H

源文件:

#include "mainwindow.h"
#pragma execution_character_set("utf-8")
#include <QWidget>
#include <QPushButton>
#include <QMessageBox>
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    resize(500,300);
    // 设置标题名称
    setWindowTitle("Qt编程");
    //定义一个按钮,它位于 widget 窗口中
    QPushButton *qbutton=new QPushButton("点击命令按钮",this);
    //设置按钮的位置和尺寸
    qbutton->setGeometry(10,10,150,30);
    // 实现信号与槽函数
    connect(qbutton,SIGNAL(clicked()),this,SLOT(TextFunc1()));
    connect(qbutton,SIGNAL(clicked()),this,SLOT(TextFunc2()));
MainWindow::~MainWindow()
void MainWindow::TextFunc1()
    QMessageBox::information(this,"提示","调用槽函数:TextFunc1",QMessageBox::Yes);