Qt 编程指南 3 信号和槽沟通

时间:2021-07-15 04:09:09

https://qtguide.ustclug.org/

1 信号和槽

所谓信号槽,简单来说,就像是插销一样:一个插头和一个插座。怎么说呢?当某种事件发生之后,比如,点击了一下鼠标,或者按了某个按键,这时,这个组件就会发出一个信号。就像是广播一样,如果有了事件,它就漫天发声。这时,如果有一个槽,正好对应上这个信号,那么,这个槽的函数就会执行,也就是回调。

#include <QtGui/QApplication>
#include <QtGui/QPushButton> int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QPushButton *button = new QPushButton("Quit"); // QApplication 的实例 a 说,如果button 发出了 clicked 信号,你就去执行我的 quit 函数。
QObject::connect(button, SIGNAL(clicked()), &a, SLOT(quit())); button->show();
return a.exec();
}

QObject 是所有类的根。Qt 使用这个 QObject 实现了一个单根继承的 C++。它里面有一个 connect静态函数,用于连接信号槽。

clicked()就是一个信号,而 quit()就是槽

2 应用实例

  • 忽略自动补全报的错
  • 在图形界面修改过后,自动补全未必及时读取新加入的控件的信息

1 在主窗口头文件Qt_tset1.h里声明这个函数FoodIsComing()

Qt 编程指南  3 信号和槽沟通

2 在主窗口函数文件Qt_tset1.cpp里实现这个函数体FoodIsComing()

Qt 编程指南  3 信号和槽沟通

3 创建链接执行函数。控件动作触发事件,然后调用函数执行

例如: 按键 的 单击动作 触发 主窗体 中的    FoodIsComing() 函数,并执行。

Qt 编程指南  3 信号和槽沟通

3 自定义信号和槽沟通

通过信号和槽机制通信,通信的源头和接收端之间是松耦合的:

  • 源头只需要顾自己发信号就行,不用管谁会接收信号;
  • 接收端只需要关联自己感兴趣的信号,其他的信号都不管;
  • 只要源头发了信号,关联该信号的接收端全都会收到该信号,并执行相应的槽函数。
为何不用回掉函数
  • 回调函数机制是很常见的,Windows 消息机制本身也是回调函数的应用,多线程编程也使用回调函数作为新线程里的任务函数。
  • 我们上一节示范的三个例子,信号与槽函数可以一对一关联,一对多关联,多对一关联,如 果用回调函数实现这些复杂的映射,那会是非常头疼的事。比如希望 theSrc 同时把数据传递给 A、B、C 三个目标对象,那 SendDataTo 函数必须手动执行三次。
  • 回调函数难以实现同时一发多收、多发一收,而信号和槽机制是完全可以的,并且代码非常简洁明了。
  • 另外信号与槽函数可以在运行时解除关联关系,这也是回调函数不好实现的特性。

源头和接收端是非常*的,connect 函数决定源头和接收端的关联关系,并会自动根据信号里的参数传递给接收端的槽函数。

因为源头是不关心谁接收信号的,所以 connect 函数一般放在接收端类的代码中,或者放在能同时访问源端和接收端对象的代码位置。

创建信号源

1 在窗体上创建一个按钮  显示 “发送自定义”   引用名 pushButton

Qt 编程指南  3 信号和槽沟通

2 在Qt_tset1.h 中 ,添加信号 SendMsg1(QString str) 和槽函数  ButtonClicked()  声明

#pragma once

#include <QtWidgets/QMainWindow>
#include "ui_Qt_tset1.h" class Qt_tset1 : public QMainWindow
{
Q_OBJECT public:
Qt_tset1(QWidget *parent = Q_NULLPTR); //添加这一段代码
public slots: //槽函数声明标志
void FoodIsComing(); //槽函数 void PrintText(const QString& text); void ButtonClicked(); // 接收按钮信号的槽函数 需要实体代码 signals: //添加自定义的信号 void SendMsg1(QString str); //信号只需要声明,不要给信号写实体代码,因为使用了关键字 emit发信号
private: Ui::Qt_tset1Class ui; };

 3  在Qt_tset1.cpp 中 ,添加信号 SendMsg1(QString str) 的关键字 和槽函数  ButtonClicked()   实体

void Qt_tset1::ButtonClicked()
{
//用 emit 发信号
//emit 是发信号的关键字,然后接下来就与调用函数是一样的格式,SendMsg 里面放置我们想传递的字符串参数。除了 emit 字样,触发信号就与函数调用一样。
emit SendMsg1(tr("This is the message!"));
}

emit 是发信号的关键字,然后接下来就与调用函数是一样的格式,SendMsg 里面放置我们想传递的字符串参数。除了 emit 字样,触发信号就与函数调用一样。这样简单一句就实现了触发信号的过程,同之前所说的,源端就顾自己发信号,至于谁接收 SendMsg 信号,源端是不管的。
Widget 窗体代码就是上面那么多,发送我们自定义的 SendMsg 信号的过程如下图所示:

