当前位置 博文首页 > 进击的汪sir:Qt自定义信号槽的使用浅析+实例

    进击的汪sir:Qt自定义信号槽的使用浅析+实例

    作者:进击的汪sir 时间:2021-06-13 18:22

    1. Qt中自定义信号槽的使用

    Qt框架提供的信号槽在某些特定场景下是无法满足我们的项目需求的,因此我们还设计自己需要的的信号和槽,使用connect()对自定义的信号槽进行连接。

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

    • 这个类必须从QObject类或者是其子类进行派生
    • 在定义类的头文件中加入 Q_OBJECT 宏

    1.1 自定义信号

    要求:
    1. 信号是类的成员函数
    2. 返回值是 void 类型
    3. 信号的名字可以根据实际情况进行指定
    4. 参数可以随意指定, 信号也支持重载
    5. 信号需要使用 signals 关键字进行声明, 使用方法类似于public等关键字
    6. 信号函数只需要声明, 不需要定义(没有函数体实现)
    7. 在程序中发送自定义信号: 发送信号的本质就是调用信号函数

    • 习惯性在信号函数前加关键字: emit
    • emit只是显示的声明一下信号要被发送, 没有特殊含义
    • 底层 emit == #define emit

    示例

    class Test : public QObject
    {
        Q_OBJECT
    signals:
        void testsignal();
    	// 参数的作用是数据传递, 谁调用信号函数谁就指定实参
    	// 实参最终会被传递给槽函数
        void testsignal(int a);
    };
    

    1.2 自定义槽

    槽函数就是信号的处理动作,自定义槽函数和自定义的普通函数写法是一样的。

    要求:

    1. 返回值是 void 类型
    2. 槽也是函数, 因此也支持重载
    • 槽函数需要指定多少个参数, 需要看连接的信号的参数个数
    • 槽函数的参数是用来接收信号发送的数据的, 信号发送的数据就是信号的参数
    • 举例:
      信号函数: void testsig(int a, double b);
      槽函数: void testslot(int a, double b);
    • 总结:
      槽函数的参数应该和对应的信号的参数个数, 类型一一对应
      信号的参数可以大于等于槽函数的参数个数 == 信号传递的数据被忽略了
      信号函数: void testsig(int a, double b);
      槽函数: void testslot(int a);
      这里槽函数只接受信号函数中的第一个参数
    1. Qt中槽函数的类型:
      - 类的成员函数
      - 全局函数
      - 静态函数
      - lambda表达式(匿名函数)
    2. 槽函数可以使用关键字进行声明: slots (Qt5中slots可以省略不写)
      - public slots:
      - private slots:
      - protected slots:
    // 举例
    // 类中的这三个函数都可以作为槽函数来使用
    class Test : public QObject
    {
    public:
        void testSlot();
        static void testFunc();
    
    public slots:
        void testSlot(int id);
    };
    

    1.3 自定义信号槽实例

    现在有一个场景,女朋友饿了,我请她吃饭,那么实现这个功能应该怎么做呢
    首先明确发送者,接收者,信号和槽分别是哪些

    • 发送者: 女朋友
    • 接收者: 我
    • 信号: 饿了
    • 槽:请她吃饭

    ok,明确了这些,接下来我们就可以开始写代码了

    首先创建两个类,GirlFriend 和 Me
    Qt Creator中会自动为我们添加头文件和CPP文件,目录结构如下图
    在这里插入图片描述

    1. 在GirlFriend类中,添加信号hungry,代码如下
    #ifndef GIRLFRIEND_H
    #define GIRLFRIEND_H
    
    #include <QObject>
    
    class GirlFriend : public QObject
    {
        Q_OBJECT
    public:
        explicit GirlFriend(QObject *parent = nullptr);
    
    signals:
        void hungry();
    
    };
    
    #endif // GIRLFRIEND_H
    

    注意图中的 signals关键字,这个就是用来定义信号的地方,信号函数只需要定义,不需要实现!

    1. 在Me这个类中添加槽函数eat();
    #ifndef ME_H
    #define ME_H
    
    #include <QObject>
    
    class Me : public QObject
    {
        Q_OBJECT
    public:
        explicit Me(QObject *parent = nullptr);
    
        // 槽函数
    public slots:
        // 槽函数
        void eat();
    
    };
    
    #endif // ME_H
    
    

    注意!
    这里用public slots主要是为了提醒开发者,这是一个槽函数,事实上,可以不用单独用public slots,可以直接将这个槽函数放到public中,与普通函数一样,槽函数不仅需要定义,也需要实现。

    1. 到me.cpp中实现Me类的槽函数eat()
    #include "me.h"
    #include <QDebug>
    Me::Me(QObject *parent) : QObject(parent)
    {
    
    }
    
    void Me::eat()
    {
        qDebug() << "带你去吃麻辣烫...";
    }
    

    ok,现在槽函数和信号函数都已经定义实现了,那怎么样实现事件的响应呢,一个简单的想法是,设置一个按钮,点击按钮发送信号:hungry,然后让eat()响应

    1. 到mainwindow中添加一个按钮Hungry,取名为hungry
      在这里插入图片描述
      如果这个时候出现在mainwindow.cpp中,无法识别这个按钮,可以参考我的这篇博客
      Qt项目ui文件新添加的控件在代码中不识别的问题解决

    2. 添加这个按钮后,我们需要在mainwindow类中添加两个成员指针
      在这里插入图片描述

    3. 在mainwindow.cpp中通过connect函数来绑定

    在这里我再复习一遍Qt中的 connect() 函数

    QMetaObject::Connection QObject::connect(
        	const QObject *sender, PointerToMemberFunction signal, 
            const QObject *receiver, PointerToMemberFunction method, 
    		Qt::ConnectionType type = Qt::AutoConnection);
    - 参数:
    	- sender: 发出信号的对象
    	- signal: 属于sender对象, 信号是一个函数, 这个参数的类型是函数指针, 信号函数地址
        - receiver: 信号接收者
    	- method: 属于receiver对象, 当检测到sender发出了signal信号, 
                  receiver对象调用method方法,信号发出之后的处理动作
                      
    // connect函数相对于做了信号处理动作的注册
    // 调用conenct函数的sender对象的信号并没有产生, 因此receiver对象的method也不会被调用
    // method槽函数本质是一个回调函数, 调用的时机是信号产生之后, 调用是Qt框架来执行的
    // connect中的sender和recever两个指针必须被实例化了, 否则conenct不会成功
    connect(const QObject *sender, &QObject::signal, 
            const QObject *receiver, &QObject::method);
    

    知道connect函数的用法之后,我们先将my_girl发送信号,m_me(m_girl和m_me 是上面加的两个成员指针)接受信号绑定在一起
    在mainwindow.cpp的构造函数中添加如下语句

    	m_me = new Me;
        m_girl = new GirlFriend;
    
        // hungry信号是自定义的,它不能由框架去发送,因为框架压根就不知道有这个信号的存在,因此需要在特定的时机,使用者自己去发射这个信号
        connect(m_girl,&GirlFriend::hungry,m_me,&Me::eat);
        
    

    注意connect上面的注释
    hungry信号是自定义的,它不能由框架去发送,因为框架压根就不知道有这个信号的存在,因此需要在特定的时机,使用者自己去发射这个信号

    简单理解就是,你的girl要发送hungry这个信号是不能自动完成的,因为Qt框架不知道hungry这个信号,你要通过点击按钮来让girl发送信号,因此需要另外一个函数,取名为hungrySlot(),用来实现点击按钮,让girl发送信号

    1. 在mainwindow中定义并实现函数hungrySlot,函数定义的代码我就不放了,大家自己去定义
      下面是实现代码
    void MainWindow::hungrySlot()
    {
        // 发射自定义信号
        emit m_girl->hungry();
    }
    

    注意!
    这里的emit关键字也是可有可不有的 ,但是还是建议大家写,用来提醒开发人员这个函数是发射自定义信号的函数

    1. 现在是最关键的一步啦,将按钮与girl发射hungry信号的函数绑定,按钮被点击这个事件是可以由框架实现发送的,所以不需要我们担心

    在mainwindow的构造函数中绑定,代码如下

    
    connect(ui->hungry,&QPushButton::clicked,this,&MainWindow::hungrySlot);
    
    

    这样,自定义信号槽的使用就欧克啦

    接下来我们去测试一下,run一手

    1. 运行结果

    在这里插入图片描述
    可以看到,每次当我点击Hungry按钮,底下就会出现 “带你去吃麻辣烫”,证明我们自定义信号槽成功了

    编写不易,大家要是转载啥的记得标明一下哦~

    bk