qt信号和槽函数

Qt是一个跨平台的C++框架,主要用来开发GUI界面程序,也可以用来开发没有界面的命令行程序。支持功能类似Virtual studio。但是和Virtual Studio不同的是,Qt支持运行在Linux,Window,Macos等桌面系统,甚至支持在Android,IOS等移动设备运行。我比较好奇的是Qt生成的windows程序,反编译后的的汇编入口标识和Virtualbox studio是否相同,有时间会试试看。

2022-04-07_21-42

一、 信号和槽函数

在GUI编程过程中,当修改一个界面部件时,我们经常需要调整另一个部件的行为。比如下面登录界面。

2022-04-07_21-55

当选择手动设置选项,那么下面对应的IP地址和端口等配置项要变为可用状态。也就是说当一个部件修改要通知下面的部件调整显示状态变为可用。使用MFC做界面开发的大佬应该比较熟悉事件和函数绑定。比如下面代码实现单击BTN1按钮调用OnBtnClick函数

1
ON_BN_CLICKED(BTN1, OnBtnClick)

但是,Qt并没有使用这种函数回调的方式进行函数和事件的绑定,而是采用信号和槽机制替换回调方式。信号和槽函数用于Qt对象之前的通信,是Qt核心特性。Qt中的部件(比如按钮,单选框,列表框)提供了很多预定义的信号和信号槽。信号是指Qt的界面部件被点击或选中等操作后出发的操作信息,比如上面网络代理设置界面选择手动设置选项后会触发**itemClicked**信号。信号槽是响应特定信号而调用的函数。Qt 的小部件有许多预定义的插槽。还是拿上面的列表举例(假如使用QListWidget实现),QListWidget包含如下槽函数
2022-04-07_22-25

二、 连接信号和槽函数

Qt使用connect函数实现信号和槽函数的绑定,伪代码原型如下

1
2
// Object1发出了signal1信号, Object2对象执行他的slot1槽函数
connect(Object1, signal1, Object2, slot1)

当然,一般来说使用匿名函数作为槽函数更加方便,也是通常的做法

1
2
3
connect(Object1, signal1, [=](){
// Object1出发signal1信号后立即执行这个匿名函数
})

使用信号和信号槽机制的好处是,发出信号的部件无需知道发出信号的处理函数。同样,槽函数也无需知道处理的是哪个部件发出的信号。这种松散耦合的设计保证只需要使用connect函数连接信号和槽函数,就可以保证一个信号的发生后对应的槽函数会被调用。并且信号和槽函数可以接受任意数量和任务类型的参数,并且完全是类型安全的。比如QLabel的槽函数setNum就是被重载的。

2022-04-07_22-41

信号也是可以重载的,但是没有找到对应的widget组件,哪天遇到了再补个图

附:Qt5类文档 https://doc.qt.io/qt-5/classes.html

三、 示例:点击按钮获取文本框内容

开发环境如下

开发环境 版本
系统版本 linux mint 20.3, 内核版本5.4.0-107
ide Qt Creator 4.14.2
编译器 Qmake
Qt版本 5.15.2
2022-04-09_08-54
  1. 新建Qwidget项目

    由于只是示例按钮点击获取文本内容,创建Qdialg足够用了。ui文件暂时也不用,用代码创建按钮和文本输入框。过程和代码如下:

    2022-04-09_08-58 2022-04-09_08-58_1 2022-04-09_08-59 2022-04-09_09-00 2022-04-09_09-01 2022-04-09_09-01_1
  2. 代码实现增加按钮和文本输入框
    Qt界面组件也是一个C++类,所以QDialg上的组件是在构造函数总添加并加载的,对话框关闭,也就是析构函数执行时释放资源。因此,只需要在Qdialog类的构造函数增加代码即可。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    #include "dialog.h"
    #include"QPushButton"
    #include"QLineEdit"
    #include"QDebug"
    #include"QMessageBox"

    Dialog::Dialog(QWidget *parent)
    : QDialog(parent)
    {
    this->setFixedSize(400, 300); // 对话框400x300,不可拉伸
    QPushButton *btn = new QPushButton(this); // 新建按钮
    btn->setText("按我打印文本框内容");
    btn->setGeometry(QRect(150, 200, 150, 40));

    QLineEdit *text = new QLineEdit(this);
    text->setText("初始内容");
    text->setGeometry(QRect(50, 50, 300, 50));
    // 弹出消息框显示文本框内容
    QMessageBox *messageBox = new QMessageBox(this);
    // 按钮按下获取文本框内容并打印
    connect(btn, &QPushButton::clicked, [=](){
    // 打印日志
    qDebug() << text->text();

    messageBox->setText(text->text());
    messageBox->show();
    });

    }

    Dialog::~Dialog()
    {

    }

    2022-04-09_11-24

