概述

信号

  • 信号的本质就是事件
    • 按钮单击、双击
    • 窗口刷新
    • 鼠标移动、鼠标按下、鼠标释放
    • 键盘输入
  • 信号的呈现形式就是函数, 也就是说某个事件产生了, Qt 框架就会调用某个对应的信号函数, 通知使用者
  • 信号的发出者是某个实例化的类对象,对象内部可以进行相关事件的检测

  • 本质是一个函数,是一个对信号做处理的一个函数,就是信号发送给当前接受方,接收方需要干什么
  • 槽函数的所有者也是某个类的实例对象,很好理解,对象就是知指明是谁去执行这个槽函数,就是接收方,而不是所有的对象都能执行

信号和槽的关系

  • 也就是上述所说,需要指定接收方,而 QT 框架并不知道信号所对应的接收方是谁,这时候我们需要手动连接双方,告诉编译器发送方是谁,发送的什么信号,接收方是谁,接收方收到信号后干什么
  • 相当于注册功能
//  参数 signal 和 method 都是函数地址, 因此简化之后的 connect() 如下:
connect(const QObject *sender, &QObject::signal, 
        const QObject *receiver, &QObject::method);
/*
  - sender:   发出信号的对象
  - signal:   属于sender对象, 信号是一个函数, 这个参数的类型是函数
              指针, 信号函数地址
  - receiver: 信号接收者
  - method:   属于receiver对象, 当检测到sender发出了signal信号, 
              receiver对象调用method方法,信号发出之后的处理动作
*/
  • 流程:连接之后,我们只需要书写信号函数和槽函数,指明各个对象,函数的调用是不需要我们手动调用的,这里对象很多种,比如各个控件,按钮,窗口
  • 此时编译器会一直处于检测状态,当检测到发送方发送了信号,找到注册的信息,找到对应的接收方对象,然后执行槽函数

标准信号槽使用

使用的函数

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

需求是单击按钮关闭当前窗口

// 这个实现是在某个窗口的构造函数实现
// closewindow 是 ui 界面下自定义的 QPushButton 按钮,名字为 closewindow
// 所以发送方是 ui->closewindow,因为要关闭当前窗口,所以是 this
// 接收方要执行的动作是关闭窗口,所以 &MainWindow::close
connect(ui->closewindow, &QPushButton::clicked, this, &MainWindow::close);

自定义信号槽使用

需要满足一些条件:

  • 要编写新的类并且让其继承 Qt 的某些标准类
  • 这个新的子类必须从 QObject 类或者是 QObject 子类进行派生,比如继承 QWidget
  • 在定义类的头文件中加入 Q_OBJECT

自定义信号

  • 信号是类的成员函数
  • 返回值必须是 void 类型
  • 参数可以随意指定,信号也支持重载
  • 信号需要使用 signals 关键字进行声明,使用方法类似于 public 等关键字
  • 信号函数只需要声明,不需要定义 (没有函数体实现)
  • 在程序中发射自定义信号:发送信号的本质就是调用信号函数
    • emit 关键字可以省略,因为底层 emit 等价于空,使用的 define 定于 emit 为空,但是写上更具有可读性,表示发送信号
    • 底层 emit --> #define emit 空(这里方便显示,写空)

自定义槽

  • 返回值必须是 void 类型
  • 槽也是函数,因此也支持重载
  • 槽函数需要指定多少个参数,小于等于信号函数的参数个数,因为槽函数是对数据的接收,可以不接收某些参数,但是不能多于信号传递的参数
  • 槽函数的参数是用来接收信号传递的数据的, 信号传递的数据就是信号的参数
  • 槽函数可以是类的成员函数、全局函数、静态函数、Lambda 表达式(匿名函数)
  • 槽函数可以使用关键字进行声明:slots (Qt 5 中 slots 可以省略不写)
public slots:
private slots:    // 这样的槽函数不能在类外部被调用
protected slots:  // 这样的槽函数不能在类外部被调用

示例

需求:实现女朋友饿了,我带她吃东西

  • 为了方便,就创建了 GirlfriendMe
  • 首先在头文件定义信号函数和槽函数
// Grifriend 类中
signals:
    void hungry();   // 这个不写执行体
// Me 类中
public slots:
    void eat();
