【Qt】信号与槽

时间:2024-07-20 14:04:10

文章目录

  • 信号与槽
    • 1. connect
    • 2. 槽 slot
    • 3. 信号 signal
    • 4. 带参数的信号和槽
    • 5. 信号槽机制的意义


信号与槽

在Qt中,用户和控件的每一次交互都称为一个“事件”。比如,用户点击按钮,用户关闭窗口。每一次事件的发生,都会触发一个信号。信号的作用就是用来区分用户的操作类型,并针对用户的操作进行不同的处理,这种不同的处理就是

信号源:信号是由谁发出的?

信号类型:不同信号的类型

接收者:信号由谁处理?

:对信号作出的响应动作

1. connect

Qt中提供connect函数,用于绑定信号与槽,即先准备好信号的处理方式,才能在与用户交互过程中处理对应的信号。

QObject::connect
    (const QObject *sender,
     const char *signal, 
     const QObject *receiver, 
     const char *method, 
     Qt::ConnectionType type = Qt::AutoConnection)

sender: 信号源

signal: 信号类型

receiver: 负责信号接收处理者

method: 处理信号的方式

上面是旧版本Qt中的connect函数,因为signal和method的参数类型都是char*,但信号函数和槽函数的类型是变化的,所以每次传参还需要调用SIGNAL()SLOT()宏函数将函数指针转换为char*类型,新版本Qt提供了connect函数的泛型版本:

template <typename Func1, typename Func2>
static inline QMetaObject::Connection connect(
    const typename QtPrivate::FunctionPointer<Func1>::Object *sender, 
    Func1 signal,
	const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, 
    Func2 slot,
    Qt::ConnectionType type = Qt::AutoConnection)
  1. 这是一个模板函数,signalslot的类型由传入参数决定。
  2. senderreceiver分别是Func1Func2类型萃取后的结果,保证了sender中必须包含signal信号,receiver中必须包含slot槽函数,若不存在对应的信号或槽,程序报错,避免程序员手动检查

注意:

  1. click是一个槽函数,clicked是一个信号

  2. 一个信号绑定多个槽函数时,信号发生,多个槽函数同时被调用。

  3. disconnectconnect作用相反,断开信号与槽的连接,使用方式与connect类似。一般使用disconnect主动断开信号与槽的连接,是为了将信号绑定到另一个槽函数上。

2. 槽 slot

槽本质上就是一个成员函数。槽函数不仅有Qt内置的,还可由用户自定义,这是开发中的关键,因为不同的业务,对于用户的交互有不同的处理逻辑。那么,如何定义槽函数呢?

  1. 跟定义一个普通的成员函数没有区别

    widget.h

    image-20240517170525995

    widget.cpp

    image-20240517170637463

  2. 图形化操作,对于生成的控件,选择信号,并自动生成槽函数的声明和槽函数的定义,槽函数的函数体由用户设计(默认为什么都不做),而信号与槽函数的绑定是由Qt自动完成的(connectSlotsByName)。

    image-20240517172949289

image-20240517172627355

最终ui_widget.h中调用connectSlotsByName()函数,根据槽函数名称,实现槽函数与信号之间的绑定

image-20240517172804395

3. 信号 signal

信号本质上也是一个函数,而且也可以由用户自定义,不过自定义信号比较少见,一般Qt内置的信号类型就足以应对大部分开发场景。emit 通过代码发射信号

  1. 自定义类Widget的信号函数mySignal(),记得加上signals关键字修饰(函数的定义是Qt生成的,我们只需写函数声明)

image-20240519143602061

  1. 写一个槽函数handleMySignal(),用于处理自定义信号

    void Widget::handleMySignal()
    {
        this->setWindowTitle("mySignal信号:修改标题");
    }
    
  2. 调用connect连接自定义的信号和槽

    connect(this, &Widget::mySignal, this, &Widget::handleMySignal); // Widget类的构造函数中
    
  3. 随便整一个按钮(这里用了图形化界面生成),点击它会发射自定义信号mySignal,验证效果

    void Widget::on_pushButton_2_clicked()
    {
        emit mySignal(); //发射自定义信号, emit可以省略
    }
    

4. 带参数的信号和槽

  1. 信号函数可以向槽函数传递参数,以实现代码复用;

  2. 信号函数的参数类型必须和槽函数的参数类型一致,才能传递,否则会报错;

  3. 信号函数的参数个数 >= 槽函数的参数个数,这使得信号与槽之间的连接更加灵活,即一个槽函数可以与被多个不同的信号函数连接,只要保证每个信号函数传递的参数不少于槽函数的参数个数即可,槽函数会按顺序取前N个(N是槽函数的参数个数),多余的参数自动丢弃;

  4. 带参数的信号与槽

//助于理解的伪代码
void Widget::mySignal1(QString t1); //signal

void handleMySignal(const QString& s); //slot
void Widget::handleMySignal(const QString& s)
{
    qDebug() << s;
}

connect(this, &Widget::mySignal1, this, &Widget::handleMySignal); //连接信号槽


void Widget::on_pushButton_2_clicked() //发射信号
{
    emit mySignal1("mySignal1");
}

最后执行情况:

image-20240519150113328

  1. 参数个数不一致的信号与槽

    //助于理解的伪代码
    void Widget::mySignal1(QString t1); //signal
    void Widget::mySignal2(QString t1, QString t2);//signal
    
    void handleMySignal(const QString& s); //slot
    void Widget::handleMySignal(const QString& s)
    {
        qDebug() << s;
    }
    
    connect(this, &Widget::mySignal1, this, &Widget::handleMySignal); //连接信号槽
    connect(this, &Widget::mySignal2, this, &Widget::handleMySignal);
    
    void Widget::on_pushButton_2_clicked() //发射信号
    {
        emit mySignal1("mySignal1");
        emit mySignal2("mySignal2", "mySignal2");
    }
    

    最终执行结果,可以看到发送mySignal2也是只打印一次,因为槽函数handleMySignal只取mySignal2第一个参数

    image-20240519150526759


5. 信号槽机制的意义

  1. 解耦合

    信号函数的定义,槽函数的定义,以及信号槽的连接是分开处理的,耦合度低。

  2. 多对多

    一个信号可以绑定多个槽函数,一个槽函数也可以被多个信号绑定。

    QPushButton* btn3 = new QPushButton(this);
    btn3->setText("点击此按钮,会发生三件事!");
    btn3->move(0, 200);
    
    connect(btn3, &QPushButton::clicked, this, &Widget::mySlot1);
    connect(btn3, &QPushButton::clicked, this, &Widget::mySlot2);
    connect(btn3, &QPushButton::clicked, this, &Widget::mySlot3);