了解信号槽
所谓信号槽,实际就是观察者模式。当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,用自己的一个函数(成为槽(slot))来处理这个信号。也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。(这里提一句,Qt 的信号槽使用了额外的处理来实现,并不是 GoF 经典的观察者模式的实现方式。)
看一个例子:
先看代码
#include <QApplication>
#include <QPushButton>>
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QPushButton button("Quit");
QObject::connect(&button, &QPushButton::clicked, &QApplication::quit);
button.show();
return app.exec();
}
代码和上一篇的HelloWorld类型,只不过把文本框换成了按钮(button)
但是有一行是特别的
QObject::connect(&button, &QPushButton::clicked, &QApplication::quit);
connect的常用形式是:
connect(sender, signal,
receiver, slot);
四个参数分别是发出信号的对象, 信号, 接收者, 接受者在接到信号后做出的反应(即调用的函数)。
即sender 发生信号 signal后, 就调用receiver的slot函数。
QObject::connect有5种重载
QMetaObject::Connection connect(const QObject *, const char *,
const QObject *, const char *,
Qt::ConnectionType);
QMetaObject::Connection connect(const QObject *, const QMetaMethod &,
const QObject *, const QMetaMethod &,
Qt::ConnectionType);
//将this指针作为receiver,隐式传入
QMetaObject::Connection connect(const QObject *, const char *,
const char *,
Qt::ConnectionType) const;
QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
const QObject *, PointerToMemberFunction,
Qt::ConnectionType)
QMetaObject::Connection connect(const QObject *, PointerToMemberFunction,
Functor);
对应位置的参数分别和基本形式对应
从中可以看出
connect()函数的sender和reveicer都是QObject指针,
signal和slot一致,可以是char*,QMethod, PointerToMemberFunction
第5个比较特别 最后一个参数是Functor。 这个类型可以接受 static 函数、全局函数以及 Lambda 表达式。
信号槽要求信号和槽的参数类型一致。允许槽函数的参数可以比信号的少(这样槽会忽略信号传来的多余的参数)
自定义信号槽:
这个例子定义了一个发送者Newspaper(报纸),一个接收者Reader(读者)
发送者:报纸 有1个信号,
接受者 :读者 有1个成员函数 在connect时用作槽。
// newspaper.h
//Newspaper类 继承了QObject,继承了它的信号槽
class Newspaper : public QObject
{
//QObject类(及其子类)都要在第1行写Q_OBJECT
Q_OBJECT
public:
Newspaper(const QString & name) :
m_name(name)
{
}
void send()
{
emit newPaper(m_name); //emit:发出newPaper()信号, 感兴趣的接受者会关注这个信号
}
//signals:该类的信号
signals:
//函数名就是信号,返回值是void,参数是让外界知道的数据。不需要在.cpp中实现
void newPaper(const QString &name);
private:
QString m_name;
};
reader.h
接收者receiver:Reader
/ reader.h
#include <QObject>
#include <QDebug>
//接收信号,继承QObject ,添加Q_OBJECT
class Reader : public QObject
{
Q_OBJECT
public:
Reader() {}
//成员函数 作为 槽函数
void receiveNewspaper(const QString & name)
{
qDebug() << "Receives Newspaper: " << name;
}
};
main.cpp
在主函数先创建1个newspaper(发送者) 和 1个reader(接收者)
使用函数connect()让我们连接发送者和接收者,及发送者的信号,接收者的槽。
当调用newspaper的send时,send执行函数emit newsPaper 即发送信号newsPaper
接收者reader自动接收信号,调用相应的槽
Reader::receiveNewspaper
执行对应的语句
qDebug() << "Receives Newspaper: " << name;
在控制台输出:
Receives Newspaper: "Newspaper A"
#include <QCoreApplication>
#include "newspaper.h"
#include "reader.h"
int main(int argc, char *argv[])
{
QCoreApplication app(argc, argv);
//创建 报纸 和 接受者
Newspaper newspaper("Newspaper A");
Reader reader;
//连接
QObject::connect(&newspaper, &Newspaper::newPaper,
&reader, &Reader::receiveNewspaper);
//调用send函数,newspaper的send函数就发送 1个信号newPaper,然后连接者newspaper的reader调用槽函数
newspaper.send();
return app.exec();
}
注:如果运行出现LINK错误,解决办法:确定代码正确后重新qmake(右键项目,选择qmake)
总结:
- 发送者和接收者都需要是
QObject
的子类(当然,槽函数是全局函数、Lambda 表达式等无需接收者的时候除外); - 使用 signals 标记信号函数,信号是一个函数声明,返回 void,不需要实现函数代码;
- 槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;
- 使用 emit 在恰当的位置发送信号;
- 使用
QObject::connect()
函数连接信号和槽。