// Me cpp 中
void Me::eat() {
    qDebug() << "我带你去吃汉堡" << endl;
}
  • 如上已经封装好 GrilfriendMe 类了,此时需要考虑连接问题,要让它们同时出现,才能连接,一般考虑定义对象为某个类的成员变量
  • 首先就需要创建对象,看我们在哪里使用,如果我这里需求是在当前 MainWindow 窗口使用,那么定义这两个类对象为 MainWindow 的成员函数
// MainWindow 头文件
private:
    Ui::MainWindow *ui;
    Girlfriend *m_girl;
    Me *m_me;
};
// MainWindow 的 cpp 文件中,构造函数中实现连接
// QT 好多这些基础都在构造函数实现,hh,实例化对象就实现这些功能,也可
m_me = new Me;
m_girl = new Girlfriend;
connect(m_girl, &Girlfriend::hungry, m_me, &Me::eat);
  • 问题又来了,这个自定义信号 hungry 不能由 QT 框架去发生信号,框架不知道这个信号存在,需要使用者自己去发送信号,这里就给个按钮作为发送对象,点击它作为发送信号,槽函数的实现就是发送 hungry 信号(还可信号连接信号)
// MainWindow 头文件中声明,slots 关键字 QT5 中可省略
public:
    void hungrysolt();

// 实现
void MainWindow::hungeysolt() {
    emit m_girl->hungry();
}

// 连接,这个 hungry 和上述标准信号槽使用一致
connect(ui->hungry, &QPushButton::clicked, this, &MainWindow::hungeysolt);
// 信号连接信号
connect(ui->hungry, &QPushButton::clicked, m_girl, &Girlfriend::hungry);

信号槽扩展

使用

  • 一个信号可以连接多个槽函数, 发送一个信号有多个处理动作
    • 需要写多个 connect() 连接
    • 槽函数的执行顺序是随机的, 和 connect 函数的调用顺序没有关系
    • 信号的接收者可以是一个对象,也可以是多个对象
  • 一个槽函数可以连接多个信号, 多个不同的信号, 处理动作是相同的
    • 需要写多个 connect() 连接
  • 信号可以连接信号
  • 信号槽是可以断开的
disconnect(const QObject *sender, &QObject:: signal, 
        const QObject *receiver, &QObject::method);

连接方式

上述介绍的都是 QT5 的方式

// Qt4的信号槽连接方式(简化版)
connect(const QObject *sender,SIGNAL(信号函数名(参数1, 参数2, ...)),
        const QObject *receiver,SLOT(槽函数名(参数1, 参数2, ...)));

对上述的自定义示例的改变:

connect(m_girl, SIGNAL(hungry()), m_me, SLOT(eat()));

对于上述 QT5 形式信号槽的相关容错提示

signals:
    void hungry();  
    void hungry(QString msg);  
  • 因为信号函数可以重载,那么还以上述 connect(m_girl, &Girlfriend::hungry, m_me, &Me::eat); 就错误了,因为重载,编译器就不知道这个函数地址是哪个了
  • 传参的话就是在发送 hungry 函数部分传参了
  • 解决方案一
// 使用 QT4
// 无参的
connect(m_girl, SIGNAL(hungry()), m_me, SLOT(eat()));
// 有参数从的
connect(m_girl, SIGNAL(hungry(QString)), m_me, SLOT(eat(QString)));
// 使用 QT5 先定义函数指针
void (GirlFriend::*girl1)() = &GirlFriend:: hungry;
void (GirlFriend::*girl2)(Qstring) = &GirlFriend::hungry;
void (Me::*me1)() = &Me::eat;
void (Me::*me2)(Qstring) = &Me::eat;
connect(m_girl, girl1, m_me, me1));
connect(m_girl, girl2, m_me, me2));

总结

  • 对于有重载版本的信号槽,看似 QT4 更有优势

  • 但是需注意的是 QT4 这种形式,SIGNAL 宏传参数的时候不会检查语法错误,如果写错函数名、参数名也不会报错,就会导致隐患

  • 而虽然 QT5 稍显麻烦,但是定义函数指针的时候,如果书写错误,会检查出来,报语法错误


说明:参考 https://subingwen.cn/

只管努力,剩下的交给天意