四、 自定义信号和槽函数

前面写过信号和槽用于Qt对象之间的通信,那么信号的定义应该是Qt对象树的最顶端的对象支撑的。事实确实也是如此,不管是QWidget还是QPushButton都是继承自QObject,这点可以类比到JAVA所有对象都继承自Object。新建一个类CustomSignalTest并继承自QObject

2022-04-09_11-44

完成后打开头文件(customsignaltest.h),在signals块定义一个eat信号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#ifndef CUSTOMSIGNALTEST_H
#define CUSTOMSIGNALTEST_H

#include <QObject>

class CustomSignalTest : public QObject
{
Q_OBJECT
public:
explicit CustomSignalTest(QObject *parent = nullptr);

signals:
void eat(); // 增加eat信号
};

#endif // CUSTOMSIGNALTEST_H

基于上面按钮点击后弹出文本框内容的例子, 当点击按钮后,发出eat信息,文本框接受到eat信号后将文本框的字体颜色改为红色。正常情况下我们不会这么做,如此击鼓传花只为演示自定义信号的使用。还是在Dialog构造函数添加代码,最终代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#include "dialog.h"
#include"QPushButton"
#include"QLineEdit"
#include"QDebug"
#include"QMessageBox"
#include"customsignaltest.h"

Dialog::Dialog(QWidget *parent)
: QDialog(parent)
{
this->setFixedSize(400, 300); // 对话框400x300,不可拉伸
QPushButton *btn = new QPushButton(this); // 新建按钮
btn->setText("按我打印文本框内容");
btn->setGeometry(QRect(150, 200, 150, 40));

QLineEdit *text = new QLineEdit(this);
text->setText("初始内容");
text->setGeometry(QRect(50, 50, 300, 50));
// 弹出消息框显示文本框内容
QMessageBox *messageBox = new QMessageBox(this);

// 自定义信号演示类
CustomSignalTest *ct = new CustomSignalTest(this);

// 按钮按下获取文本框内容并打印
connect(btn, &QPushButton::clicked, [=](){
// 打印日志
qDebug() << text->text();

messageBox->setText(text->text());
messageBox->show();
// 发送eat信号
emit ct->eat();
});

// 当ct发出eat信号时,文本框变为红色
connect(ct, &CustomSignalTest::eat, [=](){
qDebug()<< "收到了eat信号";
QPalette palette;
palette.setColor(QPalette::Text, QColor(255, 0, 0));
text->setPalette(palette);
});

}

Dialog::~Dialog()
{

}


五、 信号重载

上面自定义信号是没有任何参数的,有些时候是需要发送信号的同时传入参数,这个时候就需要对信号进行重载,部分代码如下

1
2
3
4
5
signals:
void eat();
// 重载eat方法
void eat(QString footName);
};

当触发了带参数的重载方法后,将发送信号时传入的字符串打印出来。增加重载方法后会发现上面的例子报错了。原因是connect无法判断应该接收哪个eat信号,所以需要声明,修改后的部分代码如下

1
2
3
4
5
6
7
8
// 当ct发出eat信号时,文本框变为红色
void (CustomSignalTest:: *eatNoParam)() = &CustomSignalTest::eat; // 定义方法引用
connect(ct, eatNoParam, [=](){
qDebug()<< "收到了eat信号";
QPalette palette;
palette.setColor(QPalette::Text, QColor(255, 0, 0));
text->setPalette(palette);
});

接收带参数的信号和无参数信号类似,首先发送信息位置增加参数

1
2
3
4
5
6
7
8
9
10
11
12
// 按钮按下获取文本框内容并打印
connect(btn, &QPushButton::clicked, [=](){
// 打印日志
qDebug() << text->text();

messageBox->setText(text->text());
messageBox->show();
// 发送eat信号
emit ct->eat();
// 发送带参数的信号
emit ct->eat("我想喝鱼汤");
});

接收带参数的信号,打印到终端

1
2
3
4
5
// 定义带参数的信号变量
void (CustomSignalTest:: *eatParamString)(QString) = &CustomSignalTest::eat;
connect(ct, eatParamString, [=](QString content){
qDebug() << content; // 打印到终端
});
2022-04-09_13-05

附:

​ qt类文档: https://doc.qt.io/qt-6/classes.html

​ qt资源下载地址: https://download.qt.io/