Qt 编程指南  3 信号和槽沟通

4在Qt_tset1.cpp 中  关联 信号 和 槽函数   。 按键动clicked(),触发执行ButtonClicked()函数。该函数 内部 执行 发射信号动作。

 //关联
  connect(ui.pushButton, SIGNAL(clicked()), this, SLOT(ButtonClicked()));

  Qt 编程指南  3 信号和槽沟通

创建接收槽

添加一个新的类(新的窗口或者其他界面),里面创建接受函数接收上一个窗口发来的数据

Qt 编程指南  3 信号和槽沟通

这里创建一个QT Class新的类ShowMsg ,(也可以是 Qt GUI Class)

Qt 编程指南  3 信号和槽沟通

依赖于QObject 基类

Qt 编程指南  3 信号和槽沟通

然后可以看到新创建的ShowMsg.h和ShowMsg.cpp

Qt 编程指南  3 信号和槽沟通

接下来,我们编辑 showmsg.h ,声明接收 SendMsg 信号的槽函数 RecvMsg:

#pragma once

#include <QObject>

class ShowMsg : public QObject
{
Q_OBJECT public:
//ShowMsg(QObject *parent);
explicit ShowMsg(QObject *parent = 0); //构造函数 ~ShowMsg(); public slots:
//接收 SendMsg 信号的槽函数
void RecvMsg(QString str); };

  RecvMsg 槽函数声明的参数类型和返回类型要与 SendMsg 信号保持一致,所以参数是 QString,返回 void。

然后我们编辑 showmsg.cpp,实现 RecvMsg 槽函数:

#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:

#include "Qt_tset1.h"    // 主窗体
#include "ShowMsg.h" // 接收窗体 #include <QtWidgets/QApplication>
#include <QtWidgets/QLabel> //#include <iostream>
//using namespace std; int main(int argc, char *argv[])
{ //cout << 123 << endl; QApplication a(argc, argv); Qt_tset1 w; // ①主窗体对象,内部会发送 SendMsg 信号
ShowMsg s; //②接收端对象,有槽函数 RecvMsg
//③关联,信号里的字符串参数会自动传递给槽函数
QObject::connect(&w, SIGNAL(SendMsg1(QString)), &s, SLOT(RecvMsg(QString))); // QLabel label(QLabel::tr("Hello Qt!"));
//label.show(); //QPushButton *button = new QPushButton("Quit");
// QObject::connect(button, SIGNAL(clicked()), &a, SLOT(quit()));
//button->show(); //显示主界面
w.show();
return a.exec(); //cout << 123 << endl;
}

首先添加 "showmsg.h" 头文件包含,然后在主窗体对象 w 定义之后,定义了接收端对象 s。

主窗体对象 w 会发 SendMsg 信号,接收端 s 有对应的槽函数 RecvMsg,这样完成了信号和槽机制的头两步。

接下来第三步就是调用关联函数 QObject::connect,将源头对象、信号、接收端对象、槽函数关联。

connect 函数是通用基类 QObject 里面定义的,之前用 connect 函数都没有加类前缀,是因为在 QObject 派生类里面自动继承了 connect 函数,不需要额外的前缀。

在 main 函数里,需要手动加 QObject:: 前缀来调用 connect 函数。
关联完成之后,一旦用户点击主窗体里的按钮,我们自定义的 SendMsg 信号就会发出去,然后 接收端对象 s 里的槽函数就会执行,并且信号里的字符串也会自动传递给 RecvMsg 槽函数,然后会出现弹窗显示传递的字符串。

Qt 编程指南  3 信号和槽沟通

这个例子完整的执行流程如下图所示:

Qt 编程指南  3 信号和槽沟通

本小节需要大家学习的就是右半段的部分,我们在主窗体 ButtonClicked 函数里触发自定义的信号 SendMsg,然后通过 connect 函数关联,自动调用了接收端对象 s 的槽函数 RecvMsg,并弹窗显示了传递的字符串。

也许有读者会问,费这么大劲,为什么不直接在 ButtonClicked 里面弹窗?那不简单多了?
因为本小节的目的不是弹窗,而是为了展现自定义信号和槽函数的代码写法,理解信号和槽机制的运行流程。以后遇到复杂多窗口的界面程序,在多个窗体对象之间就可以用 上图示范的流程,来进行通信、传递数据。

4 信号关联到信号示例

信号除了可以关联到槽函数,还可以关联到类型匹配的信号,实现信号的接力触发。上个示例中因为 clicked 信号没有参数,而 SendMsg 信号有参数,所以不方便直接关联。本小节示范一个信号到信号的关联,将按钮的 clicked 信号关联到一个参数匹配的 SendVoid 信号。
重新打开 QtCreator,新建一个 Qt Widgets Application 项目,在新建项目的向导里填写:

4.1 发射信号

Qt 编程指南  3 信号和槽沟通

1创建新按钮

Qt 编程指南  3 信号和槽沟通

Qt_tset1.h添加我们自定义的信号SendVoid():

Qt 编程指南  3 信号和槽沟通

2新添加的 SendVoid 信号声明,没有参数,所以能和按钮的 clicked 信号匹配,实现信号到信号的关联。

添加关联函数调用:Qt 编程指南  3 信号和槽沟通

仅在构造函数里加了一句 connect 调用,注意 connect 函数第四个参数是 SIGNAL(SendVoid()),这就是关联到信号的用法。以前都是关联到槽函数,这里直接关联到自定义的信号,而不需要槽函数中转。

关联之后,一旦按钮的 clicked 信号触发,主窗体的信号 SendVoid() 紧跟着自动触发,实现信号触发的接力过程。

4.2 接收信号

自定义信号的触发过程编完之后,下面为项目添加新的ShowMsg 类 也是从 QObject 派生

然后我们声明自定义的槽函数,用于接收 SendVoid() 信号,打开 showvoid.h,编辑如下:

Qt 编程指南  3 信号和槽沟通

#pragma once

#include <QObject>

class ShowMsg : public QObject
{
Q_OBJECT public:
//ShowMsg(QObject *parent);
explicit ShowMsg(QObject *parent = 0); //构造函数 ~ShowMsg(); public slots:
//接收 SendMsg 信号的槽函数
void RecvMsg(QString str); //接收 SendVoid() 信号的槽函数
void RecvVoid(); };

  

头文件增加了与 SendVoid() 信号匹配的槽函数 RecvVoid() 声明。

然后我们编辑 ShowMsg.cpp,添加槽函数实体代码:

Qt 编程指南  3 信号和槽沟通

#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);
} //槽函数,弹窗
void ShowMsg::RecvVoid()
{
QMessageBox::information(NULL, tr("Show"), tr("Just void."));
}

  

有了 ShowVoid 类声明是不够的,接收信号需要一个对象实体,然后才能关联,所以同样地,编辑 main.cpp 文件,添加代码如下:

Qt 编程指南  3 信号和槽沟通

#include "Qt_tset1.h"    // 主窗体
#include "ShowMsg.h" // 接收窗体 #include <QtWidgets/QApplication>
#include <QtWidgets/QLabel> //#include <iostream>
//using namespace std; int main(int argc, char *argv[])
{ //cout << 123 << endl; QApplication a(argc, argv); Qt_tset1 w; // ①主窗体对象,内部会发送 SendMsg 信号
ShowMsg s; //②接收端对象,有槽函数 RecvMsg
//③关联,信号里的字符串参数会自动传递给槽函数
QObject::connect(&w, SIGNAL(SendMsg1(QString)), &s, SLOT(RecvMsg(QString))); //关联源头的信号和接收端的槽函数
QObject::connect(&w, SIGNAL(SendVoid()), &s, SLOT(RecvVoid())); // QLabel label(QLabel::tr("Hello Qt!"));
//label.show(); //QPushButton *button = new QPushButton("Quit");
// QObject::connect(button, SIGNAL(clicked()), &a, SLOT(quit()));
//button->show(); //显示主界面
w.show();
return a.exec(); //cout << 123 << endl;
}

  Qt 编程指南  3 信号和槽沟通

执行流程如下图所示:

Qt 编程指南  3 信号和槽沟通

主窗体里将信号关联到信号,是需要大家学会用的。也许有读者会问,为什么不直接将 ui->pushButton 的信号关联到最终的目的端 s 呢?

因为 ui 是主窗体对象 w 的私有成员变量,在类外不可访问,无论是 main 函数还是 ShowVoid 类的代码里,都是看不到 ui->pushButton 这个按钮的,源头都找不到,是没法关联的。

如果把私有变量 ui 改成公有的,那会破坏类的封装性,不建议这么弄。在面对私有成员无法访问的情况下,使用信号接力是比较科学的方法。

