Qt 编程指南 3 信号和槽沟通
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()
2 在主窗口函数文件Qt_tset1.cpp里实现这个函数体FoodIsComing()
3 创建链接执行函数。控件动作触发事件,然后调用函数执行
例如: 按键 的 单击动作 触发 主窗体 中的 FoodIsComing() 函数,并执行。
3 自定义信号和槽沟通
通过信号和槽机制通信,通信的源头和接收端之间是松耦合的:
- 源头只需要顾自己发信号就行,不用管谁会接收信号;
- 接收端只需要关联自己感兴趣的信号,其他的信号都不管;
- 只要源头发了信号,关联该信号的接收端全都会收到该信号,并执行相应的槽函数。
为何不用回掉函数
- 回调函数机制是很常见的,Windows 消息机制本身也是回调函数的应用,多线程编程也使用回调函数作为新线程里的任务函数。
- 我们上一节示范的三个例子,信号与槽函数可以一对一关联,一对多关联,多对一关联,如 果用回调函数实现这些复杂的映射,那会是非常头疼的事。比如希望 theSrc 同时把数据传递给 A、B、C 三个目标对象,那 SendDataTo 函数必须手动执行三次。
- 回调函数难以实现同时一发多收、多发一收,而信号和槽机制是完全可以的,并且代码非常简洁明了。
- 另外信号与槽函数可以在运行时解除关联关系,这也是回调函数不好实现的特性。
源头和接收端是非常自由的,connect 函数决定源头和接收端的关联关系,并会自动根据信号里的参数传递给接收端的槽函数。
因为源头是不关心谁接收信号的,所以 connect 函数一般放在接收端类的代码中,或者放在能同时访问源端和接收端对象的代码位置。
创建信号源
1 在窗体上创建一个按钮 显示 “发送自定义” 引用名 pushButton
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 信号的过程如下图所示:
4在Qt_tset1.cpp 中 关联 信号 和 槽函数 。 按键动clicked(),触发执行ButtonClicked()函数。该函数 内部 执行 发射信号动作。
//关联
connect(ui.pushButton, SIGNAL(clicked()), this, SLOT(ButtonClicked()));
创建接收槽
添加一个新的类(新的窗口或者其他界面),里面创建接受函数接收上一个窗口发来的数据
这里创建一个QT Class新的类ShowMsg ,(也可以是 Qt GUI Class)
依赖于QObject 基类
然后可以看到新创建的ShowMsg.h和ShowMsg.cpp
接下来,我们编辑 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 槽函数,然后会出现弹窗显示传递的字符串。
这个例子完整的执行流程如下图所示:
本小节需要大家学习的就是右半段的部分,我们在主窗体 ButtonClicked 函数里触发自定义的信号 SendMsg,然后通过 connect 函数关联,自动调用了接收端对象 s 的槽函数 RecvMsg,并弹窗显示了传递的字符串。
也许有读者会问,费这么大劲,为什么不直接在 ButtonClicked 里面弹窗?那不简单多了?
因为本小节的目的不是弹窗,而是为了展现自定义信号和槽函数的代码写法,理解信号和槽机制的运行流程。以后遇到复杂多窗口的界面程序,在多个窗体对象之间就可以用 上图示范的流程,来进行通信、传递数据。
4 信号关联到信号示例
信号除了可以关联到槽函数,还可以关联到类型匹配的信号,实现信号的接力触发。上个示例中因为 clicked 信号没有参数,而 SendMsg 信号有参数,所以不方便直接关联。本小节示范一个信号到信号的关联,将按钮的 clicked 信号关联到一个参数匹配的 SendVoid 信号。
重新打开 QtCreator,新建一个 Qt Widgets Application 项目,在新建项目的向导里填写:
4.1 发射信号
1创建新按钮
Qt_tset1.h添加我们自定义的信号SendVoid():
2新添加的 SendVoid 信号声明,没有参数,所以能和按钮的 clicked 信号匹配,实现信号到信号的关联。
添加关联函数调用:
仅在构造函数里加了一句 connect 调用,注意 connect 函数第四个参数是 SIGNAL(SendVoid()),这就是关联到信号的用法。以前都是关联到槽函数,这里直接关联到自定义的信号,而不需要槽函数中转。
关联之后,一旦按钮的 clicked 信号触发,主窗体的信号 SendVoid() 紧跟着自动触发,实现信号触发的接力过程。
4.2 接收信号
自定义信号的触发过程编完之后,下面为项目添加新的ShowMsg 类 也是从 QObject 派生
然后我们声明自定义的槽函数,用于接收 SendVoid() 信号,打开 showvoid.h,编辑如下:
#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,添加槽函数实体代码:
#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 文件,添加代码如下:
#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;
}
执行流程如下图所示:
主窗体里将信号关联到信号,是需要大家学会用的。也许有读者会问,为什么不直接将 ui->pushButton 的信号关联到最终的目的端 s 呢?
因为 ui 是主窗体对象 w 的私有成员变量,在类外不可访问,无论是 main 函数还是 ShowVoid 类的代码里,都是看不到 ui->pushButton 这个按钮的,源头都找不到,是没法关联的。
如果把私有变量 ui 改成公有的,那会破坏类的封装性,不建议这么弄。在面对私有成员无法访问的情况下,使用信号接力是比较科学的方法。
Qt 编程指南 3 信号和槽沟通的更多相关文章
- Qt Quick 事件处理之信号与槽
前面两篇文章<QML 语言基础>和<Qt Quick 简单教程>中我们介绍了 QML 语言的基本的语法和 Qt Quick 的常见元素,亲们,通过这两篇文章,您应该已经能够完毕 ...
- Qt对象模型之一:信号和槽
一.信号和槽机制概述 信号槽是 Qt 框架引以为豪的机制之一.所谓信号槽,实际就是观察者模式.当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal).这种发出是没有目 ...
- Qt Quick 事件处理之信号与槽(foruok的博客)
前面两篇文章<QML 语言基础>和<Qt Quick 简单教程>中我们介绍了 QML 语言的基本语法和 Qt Quick 的常见元素,亲们,通过这两篇文章,您应该已经可以完成简 ...
- 第七章 探秘Qt的核心机制-信号与槽
第七章 探秘Qt的核心机制-信号与槽 注:要想使用Qt的核心机制信号与槽,就必须在类的私有数据区声明Q_OBJECT宏,然后会有moc编译器负责读取这个宏进行代码转化,从而使Qt这个特有的机制得到使用 ...
- 2.QT-窗口组件(QWidget),QT坐标系统,初探消息处理(信号与槽)
本章主要内容如下: 1) 窗口组件(QWidget) 2) QT坐标系统 3) 消息处理(信号与槽) 窗口组件(QWidget) 介绍 Qt以组件对象的方式构建图形用户界面 Qt中没有父组件的顶级组件 ...
- Qt 编程指南
Qt 编程指南 持续关注一本正在编写的Qt编程指南,期待作者早日完成创作.
- C/C++ -- Gui编程 -- Qt库的使用 -- 信号与槽的关联
Qt信号与槽的三种关联方法:1.设计界面关联,编辑信号/槽,自动关联 2.手动关联(1).头文件中定义槽 -----mywidget.h----- #ifndef MYWIDGET_H #define ...
- Qt 编程指南 4 按钮
1按钮类的控件 逐个解释一下各个用途:(1)按压按钮 QPushButton最基本的按钮,点击该按钮通常是通知程序进行一个操作,比如弹个窗.下一步.保存.退出等等,这是经常用到的,操作系统里的对话框里 ...
- Qt Quick中的信号与槽
在QML中,在Qt Quick中,要想妥善地处理各种事件,肯定离不开信号与槽,本博的主要内容就是整理Qt 中的信号与槽的内容. 1. 链接QML类型的已知信号 QML中已有类型定义的信号分为两类:一类 ...
随机推荐
- pip更新到18版本后使用pycharm更新问题:module 'pip' has no attribute 'main'
今天升级pip到18.0版本后更新模块时出现错误 分析报错信息可知,问题出在packaging_tool.py文件的213行和109行,找到具体的代码,如下 # 109行函数 def do_insta ...
- [转]微擎人人商城m()函数调用model方法
本文转自:http://yangjunwei.com/a/3177.html 微擎人人商城p()函数可内部调用任意插件的函数 http://yangjunwei.com/a/3216.html
- Sharepoint 2010 工作流状态值
在Sharepoint2010中,如果要使用工作流状态值进行筛选,必须使用内部值,不能使用文字,要不然是筛选不出来的. 进行中:2 已取消:4 已批准:16 拒绝:17 下边是已取消的工作流状态:
- java命令运行带包的类
类文件d:\test2.java package b; public class test2 { public static void main(String[] args) { ...
- Extjs 项目中常用的小技巧,也许你用得着(1)
我在项目中遇到的一些知识点: 1.在GridPanel中显示图片,效果 对应的代码实现 { text: '是否启用', width: 80, // xtype: 'checkcolumn', data ...
- MySQL学习(五) UNION与UNION ALL
UNION用于把来自许多SELECT语句的结果组合到一个结果集合中,也叫联合查询. SELECT ... UNION [ALL | DISTINCT] SELECT ... [UNION [ALL | ...
- 【分布式】1、CAP原则(CAP定理)、BASE理论
CAP原则又称CAP定理,指的是在一个分布式系统中, Consistency(一致性). Availability(可用性).Partition tolerance(分区容错性),三者不可得兼. CA ...
- Nginx学习笔记(三)--- Nginx实现反向代理和配置负载均衡
1.反向代理 2.Nginx反向代理流程图 3.安装多个tomcat 3.1把tomcat的压缩包传到Linux上 3.2 解压tomcat 3.3 给压缩好的tomcat改个名字用来区分一下 3.4 ...
- HDU3567
Eight II Time Limit: 4000/2000 MS (Java/Others) Memory Limit: 130000/65536 K (Java/Others)Total S ...
- canvas-2lineJoin.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...