信号与槽函数

在 Qt 中,信号(Signals)和槽(Slots)是用于对象间通信的一种机制,是 Qt 中事件处理和消息传递的重要方式。信号和槽机制使得对象之间的通信更加灵活和方便,同时也实现了很好的解耦。

当某个事件发生后,如某个按钮被点击了一下,它就会发出一个被点击的信号(signal)。

某个对象接收到这个信号之后,就会做一些相关的处理动作(称为槽slot)。

但是Qt对象不会无故收到某个信号,要想让一个对象收到另一个对象发出的信号,这时候需要建立连接(connect)

在Qt中,有一种回调技术的替代方法:那就是信号和槽机制。当特定事件发生时,会发出一个信号。Qt的小部件中有许多预定义的信号,但我们可以将小部件子类化,向它们添加自定义的信号。槽是响应特定信号的函数。Qt的小部件有许多预定义的槽函数,但是通常是子类化小部件并添加自己的槽函数,这样就可以处理与之相关联的信号了。如下图所示:

image-20240518225711864

信号和槽用于多个对象之间的通信。信号和槽机制是Qt的核心特性,也是与其他框架最大的不同之处。Qt的元对象系统是信号和槽实现的基础。

在GUI编程中,当更改一个小部件时,通常希望另一个小部件得到通知。希望任何类型的对象都能够相互通信。例如,如果用户单击关闭按钮,可能希望调用窗口的Close()函数。

image-20240518225553016

这是 Qt 中的 connect 函数的声明,它用于连接信号和槽。让我解释一下每个参数的含义:

  • const typename QtPrivate::FunctionPointer<Func1>::Object *sender:表示发出信号的对象指针。这里使用了模板元编程的技巧,Func1 是信号函数的类型,QtPrivate::FunctionPointer 用于提取函数指针的对象类型。
  • Func1 signal:表示信号的函数指针。在这里,Func1 代表信号函数的类型,通常是一个成员函数指针,用于指示发出信号的具体函数。
  • Func2 &&slot:表示槽的函数指针。Func2 代表槽函数的类型,通常也是一个成员函数指针。&& 表示这是一个右值引用,用于接收槽函数的参数。

该函数返回一个 QMetaObject::Connection 对象,用于表示信号和槽之间的连接。

总的来说,这个 connect 函数的作用是将一个发出信号的对象(sender)的特定信号(signal)连接到一个槽函数(slot)上。当信号被发出时,与之连接的槽函数会被调用。

image-20240518231448027

像之前的代码就是默认使用的自动连接。

QObject::connect(lineEdit,&QLineEdit::textChanged,[&](){
    QString text = lineEdit->text();
    qDebug() << "文本已改变:" << text;
});

当使用 Qt 进行信号和槽的连接时,可以根据需要选择不同的连接类型。以下是一些常见的连接类型及其示例:

  1. 直接连接

    • 直接连接是默认的连接方式,它会在发射信号时立即调用槽函数。

    • 示例:

      connect(sender, &SenderClass::signal, receiver, &ReceiverClass::slot);
      
  2. 排队连接

    • 排队连接会将信号放入接收者的事件队列中,以便稍后被处理。这在多线程应用程序中很有用。

    • 示例:

      connect(sender, &SenderClass::signal, receiver, &ReceiverClass::slot, Qt::QueuedConnection);
      
  3. 自动连接

    • 自动连接会根据发送者和接收者对象是否处于同一线程来决定使用直接连接还是排队连接。

    • 示例:

      connect(sender, &SenderClass::signal, receiver, &ReceiverClass::slot, Qt::AutoConnection);
      
  4. 连接类型组合

    • 除了单独使用直接连接、排队连接和自动连接外,还可以对其进行组合,以实现更复杂的连接行为。

    • 示例:

      connect(sender, &SenderClass::signal, receiver, &ReceiverClass::slot, Qt::UniqueConnection | Qt::QueuedConnection);
      

