Qt信号槽一览

Qt信号槽一览

信号和插槽用于对象之间的通信。信号和插槽机制是 Qt 的一个核心特性,也可能是与其他框架提供的特性最不同的部分。Qt 的元对象系统使信号和插槽成为可能。

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

其他工具包使用回调来实现这种通信。回调函数是一个指向函数的指针,所以如果您想要一个处理函数通知您某个事件,您需要传递一个指向另一个函数(回调函数)的指针到该处理函数。然后,处理函数在适当的时候调用回调。虽然使用这种方法的成功框架确实存在,但是回调可能不直观,并且在确保回调参数的类型正确性方面可能会遇到问题。

在 Qt 中,我们有一种替代回调技术的方法: 我们使用信号和插槽。当特定事件发生时发出信号。Qt 的小部件有许多预定义的信号,但是我们总是可以子类化小部件来向它们添加我们自己的信号。插槽是响应特定信号而调用的函数。Qt 的小部件有许多预定义的插槽,但通常的做法是对小部件进行子类化并添加自己的插槽,以便处理感兴趣的信号。

在这里插入图片描述

信号和插槽机制是类型安全的: 信号的签名必须与接收插槽的签名相匹配。(实际上,一个插槽的签名可能比它接收到的信号短,因为它可以忽略额外的参数。)由于签名是兼容的,编译器可以帮助我们在使用基于函数指针的语法时检测类型不匹配。基于字符串的 SIGNAL 和 SLOT 语法将在运行时检测类型不匹配。信号和插槽是松散耦合的: 发射信号的类既不知道也不关心哪个插槽接收信号。**Qt 的信号和插槽机制确保如果您将信号连接到插槽,插槽将在正确的时间用信号的参数调用。**信号和插槽可以接受任意类型的任意数量的参数。它们是完全类型安全的。

所有继承自 QObject 或其子类(例如 QWidget)的类都可以包含信号和插槽。当对象以其他对象可能感兴趣的方式改变其状态时,就会发出信号。这就是对象进行通信所做的全部工作。它不知道也不关心任何东西是否接收到它发出的信号。这是真正的信息封装,并确保对象可以用作基于组件的软件工程。

插槽可用于接收信号,但它们也是正常的成员函数。正如一个对象不知道是否有任何东西接收到它的信号一样,一个槽也不知道是否有任何信号连接到它。这确保了可以使用 Qt 创建真正独立的组件。

您可以将任意多个信号连接到单个插槽,并且可以根据需要将任意多个信号连接到任意多个插槽。甚至可以将一个信号直接连接到另一个信号。(只要发出第一个信号,它就会立即发出第二个信号。)

信号和插槽组成了一个强大的组件编程机制。

Signals

当对象的内部状态以某种方式发生变化时,对象的客户端或所有者可能会感兴趣,这时就会发出信号。信号是公共访问函数,可以从任何地方发出,但是我们建议只从定义信号及其子类的类中发出它们。

当发出一个信号时,连接到它的插槽通常会立即执行,就像普通的函数调用一样。当这种情况发生时,信号和插槽机制完全独立于任何 GUI 事件循环。一旦所有插槽都返回,就会执行发出语句后面的代码。在使用排队连接时,情况稍有不同; 在这种情况下,发出关键字后面的代码将立即继续,插槽将在稍后执行。

如果多个插槽连接到一个信号上,当信号发出时,这些插槽将按照它们连接的顺序一个接一个地执行。

信号由 moc 自动生成,不能在。Cpp 文件。它们永远不能有返回类型(即使用 void)。

关于参数的注意事项: 我们的经验表明,如果信号和插槽不使用特殊类型,则它们的可重用性更高。如果 QScrollBar: : valueChanged ()要使用一种特殊类型,比如假设的 QScrollBar: : Range,那么它只能连接到专门为 QScrollBar 设计的插槽。将不同的输入小部件连接在一起是不可能的。

Slots

当连接到插槽的信号发出时,将调用插槽。插槽是正常的 C + + 函数,可以正常调用; 它们唯一的特性是信号可以连接到它们。

因为插槽是普通的成员函数,所以当直接调用时它们遵循普通的 C + + 规则。但是,作为插槽,它们可以由任何组件通过信号插槽连接调用,而不管其访问级别如何。这意味着从任意类的实例发出的信号可以导致在不相关类的实例中调用私有槽。

您还可以将插槽定义为虚拟的,我们发现这在实践中非常有用。

