【QT学习之路】-信号槽

Qt信号槽机制详解

目录

1. 信号和槽概述

1.1. 信号的本质

1.2. 槽的本质

1.3. 信号和槽的关系

2. 标准信号槽的使用

2.1. 标准信号/槽

2.2. 使用

3. 自定义槽函数的使用

3.1. 自定义信号

3.2. 自定义槽函数


内容摘要:文章中主要介绍了Qt中的信号槽, 主要内容包括: 信号槽的本质, 信号槽的关系, 标准信号槽的使用, 自定义信号槽的使用, 信号槽的拓展, Lambda表达式。 

1. 信号和槽概述

信号槽机制是Qt最引以为豪的机制之一,信号槽的本质实际上就是一种观察者模式(发送-订阅)。当某一个事件发生后,例如:有一个按钮组件,当它检测到自己被点击之后,就会发出一个信号(signal),这个信号本身是没有目的性的(类似于广播),但是当有一个对象对这个信号感兴趣时,这个对象就会调用连接(connect)函数,意思是,对象会将自己想要的信号与自己绑定,以此来共同处理这个信号。也就是说,当一个信号产生时,被连接的槽函数会自动回调。


1.1. 信号的本质

信号就是当用户对窗口或者组件进行了相关操作,然后窗口和组件就会产生特定的事件,这个时候Qt的对应窗口和组件就会发出信号。

从上面的描述中我们可以得出一个结论:事件就是信号!!!比如:

  • 单击、双击
    女朋友,“我饿了”,于是我带她去吃饭
    

  • 点击刷新
  • 鼠标的点击与释放
  • 键盘的输入

那么信号是以什么样的形式呈现给使用者的呢?

  • 当使用者对某个窗口进行操作时,这个窗口就会捕捉被触发的事件
  • 也就是说,我们触发了一个事件,就可以得到Qt框架给我们的一个信号
  • 而信号的呈现形式就是函数(某个信号发生了,Qt框架就会调用相应的函数通知我们)

1.2. 槽的本质

在Qt中槽函数是一种具有特殊功能的函数,实际上也是普通的成员函数,只不过它们还有一个作用就是对于Qt框架中的信号进行处理。

下面举一个例子进行说明:

女朋友说,“我饿了”,于是我带她去吃饭。

上面这个例子就说明了,女朋友发出了一个信号,我接收到了并且做出处理,带她去吃饭。

实例对象      
       
角色  描述
女朋友 信号发出者信号携带的信息: 我饿了
我 信号接收者处理女朋友发射的信号: 带她去吃饭

注意:Qt中槽函数属于某一个类的实例对象!


1.3. 信号和槽的关系

其实在Qt中,槽和函数本身并没有任何联系。但是由于某种特性需求我们需要将他们两者联系到一起,而我们这里用到的是QObject类中的connect()函数。

连接信号和槽的connect()函数原型如下, 其中PointerToMemberFunction是一个指向函数地址的指针:

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方法,信号发出之后的处理动作
 
//  参数 signal 和 method 都是函数地址, 因此简化之后的 connect() 如下:
connect(const QObject *sender, &QObject::signal, 
        const QObject *receiver, &QObject::method);

使用connect()函数进行信号槽连接的注意事项:

  • connect()函数只是做了信号处理的注册,换句话说connect()函数并不会立刻执行,只是在Qt框架中定义了一个“订阅关系”(举个例子:当你订阅了新闻频道,频道也并不会立刻给你发消息,只有当重大新闻发生时,才会收到消息)
  • 调用connect()函数的sender对象的信号没有产生,receiver的method方法也不会调用
  • method槽函数是一个回调函数,调用的信号就是信号特定的信号产生之后,由Qt框架来执行
  • 如果sender和receiver两个指针必须实例化,否则connect()函数不会成功

2. 标准信号槽的使用

2.1. 标准信号/槽

  • 在Qt中提供了很多标准类对于特定的事件进行检测,当用户做了这些操作事件被触发类的内部就会产生信号,这些信号都是Qt内部自带的,因此称之为标准信号。
  • 同样的,在Qt的很多类内部为我们提供了很多功能函数,并且这些函数可以作为触发信号的处理动作,这些函数被称作标准槽函数。

那么系统自带的信号和槽函数应该怎么寻找呢?这里我们可以借助帮助文档,例如我们试着看一下

按钮的点击信号(QPushButton):

然后我们输入QPushButton:

然后我们看一下右边文档的目录中有没有signals,但是我们发现好像并没有,此时我们再看一下它的父类继承下来的那些信号:

我们可以看见,QPushButton的父类是继承了信号的:


2.2. 使用

掌握了标准信号、标准信号槽的使用以及connect()函数的作用之后,我们来看一看怎么它们的使用方式。

下面举一个例子:

功能实现: 点击窗口上的按钮, 关闭窗口
功能分析:
	- 按钮: 信号发出者          -> QPushButton 类型
	- 窗口: 信号的接收者和处理者  -> QWidget 类型

需要使用的标准信号、槽函数:

// 单击按钮发出的信号
[signal] void QAbstractButton::clicked(bool checked = false)
// 关闭窗口的槽函数
[slot] bool QWidget::close();

然后我们使用connect()进行连接:

// 单击按钮关闭窗口
connect(ui->closewindow, &QPushButton::clicked, this, &MainWindow::close);

注意:connnect()函数一般写在窗口的构造函数中,这相当于在事件发生之前就在Qt框架中进行注册。这样的话,在程序运行的过程中如果产生了信号,那么就会调用槽函数进行处理,如果没有信号产生的话,槽函数就不会被调用。


3. 自定义槽函数的使用

当Qt提供的一些标准信号和槽函数不能满足我们的需求时,我们可以自己定义信号和槽函数,同样使用connect()函数进行连接。

但是有一些自定义信号和槽函数需要注意的事项:

  • 自定义的类必须让其继承Qt的某些标准类
  • 这个新子类必须从QObject或者QObject的子类继承
  • 在头文件中派生类中需要引入Q_Object宏
// 在头文件派生类的时候,首先像下面那样引入Q_OBJECT宏:
class MyMainWindow : public QWidget
{
    Q_OBJECT
    ......
}

3.1. 自定义信号

  • 信号是类的成员函数
  • 返回值必须是viod类型
  • 信号的名字可以自定义
  • 参数可以也可以自定义(信号也可以重载)
  • 信号函数只需要声明不需要定义(就是没有函数体实现)
// 举例: 信号重载
// Qt中的类想要使用信号槽机制必须要从QObject类派生(直接或间接派生都可以)
class Test : public QObject
{
    Q_OBJECT
signals:
    void testsignal();
	// 参数的作用是数据传递, 谁调用信号函数谁就指定实参
	// 实参最终会被传递给槽函数
    void testsignal(int a);
};

3.2. 自定义槽函数

槽函数就是信号的处理动作,本质可以当做普通的成员函数来使用。如果标准槽函数

  • 返回值必须是void类型的
  • 槽函数本质上也是函数,所以可以重载
  • 槽函数指定多少个参数取决于连接连接信号的参数个数
  • 槽函数的参数是用来接收信号传递的数据的,信号传递的数据就是信号的参数

总结:

  • 槽函数对应的参数个数应该与信号信号传递的参数个数相等,从左到右类型依次对应
  • 信号的参数可以大于槽函数的参数个数(只是会导致数据的丢失)
信号函数: void testsig(int a, double b);
槽函数: void testslot(int a);
  • Qt槽函数的类型可以是多样的,可以是成员函数、全局函数、静态函数、Lambda表达式。
  • 槽函数可以使用关键字进行声明: 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);
};

下面举一个例子:

// class GirlFriend --卸载头文件当中
class GirlFriend : public QObject
{
    Q_OBJECT
public:
    explicit GirlFriend(QObject *parent = nullptr);

signals:
    void hungry();	            // 不能表达出想要吃什么
    void hungry(QString msg);	// 可以通过参数表达想要吃什么
};

// class Me
class Me : public QObject
{
    Q_OBJECT
public:
    explicit Me(QObject *parent = nullptr);

public slots:
    // 槽函数
    void eatMeal();             // 不能知道信号发出者要吃什么
    void eatMeal(QString msg);  // 可以知道信号发出者要吃什么
};

然后使用connect()函数进行连接:

// 写在int main()中
// 连接两个重载信号/槽。Qt5+ 推荐用函数指针语法 
       QObject::connect(&girl, QOverload<>::of(&GirlFriend::hungry),
                         &me,   QOverload<>::of(&Me::eatMeal));          // 无参版本

       QObject::connect(&girl, QOverload<QString>::of(&GirlFriend::hungry),
                         &me,   QOverload<QString>::of(&Me::eatMeal));      // 有参版本

运行结果:


(本篇完)

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值