通过信号和槽机制通信,通信的源头和接收端之间是松耦合的:
- 源头只需要顾自己发信号就行,不用管谁会接收信号;
- 接收端只需要关联自己感兴趣的信号,其他的信号都不管;
- 只要源头发了信号,关联该信号的接收端全都会收到该信号,并执行相应的槽函数。
源头和接收端是非常自由的,connect 函数决定源头和接收端的关联关系,并会自动根据信号里的参数传递给接收端的槽函数。
因为源头是不关心谁接收信号的,所以 connect 函数一般放在接收端类的代码中,或者放在能同时访问源端和接收端对象的代码位置。
编辑好之后保存界面,回到代码编辑模式,打开 widget.h,添加处理按钮 clicked 信号的槽函数,和新的自定义的信号 SendMsg:
cpp
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
namespace Ui {
class Widget;
}
class Widget : public QWidget
{
Q_OBJECT
public:
explicit Widget(QWidget *parent = 0);
~Widget();
signals: //添加自定义的信号
void SendMsg(QString str); //信号只需要声明,不要给信号写实体代码
public slots: //接收按钮信号的槽函数
void ButtonClicked();
private:
Ui::Widget *ui;
};
#endif // WIDGET_H
signals: 就是信号代码段的标志,这个标志不带 public 、protected、private 等前缀,那是因为信号默认强制规定为公有类型,这样才能保证其他对象能接收到信号。
我们定义了 SendMsg 信号,带一个 QString 参数,这个声明与普通函数声明类似。注意信号只是一个空壳,只需要声明它,而不要给它写实体代码。自定义信号的全部代码就是头文件这里的两行(包括 signals: 行),不需要其他的。signals: 标识的代码段只能放置信号声明,不能放其他任何东西,普通的函数或变量、槽函数都不要放在这里。
public slots: 是公有槽函数代码段的标志,定义了 ButtonClicked 槽函数,接收按钮被点击的信号,这个槽函数以后会触发我们自定义的信号。槽函数代码段也只能放槽函数声明的代码,不要把其他的东西放在这个代码段里。
下面来编写 widget.cpp 里面的代码,实现发送我们自定义信号的槽函数,并和按钮的信号关联起来:
cpp
#include "widget.h"
#include "ui_widget.h"
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//关联
connect(ui->pushButton, SIGNAL(clicked()), this, SLOT(ButtonClicked()));
}
Widget::~Widget()
{
delete ui;
}
//槽函数
void Widget::ButtonClicked()
{
//用 emit 发信号
emit SendMsg( tr("This is the message!") );
}
在 Widget 构造函数里,我们将按钮的 clicked 信号关联到槽函数 ButtonClicked,当按钮被点击时,ButtonClicked 会自动被调用。
ButtonClicked 里面只有一句代码,就是
emit SendMsg( tr("This is the message!") );
emit 是发信号的关键字,然后接下来就与调用函数是一样的格式,SendMsg 里面放置我们想传递的字符串参数。除了 emit 字样,触发信号就与函数调用一样。这样简单一句就实现了触发信号的过程,同之前所说的,源端就顾自己发信号,至于谁接收 SendMsg 信号,源端是不管的。
Widget 窗体代码就是上面那么多,发送我们自定义的 SendMsg 信号的过程如下图所示:
对于上图左半段,按钮的点击信号和相应的槽函数我们之前两节已经用过多次了,这个不是关注的重点。重点就是在主窗体槽函数里面,我们发出了自定义的信号 SendMsg ,谁来接收它呢?
接下来我们造一个接收 SendMsg 信号的类对象和槽函数,并将收到的字符串参数弹窗显示。我们先为该项目添加一个新的类,并编写接收 SendMsg 信号的槽函数。
打开 QtCreator 菜单"文件"-->"新建文件或项目",在"新建"对话框里,左边部分选择"C++",中间部分选 "C++ Class",如下图所示:
然后点击右下角 Choose,进入新建 C++ 类的向导界面,将 Class name 修改为 ShowMsg,基类选择 QObject,其他的就用自动填充的,选择基类 QObject 之后,会自动包含相应头文件。要使用信号和槽机制,必须直接或间接从 QObject 类派生,我们这里是直接从 QObject 派生了子类 ShowMsg:
然后点击"下一步",进入项目管理界面:
这里就按照默认的值,不用修改,自动添加到项目 qobjcom.pro 里面,版本控制默认是没有。点击"完成",稍等一会,QtCreator 就会生成好 ShowMsg 类的两个文件 showmsg.h 和 showmsg.cpp,并添加到项目里。
接下来,我们编辑 showmsg.h ,声明接收 SendMsg 信号的槽函数 RecvMsg:
cpp
#ifndef SHOWMSG_H
#define SHOWMSG_H
#include <QObject>
class ShowMsg : public QObject
{
Q_OBJECT
public:
explicit ShowMsg(QObject *parent = 0);
~ShowMsg();
signals:
public slots:
//接收 SendMsg 信号的槽函数
void RecvMsg(QString str);
};
#endif // SHOWMSG_H
cpp
RecvMsg 槽函数声明的参数类型和返回类型要与 SendMsg 信号保持一致,所以参数是 QString,返回 void。
然后我们编辑 showmsg.cpp,实现 RecvMsg 槽函数:
cpp
#include "showmsg.h"
#include <QMessageBox>
ShowMsg::ShowMsg(QObject *parent) : QObject(parent)
{
}
ShowMsg::~ShowMsg()
{
}
//str 就是从信号里发过来的字符串
void ShowMsg::RecvMsg(QString str)
{
QMessageBox::information(NULL, tr("Show"), str);
}
添加头文件 <QMessageBox> 包含之后,我们添加槽函数 RecvMsg 的实体代码,里面就是一句弹窗的代码,显示收到的字符串。QMessageBox::information 函数第一个参数是父窗口指针,设置为 NULL,代表没有父窗口,就是在系统桌面直接弹窗的意思。
信号和槽机制有三步,一是有源头对象发信号,我们完成了;第二步是要有接收对象和槽函数,注意,上面只是类的声明,并没有定义对象。我们必须定义一个接收端的对 象,然后才能进行第三步 connect。
编辑项目里 main.cpp,向其中添加代码,定义接收端对象,然后进行 connect:
cpp
#include "widget.h"
#include <QApplication>
#include "showmsg.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w; //①主窗体对象,内部会发送 SendMsg 信号
ShowMsg s; //②接收端对象,有槽函数 RecvMsg
//③关联,信号里的字符串参数会自动传递给槽函数
QObject::connect(&w, SIGNAL(SendMsg(QString)), &s, SLOT(RecvMsg(QString)));
//显示主界面
w.show();
return a.exec();
}
首先添加 "showmsg.h" 头文件包含,然后在主窗体对象 w 定义之后,定义了接收端对象 s。主窗体对象 w 会发 SendMsg 信号,接收端 s 有对应的槽函数 RecvMsg,这样完成了信号和槽机制的头两步。接下来第三步就是调用关联函数 QObject::connect,将源头对象、信号、接收端对象、槽函数关联。connect 函数是通用基类 QObject 里面定义的,之前用 connect 函数都没有加类前缀,是因为在 QObject 派生类里面自动继承了 connect 函数,不需要额外的前缀。在 main 函数里,需要手动加 QObject:: 前缀来调用 connect 函数。
关联完成之后,一旦用户点击主窗体里的按钮,我们自定义的 SendMsg 信号就会发出去,然后 接收端对象 s 里的槽函数就会执行,并且信号里的字符串也会自动传递给 RecvMsg 槽函数,然后会出现弹窗显示传递的字符串。
这个示例的运行效果如下图所示:
本小节需要大家学习的就是右半段的部分,我们在主窗体 ButtonClicked 函数里触发自定义的信号 SendMsg,然后通过 connect 函数关联,自动调用了接收端对象 s 的槽函数 RecvMsg,并弹窗显示了传递的字符串。
也许有读者会问,费这么大劲,为什么不直接在 ButtonClicked 里面弹窗?那不简单多了?
因为本小节的目的不是弹窗,而是为了展现自定义信号和槽函数的代码写法,理解信号和槽机制的运行流程。以后遇到复杂多窗口的界面程序,在多个窗体对象之间就可以用 上图示范的流程,来进行通信、传递数据。