余睿的博客

浮生若梦,别多会少,不如莫遇

0%

Qt信号槽

信号槽原理

Qt中,提供了信号槽来处理对象间的通信。信号槽机制是Qt中的主要特征,同时也是Qt不同于其它框架的最大特点。

信号槽有些类似windows中的消息机制,通过回调函数来处理对象间的通信。但是回调函数是一种紧耦合的设计,调用者必须知道该在什么时候调用哪个回调函数。

Qt中的信号槽机制主要有以下两类函数实现:

  • 信号函数:只管发出信号,不需要知道信号的接收者。例如按钮点击事件。
  • 槽函数:只管接收,不管是谁发出的信号。例如响应一个点击事件。

信号函数与槽函数之间的联系通过QObject对象来绑定。

Qt信号槽的运行原理大致如下:

  1. 绑定信号函数和槽函数。
  2. 调用信号函数,发出信号(将信号写入队列中)
  3. 主线程从队列中获取信号。

需要注意的是,由于主线程是直接从队列中获取信号,所以信号的响应是依次执行的。当进行网络编程等响应时间过长的处理时,应该将信号的处理放到新线程中执行,否则会阻塞主线程。

信号槽关系图

通过Qt设计器(Qt designer)设置信号槽

拖拽添加信号槽

  • 首先,我们打开Qt设计器界面,拖动一个按钮到界面中,并命名为关闭窗口,我们希望点击这个按钮可以关闭当前窗口。
    关闭窗口按钮

  • 然后选中下图所示的编辑信号槽按钮(左上角第二个图标)
    编辑信号槽

  • 选中按钮,按住左键将其拖到窗体边缘。在弹出的对话框中选择clicked()事件,勾选下方的显示从Qwidget继承的槽,选中close()事件。
    绑定信号槽事件
    通过上图,我们可以看到,pushButton作为信号发出的对象,发出一个clicked()信号。MainWindow作为接收的对象,执行close()操作。

  • 然后单击OK
    创建成功信号槽

编译运行后,我们单击该按钮即可关闭窗口。

关闭窗口

信号槽编辑器添加

除了上面的拖拽添加信号槽,我们还可以在Qt设计器中的信号槽编辑器中添加信号槽。

信号槽编辑器

同样我们再拖动一个按钮到界面设计器中,并命名为最大化,通过单击该按钮实现最大化窗口。

最大化按钮

添加一个按钮,并将对象名字改为max,如上图右边所示。

然后在信号槽编辑器中添加如下规则:

信号槽编辑器

其中,发送者为max按钮,发送clicked()信号,接收者为MainWindow,响应的槽为showMaximized()

最大化

通过上面的信号槽编辑器不难发现,拖拽的添加方法和信号槽编辑器添加是一样的。

通过代码绑定信号槽

通过界面设计器,我们可以很方便的添加信号槽,但是在实际应用中,我们往往需要自己定义要发送的信号以及处理的槽函数,此时就需要通过代码来实现信号以及槽函数,并对他们进行绑定。

Q_OBJECT

对于所有需要添加信号槽的类,我们都需要定义Q_OBJECT这个宏,这个宏本身没有任何作用,只是用来告诉moc程序,下面来对比以下添加和不添加Q_OBJECT宏的编译输出。

未添加Q_OBJECT时的编译输出如下:

未添加`Q_OBJECT`输出

添加Q_OBJECT后编译输出如下:

添加`Q_OBJECT`后编译输出

我们可以看到,在添加了Q_OBJECT后编译输出多了moc_*的内容。

信号函数

我们在mainwindow.h中新增加一个信号函数mySignal(),我只要定义这个信号就可以了,不需要去实现它,moc程序会帮我们实现。

需要注意的是,信号函数需要有signals:声明。

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
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
Q_OBJECT

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

signals:
void mySignal();
# ^^^^^^^^^^^

private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

编译后,我们可以在moc_mainwindow.cpp文件中找到如下内容,moc程序帮我们完成了信号的定义,并可以将其加入到消息队列中。

1
2
3
4
5
// SIGNAL 0
void MainWindow::mySignal()
{
QMetaObject::activate(this, &staticMetaObject, 0, nullptr);
}

槽函数

下面我们来编写一个槽函数。槽函数可以申明多种访问权限,这里我们指定为public

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
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
Q_OBJECT

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

signals:
void mySignal();

public slots:
void mySlots();
# ^^^^^^^^^^

private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

需要注意的是,信号函数会自动生产定义,但是槽函数不会,槽函数需要我们自己添加定义。

我们实现槽函数的定义如下:

1
2
3
void MainWindow::mySlots(){
std::cout<<"mySlots()"<<std::endl;
}

通过控制台输出mySlots(),此时需要在pro文件中增加CONFIG += console来打开控制台。

信号槽的绑定

界面绑定

我们可以在Qt设计器中通过拖拽来绑定事件,但是没有显示我们的槽函数,我们需要手动添加槽函数。

信号槽

添加槽

绑定

完成后,可以看到如下的结果:

信号槽响应

可以看到,信号函数和槽函数绑定成功,槽函数响应了我们的点击事件。

手动添加槽函数

上面演示了用Qt设计器绑定信号槽,下面通过代码来绑定信号槽。

通过观察上面生成的ui_mainwindow.h文件,可以看到程序为我们绑定了信号槽,并且所有的界面元素对象以及样式都在该文件中进行了定义。

1
QObject::connect(pushButton, SIGNAL(clicked()), MainWindow, SLOT(mySlots()));
  • 第一个参数pushButton为信号的发出者
  • 第二个参数SIGNAL(clicked())表示clicked()信号
  • 第三个参数MainWindow为接收者
  • 第四个参数SLOT(mySlots())为定义的槽函数

需要注意的是,QObject为多有Qt对象的基类,正常可以不写,但当进行socket编程时需注意区分开来。

下面我们手动编写信号槽的连接。

1
2
3
4
5
6
7
8
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
QObject::connect(ui->pushButton,SIGNAL(clicked()),this,SLOT(mySlots()));
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
}
  • ui->pushButton为信号发出对象
  • SIGNAL(clicked())表示clicked()信号
  • this表示当前窗体为信号接收者
  • SLOT(mySlots())为定义的槽函数

除了上面的写法,信号槽函数还可以写成如下形式:

1
QObject::connect(ui->pushButton,&QPushButton::clicked,this,&MainWindow::mySlots);

手动绑定信号槽

总结

  • 信号函数只需要声明,不用定义。
  • 槽函数需要声明且定义。
  • 一个信号可以绑定多个槽函数

欢迎关注我的其它发布渠道