• Qt 信号与槽 讲解与案例


    信号与槽

    所谓信号槽,实际就是观察者模式(发布-订阅模式)。
    当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,意思是,将想要处理的信号和自己的一个函数(称为槽(slot))绑定来处理这个信号。
    也就是说,当信号发出时,被连接的槽函数会自动被回调。这就类似观察者模式:当发生了感兴趣的事件,某一个操作就会被自动触发。


    信号是由对象发射出去的消息,信号实际上是一个特殊的函数,不需要由程序员实现,而是由Qt的Qt Meta Object System实现。

    实际上就是普通的函数,成员函数、全局函数、静态函数、lambda函数都可以!

    当我们把对象的信号和槽绑定在一起之后,当信号触发时,与之绑定的槽函数将会自动调用,并把信号的参数传递给槽函数!

    绑定信号与槽

    connect 函数

     [static] QMetaObject::Connection connect(
         const QObject *sender, 
         const QMetaMethod &signal, 
         const QObject *receiver, 
         const QMetaMethod &method,
     	, Qt::ConnectionType type = Qt::AutoConnection)
         
     [static] QMetaObject::Connection connect(
         const QObject *sender, 
         PointerToMemberFunction signal, 
         Functor functor)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    解析:

    • sender: 信号的发送者
    • signal: 发送的信号
    • receiver: 信号接收者
    • method:接受到信号之后,自动调用的方法(槽函数)
    • type: 表示信号与槽的连接类型,有默认值

    简单案例:按钮的点击

    关于QT的简单窗口实现代码,请看这篇:
    编写第一个Qt窗口

    我们首先包含头文件:

    #include < QPushButton >

    我们就可以使用QPushButton作为我们的按钮了。

    Widget::Widget(QWidget* parent)
    	:QWidget(parent)
    {
    	//设置窗口的大小
    	resize(200, 200);
    	QPushButton* btn = new QPushButton("我是个按钮", this);
    
    	connect(btn, &QPushButton::pressed, this, &Widget::close);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    connect:

    • btn:作为信号的发送者
    • &QPushButton::pressed:信号函数,表示你pressed(点击)
    • this:信号的接收者,表示的就是Widget这个窗口
    • &Widget::close:槽函数,表示接收信号之后准备干什么,即close掉这个窗口。

    效果如下:
    在这里插入图片描述
    在这里插入图片描述
    我们使用按钮控制窗口的退出就实现了!

    我们也可以控制点击按钮使得按钮本身close:

    connect(btn, &QPushButton::pressed, btn, &QPushButton::close);
    
    
    • 1
    • 2

    即信号可以被自身接收,然后调用自身的槽函数,使得自身关闭。


    自定义槽函数

    定义槽函数必须遵循以下规则

    1. 槽函数的返回类型必须是void类型,不能是其他类型;

    2. 槽函数的参数必须等于或少于信号的参数

    类中的槽成员函数可以使用:

    public slots:
    	//槽函数声明
    
    • 1
    • 2

    槽函数的类型:

    • 成员函数
      • 普通成员函数
      • 静态成员函数
    • 全局函数
    • lambda表达式(匿名函数)
    void global_func();
    Widget::Widget(QWidget *parent)
        : QWidget(parent)
    {
        QPushButton *btn = new QPushButton(this);
        //连接标准槽函数
        connect(btn,&QPushButton::clicked,self,&Widget::close);
    	//连接普通成员函数
        connect(btn,&QPushButton::clicked,this,&Widget::member_func);
    	//连接静态成员函数
        connect(btn,&QPushButton::clicked,this,&Widget::static_func);
        //连接全局函数
        connect(btn,&QPushButton::clicked,this,&Widget::global_func);    
    	//连接lambda表达式
        connect(btn,&QPushButton::clicked,this,[=]()
                {
                   qInfo()<<"lambda"; 
                   this->close(); 
                });    
     
    }
    //普通成员函数
    void Widget::member_func()
    {
        this->close();
    }
    //静态成员函数
    void Widget::static_func(bool checked)
    {
        qInfo()<<"static_func"<<checked;
    }
    //全局函数
    void global_func()
    {
        qInfo()<<"global_func";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    如果你想在槽中知道是哪个对象触发的信号,那么你可以使用 QObject *sender() const函数获取,信号的发送者。


    自定义信号

    如果想要使用自定义的信号和槽, 首先要编写新的类并且让其继承Qt的某些标准类,我们自己编写的类想要在Qt中使用使用信号槽机制, 那么必须要满足的如下条件:

    • 这个类必须从QObject类或者是其子类进行派生
    • 在定义类的第一行头文件中加入 Q_OBJECT 宏
    // 在头文件派生类的时候,首先像下面那样引入Q_OBJECT宏:
    class MyMainWindow : public QWidget
    {
        Q_OBJECT
    public:
        ......
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    如果是单文件编写的,还需要在代码的最下面加上#include "name.moc",name是指原文件的名称。

    自定义信号需要遵循以下规则:

    • 信号是类的成员函数,并且返回类型必须是 void 类型

    • 信号函数只需要声明, 不需要定义(没有函数体实现)

    • 参数可以随意指定, 信号也支持重载

    • 信号需要使用 signals 关键字进行声明, 使用方法类似于public等关键字

    • 在程序中发送自定义信号: 发送信号的本质就是调用信号函数

      emit mysignals();	//发送信号
      
      • 1

      emit是一个空宏,没有特殊含义,仅用来表示这个语句是发射一个信号


    示例:

    当我们点击第一个按钮的时候,第二个按钮会消失,但是我们使用自定义信号的方式

    class Widget :public QWidget
    {
    	...
    signals:
    	void MySignalFunc();
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    btnTemp->setGeometry(100, 100, 100, 20);
    
    connect(btn, &QPushButton::pressed,this,&Widget::MySignalFunc);
    
    connect(this, &Widget::MySignalFunc, btnTemp, &QPushButton::close);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    解析:我们又创建了一个用于展示效果的临时按钮

    • 第一个connect: btn发送pressed的消息,我们的主窗口会接收这个信号,并且这是我们自定义的 MySignalFunc信号。
    • 第二个connect:主窗口接收到MySignalFunc信号之后,会再次发出这个信号,使得btnTemp按钮调用close的函数,使得Temp按钮关闭。
      在这里插入图片描述

    信号参数的作用是数据传递, 谁调用信号函数谁就指定实参,实参最终会被传递给槽函数。

    信号和槽重载二义性问题

    信号和槽是支持重载的,那么,我们就会想到,当我们在connect的时候,会不会出现二义性的问题? 到底会调用哪个呢?

    信号的重载二义性:

    class SubWindow :public QWidget
    {	
    	...
    signals:	
    	void OnButtonCutMainWindow();
    	void OnButtonCutMainWindow(const QString& str)
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    槽的重载二义性:

    class Widget :public QWidget
    {
    	...
    public slots:
    	//响应按钮信号的槽函数
    	void OnButtonCutWindow();
    	void OnButtonCutWindow(const QString&);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    connect(btn,&SubWidget::OnButtonCutMainWindow,this,&Widget::OnButtonCutWindow);
    
    • 1

    我们应该调用哪个呢??


    解决方案

    • 一,通过函数指针(类的成员函数当作函数指针)解决
    //信号
    void (SubWindow:: * GetNone1)() = &SubWindow::OnButtonCutMainWindow;
    void (SubWindow:: * GetOne1)(const QString&) = &SubWindow::OnButtonCutMainWindow;
    //槽
    void (Widget:: * GetNone2)() = &Widget::OnButtonCutWindow;
    void (Widget:: * GetOne2)(const QString&) = &Widget::OnButtonCutWindow;
    //无参连接
    connect(SubWidget, GetNone1, this,GetNone2);
    //有参连接
    connect(SubWidget, GetOne1, this, GetOne2);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 二,通过Qt提供的重载类(QOverload)解决
    //2. QOverload解决
    //无参连接
    connect(SubWidget, QOverload<>::of(&SubWindow::OnButtonCutMainWindow),
    		this, QOverload<>::of(&Widget::OnButtonCutWindow));
    //有参连接
    connect(SubWidget, QOverload<const QString&>::of(&SubWindow::OnButtonCutMainWindow),
    		this, QOverload<const QString&>::of(&Widget::OnButtonCutWindow));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 三,Qt4的连接方式

    使用Qt4的SIGNAL 和 SLOT 宏的形式:

    //3. Qt4的宏解决
    connect(SubWidget, SIGNAL(OnButtonCutMainWindow()), this, SLOT(OnButtonCutWindow()));
    connect(SubWidget, SIGNAL(OnButtonCutMainWindow(const QString&)), this, SLOT(OnButtonCutWindow(const QString&)))
    • 1
    • 2
    • 3
    • 总结

      • Qt4的信号槽连接方式因为使用了宏函数, 宏函数对用户传递的信号槽不会做错误检测, 容易出bug
      • Qt5的信号槽连接方式, 传递的是信号槽函数的地址, 编译器会做错误检测, 减少了bug的产生
      • 当信号槽函数被重载之后, Qt4的信号槽连接方式不受影响
      • 当信号槽函数被重载之后, Qt6中需要给被重载的信号或者槽定义函数指针

    推荐使用方法二。

    案例: 实现两个窗口间的切换

    父窗口头文件

    #ifndef _WIDGET_H_
    #define _WIDGET_H_
    
    #include 
    #include 
    #include 
    #include "SubWindow.h"
    class SubWindow;
    class Widget :public QWidget
    {
    	Q_OBJECT
    public:
    	Widget(QWidget* parent = nullptr);
    	~Widget();
    public slots:
    	//响应按钮信号的槽函数
    	void OnButtonCutWindow();
    	void OnButtonCutWindow(const QString&);
    private:
    	SubWindow* SubWidget;
    };
    
    #endif // !_WIDGET_H_
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    父窗口的实现:

    #include "Widget.h"
    #include 
    Widget::Widget(QWidget* parent)
    	:QWidget(parent),SubWidget(new SubWindow)
    {
    	resize(250, 250);
    	QPushButton* btn = new QPushButton("点击切换子窗口", this);
    	//点击按钮 切换子窗口
    	connect(btn, &QPushButton::pressed,SubWidget, &SubWindow::show);
    	//当按钮released(释放时),隐藏hide父窗口
    	connect(btn, &QPushButton::released, this, &Widget::hide);
    	
    	//子窗口会发送信号,父窗口接收此信号,提示父窗口将要show
    	connect(SubWidget, QOverload<>::of(&SubWindow::OnButtonCutMainWindow),
    		this, QOverload<>::of(&Widget::OnButtonCutWindow));
    	connect(SubWidget, QOverload<const QString&>::of(&SubWindow::OnButtonCutMainWindow),
    		this, QOverload<const QString&>::of(&Widget::OnButtonCutWindow));
    }
    
    Widget::~Widget()
    {
    }
    
    void Widget::OnButtonCutWindow(const QString& Info)
    {
    	//随便打印一个信息
    	qInfo() << Info;
    }
    
    
    void Widget::OnButtonCutWindow()
    {
    	//子窗口隐藏
    	SubWidget->hide();
    	//主窗口显示
    	this->show();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    子窗口头文件:

    #ifndef _SUBWINDOW_H_
    #define _SUBWINDOW_H_
    
    #include "Widget.h"
    
    class SubWindow :public QWidget
    {
    	Q_OBJECT
    public:
    	SubWindow(QWidget* parent = nullptr);
    signals:	
    	void OnButtonCutMainWindow();
    	void OnButtonCutMainWindow(const QString& str);
    
    };
    
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    子窗口的实现文件:

    #include "SubWindow.h"
    
    SubWindow::SubWindow(QWidget* parent)
    	:QWidget(parent)
    {
    	resize(400, 400);
    
    	QPushButton* SubBtn = new QPushButton("点击切换主窗口", this);
    	
    	//点击按钮,切换回主窗口,会发送切换的信号,等待主窗口的接收。
    	connect(SubBtn, &QPushButton::clicked, this, [=]() {
    		emit this->OnButtonCutMainWindow();
    		emit this->OnButtonCutMainWindow("我不想换!!");
    		});
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    main函数:

    #include 
    #include "Widget.h"
    #include 
    
    int main(int argc, char* argv[])
    {
    	QApplication a(argc, argv);
    
    	Widget w;
    	w.show();
    	return a.exec();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    运行如下:
    两个窗口可以相互切换。。。

    在这里插入图片描述
    在这里插入图片描述

  • 相关阅读:
    React学习5(React class 组件)
    cms系统稳定性压力测试出现TPS抖动和毛刺的性能bug【杭州多测师_王sir】
    深入理解python虚拟机:程序执行的载体——栈帧
    数据挖掘实战(2):信用卡诈骗分析
    ENVI实现QUAC、简化黑暗像元、FLAASH方法的遥感影像大气校正
    python+nodejs+vue酒店点餐饮系统项目
    谈谈Kafka、ActiveMQ、RabbitMQ、RocketMQ 区别以及高可用实现
    mysql锁
    右值引用(rvalue reference)
    Redash和Metabase深度比较之四:可视化种类
  • 原文地址:https://blog.csdn.net/jj6666djdbbd/article/details/127443161