Qt 信号和槽机制详解

信号和槽是 Qt 独有的一种机制,他让窗口的各种消息处理简化到极致,常规情况下我们相应某窗口(控件)的点击时都需要自己投递消息到框架中,由框架的消息队列投递给不同的窗口消息处理函数来处理。如果使用信号和槽,需要声明信号、定义槽函数、绑定信号和槽、发射信号就可以完成上述功能,代码简单容易理解,逻辑简单易懂。信号和槽的大致实现图如下:

2015-06-23_164236

【信号和槽使用规则和注意事项】

定义信号和槽:

  • 信号和槽机制,是Qt的拓展,使程序员可以决定信号函数的调用目标
  • 信号和槽只有Qt对象才能拥有(QObject类或QObject的子类才能定义信号和槽函数)
  • 定义了信号和槽的Qt类,必须以 Q_OBJECT 宏开始,其内部是初始化信号和槽的环境
  • 信号函数,定义在类的 signal 标识符保留字下,是Qt内部自己封装的功能,只有Qt Creator才识别,其他环境是不识别的,并且信号函数不需要实现,只需定义
  • 槽函数,定义在类的 slot 标识符保留字下,也一样是Qt内部自己封装的,槽函数必须要实现

连接和调用:

  • 连接信号和槽,使用 Object 类或 Object 子类的静态成员函数 connect 来连接信号和槽
  • 发射(调用)信号函数,要使用 emit 保留字,emit 同样是Qt内部自己封装的,其他编译器并不能识别

注意事项:

  • connect函数的第二个和第四个参数都是char*类型,需要使用SIGNAL和SLOT宏将带有括号的函数名转换为char*
  • 信号和槽函数的参数个数最好保持一致,如果信号函数参数少于槽函数参数,那么程序会崩溃,因为槽不知道去哪取多出来的参数
  • 信号函数可以和多个槽函数相连,当信号触发后,多个槽函数都会执行,但是哪一个优先执行,Qt并没有保障
  • 一个槽函数可以被多个信号函数连接,这样多个信号会触发同一个槽函数
  • 信号函数可以和信号函数连接,相当于一个传递者,两个信号都会调用同一个槽函数
  • 信号和槽的参数有限制,限制比较多,比较明显的就是模版类对象是无法做参数的,如果需要传递比较特殊的数据类型,可以将数据先封装为结构体,然后调用 qRegisterMetaType<类型>(); 来注册结构体类型就可以通过信号和槽函数的参数传递了

总结:

  1. 信号和槽都在 QObecjt 类或子类下
  2. 三个处理宏 Q_OBJECT SIGNAL SLOT
  3. 三个保留字 signal slot emit
  4. 一个连接函数 QObject::connect

【手写示例代码】

代码中包含两个类和一个main.cpp文件,是将上面图中表示的情况编写为了代码,工有5个文件:

  1. csignal.h:信号类
  2. csignal.cpp:信号类
  3. cslot.h:槽类
  4. cslot.cpp:槽类+槽函数实现
  5. main.cpp:创建两个类并连接信号和槽
#ifndef CSIGNAL_H
#define CSIGNAL_H

#include <QObject>

// 信号和槽只有Qt对象才能拥有(QObject类或QObject的子类才能定义信号和槽函数)
class CSignal : public QObject
{
    // 定义了信号和槽的Qt类,必须以 Q_OBJECT 宏开始,其内部是初始化信号和槽的环境
    Q_OBJECT
public:
    explicit CSignal(QObject *parent = 0);

signals:
    void signalTest();

public slots:
};

#endif // CSIGNAL_H
#include "csignal.h"

CSignal::CSignal(QObject *parent) : QObject(parent)
{

}
#ifndef CSLOT_H
#define CSLOT_H

#include <QObject>
#include <QDebug>

// 信号和槽只有Qt对象才能拥有(QObject类或QObject的子类才能定义信号和槽函数)
class CSlot : public QObject
{
    // 定义了信号和槽的Qt类,必须以 Q_OBJECT 宏开始,其内部是初始化信号和槽的环境
    Q_OBJECT
public:
    explicit CSlot(QObject *parent = 0);

signals:

public slots:
    // 槽函数定义
    void slotTest();
};

#endif // CSLOT_H
#include "cslot.h"

CSlot::CSlot(QObject *parent) : QObject(parent)
{

}

// 槽函数实现
void CSlot::slotTest()
{
    qDebug() << "slotTest running...";
}
#include <QCoreApplication>
#include "csignal.h"
#include "cslot.h"

int main(int argc, char* argv[])
{
    QCoreApplication app(argc, argv);

    CSignal* sig = new CSignal;
    CSlot*  slot = new CSlot;

    // 连接 CSignal 类的信号到 CSlot 类的槽函数
    app.connect(sig, SIGNAL(signalTest()), slot, SLOT(slotTest()));

    // 发射信号,因为上面绑定了槽函数,所以相当于调用了 CSlot 类中的 slotTest 函数
    emit sig->signalTest();

    return app.exec();
}

【信号和槽在框架中的使用】

在 Qt 框架中,我们创建的一些由 Qt 已经实现过的窗口时,内置了许多已经写好的信号函数,比如 QLineEdit 控件,我们在写他的信号和槽连接函数时,就能看到 IDE 给我们提示的这么多的信号函数:

2015-06-23_165726

比如我们希望在 QLineEdit 控件中输入完文字按下回车后自动处理某些事情,我们就可以一处理将其 returnPressed() 信号函数与我们自定义的一个槽函数绑定在一起,如下:

#include "cwidget.h"
#include <QPushButton>
#include <QLineEdit>
#include <QDebug>

CWidget::CWidget(QWidget *parent) : QWidget(parent)
{
    QLineEdit* lineEdit = new QLineEdit(this);

    // 将信号连接到本类中的 returnSlot() 槽函数
    connect(lineEdit, SIGNAL(returnPressed()), this, SLOT(returnSlot()));
}

// 在 CWidget.h 的 slot: 关键字下声明槽函数,在此处实现
void CWidget::returnSlot()
{
    qDebug() << "lineEdit returnPress...";
}

此时当我们在 QLineEdit 窗口上按下回车键的时候,Qt Creator 调试信息就会输出 lineEdit returnPress…:

2015-06-23_170222

同样,按钮等窗口控件也都可以实现如上要求,系统都内置了很多信号函数,比如我们希望按一下按钮就退出程序,那么可以如下这样实现:

#include "cwidget.h"
#include <QPushButton>
#include <QLineEdit>
#include <QDebug>

CWidget::CWidget(QWidget *parent) : QWidget(parent)
{
    QLineEdit* lineEdit = new QLineEdit(this);

    // 将信号连接到本类中的 returnSlot() 槽函数
    connect(lineEdit, SIGNAL(returnPressed()), this, SLOT(returnSlot()));

    QPushButton* button = new QPushButton("exit", this);

    // 将button内置的信号函数 clicked 与本类中提供的 close 槽函数绑定到一起
    connect(button, SIGNAL(clicked()), this, SLOT(close()));
}

// 在 CWidget.h 的 slot: 关键字下声明槽函数,在此处实现
void CWidget::returnSlot()
{
    qDebug() << "lineEdit returnPress...";
}

当我们点下 exit 按钮时,系统会调用 CWidget 类中的 close() 函数来退出窗口。这样的案例还有很多,大家可以自己在 Qt Creator 中编写代码时多多留意。

评论