一般这个sender就是代码里定义好会发生事件的对象,signal是信号,Receiver是信号的接收者,n slot:接收对象在接收到信号之后所需要调用的函数(槽函数)。这里要注意的是connect的四个参数都是指针,信号和槽是函数指针。

系统自带的信号和槽如何查找呢,这个就需要利用帮助文档了,在帮助文档中比如我们上面的按钮的点击信号,点击signals索引到系统自带的信号有如下几个

image-20240527154953425

1.1 自定义信号和槽

Qt框架默认提供的标准信号和槽不足以完成我们日常应用开发的需求,比如说点击某个按钮让另一个按钮的文字改变,这时候标准信号和槽就没有提供这样的函数。但是Qt信号和槽机制提供了允许我们自己设计自己的信号和槽。

条件:

  1. 声明在类的signals域下

  2. 没有返回值,void类型的函数

  3. 只有函数声明,没有定义

  4. 可以有参数,可以重载

  5. 通过emit关键字来触发信号,形式:emit object->sig(参数);

局部变量引入方式

image-20240530100942314

[ ],标识一个Lambda的开始。由于lambda表达式可以定义在某一个函数体A里边,所以lambda表达式有可能会去访问A函数中的局部变量。中括号里边内容是描述了在lambda表达式里边可以使用的外部局部变量的列表:

  • []:表示lambda表达式不能访问外部函数体的任何局部变量
  • [a]:在函数体内部使用值传递的方式访问a变量
  • [&b]:在函数体内部使用引用传递的方式访问b变量
  • [=]:表示通过传值方式捕获外部变量。使用 [=] 捕获方式时,Lambda 表达式内部可以访问外部作用域中的变量,并且这些变量会被复制到 Lambda 表达式内部,因此对外部变量的修改不会影响外部作用域中的变量。
  • [&]:表示通过引用方式捕获外部变量。使用 [&] 捕获方式时,Lambda 表达式内部可以访问外部作用域中的变量,并且直接引用这些变量,因此对外部变量的修改会影响外部作用域中的变量。
  • [=,&foo]:foo使用引用方式, 其余是值传递的方式
  • [&,foo]:foo使用值传递方式, 其余是引用传递的方式
  • [this]:在函数内部可以使用类的成员函数和成员变量,=和&形式也都会默认引入

由于引用方式捕获对象会有局部变量释放了而lambda函数还没有被调用的情况。如果执行lambda函数那么引用传递方式捕获进来的局部变量的值不可预知。

所以在无特殊情况下建议使用\[=\](){}的形式

在代码里面,我们使用了一个lambda表达式,Lambda 表达式中的 [&] 捕获了所有外部变量,所以假设 lineEdit 是一个指向 QLineEdit 对象的指针,那么在 Lambda 表达式中可以直接访问 lineEdit,获取其文本并输出到调试信息中。

1.1.2 选项Opt

Opt 部分是可选项,最常用的是mutable声明,这部分可以省略。外部函数局部变量通过值传递引进来时,其默认是const,所以不能修改这个局部变量的拷贝,加上mutable就可以

image-20240530103825588

因为用的是=捕获外部变量,所以无法对外部变量进行修改,传入方式是引用。但是加上mutable就没啥问题了

image-20240530104122962

1.1.3 函数返回值 ->retType

image-20240530104536833

retType,标识lambda函数返回值的类型。这部分可以省略,但是省略了并不代表函数没有返回值,编译器会自动根据函数体内的return语句判断返回值类型,但是如果有多条return语句,而且返回的类型都不一样,编译会报错

而且使用Lambda表达式作为槽的时候不需要填入信号的接收者。一旦文本被改变,lambda表达式也会直接运行。当然lambda表达式还可以指定函数参数,这样也就能够接收到信号函数传递过来的参数了。由于lambda表达式比我们自己自定义槽函数要方便而且灵活得多,所以在实现槽函数的时候优先考虑使用Lambda表达式。一般我们的使用习惯也是lambda表达式外部函数的局部变量全部通过值传递捕获进来,也就是:[=](){ }的形式