Qt 编程指南 3 信号和槽沟通的更多相关文章

  1. Qt Quick 事件处理之信号与槽

    前面两篇文章<QML 语言基础>和<Qt Quick 简单教程>中我们介绍了 QML 语言的基本的语法和 Qt Quick 的常见元素,亲们,通过这两篇文章,您应该已经能够完毕 ...

  2. Qt对象模型之一:信号和槽

    一.信号和槽机制概述 信号槽是 Qt 框架引以为豪的机制之一.所谓信号槽,实际就是观察者模式.当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal).这种发出是没有目 ...

  3. Qt Quick 事件处理之信号与槽(foruok的博客)

    前面两篇文章<QML 语言基础>和<Qt Quick 简单教程>中我们介绍了 QML 语言的基本语法和 Qt Quick 的常见元素,亲们,通过这两篇文章,您应该已经可以完成简 ...

  4. 第七章 探秘Qt的核心机制-信号与槽

    第七章 探秘Qt的核心机制-信号与槽 注:要想使用Qt的核心机制信号与槽,就必须在类的私有数据区声明Q_OBJECT宏,然后会有moc编译器负责读取这个宏进行代码转化,从而使Qt这个特有的机制得到使用 ...

  5. 2&period;QT-窗口组件&lpar;QWidget&rpar;&comma;QT坐标系统&comma;初探消息处理&lpar;信号与槽&rpar;

    本章主要内容如下: 1) 窗口组件(QWidget) 2) QT坐标系统 3) 消息处理(信号与槽) 窗口组件(QWidget) 介绍 Qt以组件对象的方式构建图形用户界面 Qt中没有父组件的*组件 ...

  6. Qt 编程指南

    Qt 编程指南 持续关注一本正在编写的Qt编程指南,期待作者早日完成创作.

  7. C&sol;C&plus;&plus; -- Gui编程 -- Qt库的使用 -- 信号与槽的关联

    Qt信号与槽的三种关联方法:1.设计界面关联,编辑信号/槽,自动关联 2.手动关联(1).头文件中定义槽 -----mywidget.h----- #ifndef MYWIDGET_H #define ...

  8. Qt 编程指南 4 按钮

    1按钮类的控件 逐个解释一下各个用途:(1)按压按钮 QPushButton最基本的按钮,点击该按钮通常是通知程序进行一个操作,比如弹个窗.下一步.保存.退出等等,这是经常用到的,操作系统里的对话框里 ...

  9. Qt Quick中的信号与槽

    在QML中,在Qt Quick中,要想妥善地处理各种事件,肯定离不开信号与槽,本博的主要内容就是整理Qt 中的信号与槽的内容. 1. 链接QML类型的已知信号 QML中已有类型定义的信号分为两类:一类 ...

随机推荐

  1. prototype&period;js源码

    prototype 1.3.1 版本和之前的 1.2.0 版本有了不少改进,并增加了新的功能: 1. 增加了事件注册管理2. 增加了空间定位的常用函数3. 改善了 xmlhttp 的封装4. 移除了 ...

  2. Hello World(本博客启程篇)

    Hello World 作为本博客第一篇日志,作为程序员,无论走到哪里,做什么事,必须先输出这句话. 一个想法 从今天3月份到现在一直在学技术,过程中坑的解决.知识的总结以及想法等都写到了" ...

  3. Android项目——短信发送器

    因为应用要使用手机的短信服务,所以要在清单文件AndroidManifest.xml中添加短信服务权限: <?xml version="1.0" encoding=&quot ...

  4. 西南科技大学第十届ACM程序设计竞赛题解

    A.德州扑克 B. 我恨11(1089) 问题描述 11是一个孤独的数字,小明十分讨厌这个数字,因此如果哪个数字中出现了11或者该数字是11的倍数,他同样讨厌这个数字.现在问题来了,在闭区间[L,R] ...

  5. 为Android安装BusyBox

    大家是否有过这样的经历,在命令行里输入adb shell,然后使用命令操作你的手机或模拟器,但是那些命令都是常见Linux命令的阉割缩水版,用起来很不爽.是否想过在 Android上使用较完整的she ...

  6. 雅虎UED--无障碍网页设计

    转自:http://www.sharetk.com/html/ued/Interactive-Design/1394.html 随着web使用量的增加和人们网络意识的增强,一些特殊用户开始被我们所关注 ...

  7. PAT 1003&period; Emergency 单源最短路

    思路:定义表示到达i的最短路径数量,表示到达i的最短径,表示最短路径到达i的最多人数,表示从i到j的距离, 表示i点的人数.每次从u去更新某个节点v的时候,考虑两种情况: 1.,说明到达v新的最短路径 ...

  8. 面试为什么需要了解JVM

    匠心零度 转载请注明原创出处,谢谢! 说在前面 如果你经常注意面试题,你会发现现在面试题多多少少会含有jvm相关的面试题,之前也把一些jvm面试题汇总了下:面试题系列一,那么为什么现在面试需要了解或者 ...

  9. mysql-笔记--增删改查

    查看数据库:可以使用 show databases; 命令查看已经创建了哪些数据库 指定数据库:在登录后使用 use 语句指定, 命令: use 数据库名;要对一个数据库进行操作, 必须先选择该数据库 ...

  10. mybatis 传入多个参数

    一.单个参数: public List<XXBean> getXXBeanList(@param("id")String id); <select id=&quo ...