与回调相比,信号和插槽的速度要稍慢一些,因为它们提供了更大的灵活性,尽管对于实际应用程序来说,这方面的差别不大。一般来说,发射一个连接到某些插槽的信号比直接调用接收器的非虚函数调用慢大约十倍。这是定位连接对象、安全地遍历所有连接(即检查后续接收器是否在发送过程中被销毁)以及以通用方式封送任何参数所需的开销。虽然10个非虚函数调用听起来可能很多,但它比任何新建或删除操作的开销都小得多。一旦您执行了字符串、向量或列表操作,后台需要新建或删除,信号和插槽开销只占整个函数调用成本的很小一部分。无论您在槽中执行系统调用,还是间接调用十个以上的函数,情况都是一样的。信号和插槽机制的简单性和灵活性非常值得开销,您的用户甚至不会注意到这一点。

请注意,其他定义称为信号或槽的变量的库在与基于 Qt 的应用程序一起编译时可能会导致编译器警告和错误。要解决这个问题,请使用 # undef 预处理器符号。

Qt中connect的用法

老版本写法,比较复杂些,需要将信号和槽进行明确的指定,包括形参。

class MyButton : public QWidget
{
    Q_OBJECT
public:
    explicit MyButton(QWidget *parent = nullptr);

signals:
    void sigClicked();
    void sigClicked(bool check);
};

老版本写法,一目了然,但是比较麻烦

connect(m_pBtn,SIGNAL(sigClicked()),this,SLOT(onClicked()));
connect(m_pBtn,SIGNAL(sigClicked(bool)),this,SLOT(onClicked(bool)));

Qt5.0后的写法

connect(m_pBtn,&MyButton::sigClicked,this,&Widget::onClicked);

如果信号是重载的会报错

error: no matching member function for call to ‘connect’ connect(m_pBtn,&MyButton::sigClicked,this,&Widget::onClicked);

用下面的写法指定信号函数的指定版本就好,若槽函数重载用老方法也是可以的

connect(m_pBtn, static_cast<void (MyButton::*)(bool)>(&MyButton::sigClicked), this, &Widget::onClicked);

优化信号函数重载写法

但是依然不能链接重载的槽函数。

connect(m_pBtn, QOverload<bool>::of(&MyButton::sigClicked),this,&Widget::onClicked);

Lambda函数写法

槽函数内容简单的话没必要单独定义一个槽链接,直接用Lambda函数方便。

connect(m_pBtn, QOverload<bool>::of(&MyButton::sigClicked),

               [=](bool check){

                /* do something.. */

                });

connect(m_pBtn, static_cast<void (MyButton::*)(bool)>(&MyButton::sigClicked), this, [=](bool check){

                 //do something

                 });

Lambda函数

[capture](parameters) mutable ->return-type{statement}

1.[capture]:捕捉列表。捕捉列表总是出现在Lambda函数的开始处。实际上,[]是Lambda引出符。编译器根据该引出符判断接下来的代码是否是Lambda函数。捕捉列表能够捕捉上下文中的变量以供Lambda函数使用;

2.(parameters):参数列表。与普通函数的参数列表一致。如果不需要参数传递,则可以连同括号“()”一起省略;

3.mutable:mutable修饰符。默认情况下,Lambda函数总是一个const函数,mutable可以取消其常量性。在使用该修饰符时,参数列表不可省略(即使参数为空);

4.->return-type:返回类型。用追踪返回类型形式声明函数的返回类型。我们可以在不需要返回值的时候也可以连同符号”->”一起省略。此外,在返回类型明确的情况下,也可以省略该部分,让编译器对返回类型进行推导;

5.{statement}:函数体。内容与普通函数一样,不过除了可以使用参数之外,还可以使用所有捕获的变量。

与普通函数最大的区别是,除了可以使用参数以外,Lambda函数还可以通过捕获列表访问一些上下文中的数据。具体地,捕捉列表描述了上下文中哪些数据可以被Lambda使用,以及使用方式(以值传递的方式或引用传递的方式)。语法上,在“[]”包括起来的是捕捉列表,捕捉列表由多个捕捉项组成,并以逗号分隔。捕捉列表有以下几种形式:

1.[var]表示值传递方式捕捉变量var;
2.[=]表示值传递方式捕捉所有父作用域的变量(包括this);
3.[&var]表示引用传递捕捉变量var;
4.[&]表示引用传递方式捕捉所有父作用域的变量(包括this);
5.[this]表示值传递方式捕捉当前的this指针。

