zoukankan      html  css  js  c++  java
  • 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 改成公有的,那会破坏类的封装性,不建议这么弄。在面对私有成员无法访问的情况下,使用信号接力是比较科学的方法。

  • 相关阅读:
    运维笔记--ubuntu rm删除文件后 恢复
    运维笔记--阿里云服务器系统盘扩容
    运维笔记--阿里云服务器数据盘扩容
    odoo开发笔记--开启后台日志记录
    分布式和集群的区别
    ubuntu16.04 离线安装nginx
    Sqlserver的Transaction做Rollback的时候要小心(转载)
    关于调用方有事务,被调用的SP中也有事务,在嵌套SP中回滚代码的报错处理,好文推荐
    Oracle中row_number()、rank()、dense_rank() 的区别
    ESB企业服务总线到底是什么东西呢?
  • 原文地址:https://www.cnblogs.com/kekeoutlook/p/7468212.html
Copyright © 2011-2022 走看看