上面提到了一个父作用域,也就是包含Lambda函数的语句块,说通俗点就是包含Lambda的“{}”代码块。上面的捕捉列表还可以进行组合,例如:

1.[=,&a,&b]表示以引用传递的方式捕捉变量a和b,以值传递方式捕捉其它所有变量;
2.[&,a,this]表示以值传递的方式捕捉变量a和this,引用传递方式捕捉其它所有变量。

不过值得注意的是,捕捉列表不允许变量重复传递。下面一些例子就是典型的重复,会导致编译时期的错误。例如:

3.[=,a]这里已经以值传递方式捕捉了所有变量,但是重复捕捉a了,会报错的;
4.[&,&this]这里&已经以引用传递方式捕捉了所有变量,再捕捉this也是一种重复。

Lambda函数示例

[capture]讲解

在上节工程基础上,添加按钮mybutton_1,按下按钮,改变按钮文本。按照上面的Lambda表达式的基本构成代码可以写为(错误写法):

QPushButton *mybutton_1=new QPushButton(this);
mybutton_1->setText("关注<程序媛讲QT>");
mybutton_1->move(150,100);   //移动按键
connect(mybutton_1,&QPushButton::pressed,
        []()
        {
             mybutton_1->setText("Lambda表达式");
         }
);

此时编译会出现“error: ‘mybutton_1’ is not captured”的报错信息,这说明mybutton_1并没有在我作用域范围之内。这时需要用[capture]将外部的变量传进来。此时我们只要把mybutton_1传进去,代码就可正常编译运行了。connect()函数代码改为:

connect(mybutton_1,&QPushButton::pressed,
        [mybutton_1]()
        {
              mybutton_1->setText("Lambda表达式");
        }
);

此时代码可正常编译运行。那么当外部有多个变量的时候,挨个写入[]中就显得过于麻烦,此时我们可以用“=”。 例如:

QPushButton *mybutton_1=new QPushButton(this);
mybutton_1->setText("关注<程序媛讲QT>");
mybutton_1->move(150,100);   //移动按键
int a=2,b=7;
connect(mybutton_1,&QPushButton::pressed,
        [=]()
         {
               mybutton_1->setText("Lambda表达式");
               qDebug()<<a+b;
          }
);

qDebug()为打印输出,类似于C语言中的printf,使用它时需要#include “QDebug”。
  编译运行,按下按键后,按键文本发生改变并输出文本“9”。可见“=”将外部变量a、b、mybutton_1的值都传递进来了。
  可见“=”可以将外部的所有局部变量以及类中所有成员以值传递进来。同样的,“this”是类中的所有成员以值传递;“&”是把外部所有局部变量引用过来。

总结:

  • [ capture]:在Lambda表达式中,这部分必须存在,不能省略。
  • =:把外部的所有局部变量以及类中所有成员以值传递进Lambda表达式,默认只读,不能改变变量的值。
  • &:所有的局部变量(包括Lambda表达式所在类中的this),引用的传递方式。
  • this:函数体内可以使用Lambda所在类中的成员变量。
  • 推荐使用“ = ”。

mutable讲解

上面讲了“=”是以值传递,他是默认只读,如果我们想改变外部变量怎么办,此时可以用mutable,代码如下:

connect(mybutton_1,&QPushButton::pressed,
        [=]() mutable
        {
              b=3;
              mybutton_1->setText("Lambda表达式");
              qDebug()<<a+b;
         }
);
  • 总结:
  • 在Lambda表达式中,mutable是可以省略的。按值传递变量时,加上mutable修饰符后,可以修改在Lambda表达式中该变量的值,但并没有修改值本身。

(parameters)讲解

当Lambda表达式没有参数时,此部分可以忽略;当信号是有参数的时候,可以用()重载函数参数。例如:

connect(mybutton_1,&QPushButton::clicked,
             [=](bool Check)
             {
                  qDebug()<<Check;
             }
         );

QPushButton的信号void QAbstractButton::clicked(bool checked = false)是有参数的,通过()可将参数传入Lambda表达式中。

总结:

utton::clicked,
[=](bool Check)
{
qDebug()<<Check;
}
);


 QPushButton的信号void QAbstractButton::clicked(bool checked = false)是有参数的,通过()可将参数传入Lambda表达式中。

**总结:**

- **在Lambda表达式中无参数时,(parameters)部分是可以省略的。**

猜你喜欢

转载自blog.csdn.net/weixin_43925768/article/details/131124411
今日推荐