Qt 之connect 信号和槽函数连接的几种方法的总结(含signalmaper、lamda方式)

您所在的位置:网站首页 connect在键盘哪个位置 Qt 之connect 信号和槽函数连接的几种方法的总结(含signalmaper、lamda方式)

Qt 之connect 信号和槽函数连接的几种方法的总结(含signalmaper、lamda方式)

2024-05-29 19:00| 来源: 网络整理| 查看: 265

1. 最常规的用法:

信号可以是插件自带的,也可以是自己定义的如:

//新建一个按钮 QPushButton * btn = new QPushButton(this); btn->setText("设置"); //将信号和槽连接 其中btnclicked()为自定义的槽函数 connect(btn, SIGNAL(clicked()), this, SLOT(btnclicked())); 2. 带参数的信号和槽函数

当信号的参数与槽函数的参数数量不同时,只能是信号的参数数量多于槽函数的参数数量,且前面相同数量的参数类型应一致,信号中多余的参数会被忽略。

//信号: void mySignal(int a, float b); //槽: void MainWindow::mySlot(int b) { //do something!! } //信号槽: connect(this, SIGNAL(mySignal(int, float)), this, SLOT(mySLot(int))); //发送信号: emit mySignal(5, 2.2);

此外,在不进行参数传递时,信号槽绑定时也是要求信号的参数数量大于等于槽函数的参数数量。这种情况一般是一个带参数的信号去绑定一个无参数的槽函数。如下例所示。

//信号 void iSignal(int a, float b); //槽 void MainWindow::iSlot() //int b { QString qString = "I am lyc_daniel."; qDebug()setText(tr("获取的值是:%1"),arg(value));});

引用一个应用例子理解上述的几种方法:

/* * 作者:张建伟 * 时间:2018年4月1日 * 简述:该Demo仅仅用于测试和演示Qt5与Qt4的连接方式以及最新的槽函数支持lambda表达式 */ #include "widget.h" #include "ui_widget.h" #include Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { ui->setupUi(this); // 传统Qt是连接方式 // 传统Qt4连接方式为 信号发送者,信号,信号接受者,处理函数 QObject::connect(ui->pushButton,SIGNAL(clicked(bool)),this,SLOT(qT4_slot())); //Qt5连接方式 //其实这么写的方式和Qt4没有啥却别,只是在Qt4 中引用了信号槽,在简单的使用时没有问题,但是在庞大的工程中,信号和曹 仅仅是宏替换,在编译的时候没有安全监测 //Qt5的新方法,在编译的时候就会有监测,如果我们手误操作失误,就会出现问题 QObject::connect(ui->pushButton_2,&QPushButton::clicked,this,&Widget::qT5_slot); //Qt5 Lambda表达式 //这里需要注意 Lambda表达式是C++ 11 的内容,所以,需要再Pro项目文件中加入 CONFIG += C++ 11 QObject::connect(ui->pushButton_3,&QPushButton::clicked,[=](){qDebug()label->setText(QString("button%1 is clicked").arg(i)); }

这里的槽就是一个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使用,以及使用方式(以值传递的方式或引用传递的方式)。语法上,在“[]”包括起来的是捕捉列表,捕捉列表由多个捕捉项组成,并以逗号分隔。捕捉列表有以下几种形式:

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

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

    [=,&a,&b]表示以引用传递的方式捕捉变量a和b,以值传递方式捕捉其它所有变量;[&,a,this]表示以值传递的方式捕捉变量a和this,引用传递方式捕捉其它所有变量。不过值得注意的是,捕捉列表不允许变量重复传递。下面一些例子就是典型的重复,会导致编译时期的错误。例如:[=,a]这里已经以值传递方式捕捉了所有变量,但是重复捕捉a了,会报错的;[&,&this]这里&已经以引用传递方式捕捉了所有变量,再捕捉this也是一种重复

也就是说我这里定义了一个Lambda匿名函数,捕获了所有父作用域的变量,在函数体内调用了showLabel(int i)函数。这里也可以将showLabel函数嵌入到Lambda函数内,如下所示:

connect(push[i], &QPushButton::clicked, this, [ = ] { ui->label->setText(QString("button%1 is clicked").arg(i)); });

由于可以直接使用父作用域的变量,这里就不用担心signal没有参数传递了。

需要注意的是,这里的connect参数里的槽函数不能使用如下的SIGNAL()……SLOT() 形式:

connect(push[i], SIGNAL(clicked()), this, SLOT([i] { showLabel[i]; })); 5. 信号和槽的自动关联

信号和槽还有一种自动关联方式,例如on_pushButton_clicked()由字符串on、部件的objectName和信号名称3部分组成,中间用下划线隔开,之中形式命名的槽可以直接和信号关联,不再需要connect()函数。不过使用这种方式还要进行其他设置,

//widget.cpp #include "widget.h" #include "ui_widget.h" #include Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget) { QPushButton *button = new QPushButton(this); // 创建按钮 button->setObjectName("myButton"); // 指定按钮的对象名 ui->setupUi(this); // 要在定义了部件以后再调用这个函数 } Widget::~Widget() { delete ui; } void Widget::on_myButton_clicked() // 使用自动关联 { close(); } //widget.h #ifndef WIDGET_H #define WIDGET_H #include namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = 0); ~Widget(); private: Ui::Widget *ui; private slots: void on_myButton_clicked(); }; #endif // WIDGET_H

因为setupUi()函数中调用了connectSlotByName()函数,所以要使用自动关联的部件的定义,都要放在setupUi()函数调用之前,而且还必须使用setObjectName()指定它们的objectName,只有这样才能正常使用自动关联。

上述例子中object-Name 就是myButton可以看到,如果使用信号槽自动关联,必须在connectSlotsByName()函数之前进行部件的定义,而且还要指定部件的objectName。鉴于这些约束,虽然自动关联形式上简单,但是实际编写代码时很少使用。

6. 高级应用QSignalMapper 

Qt 提供了QObject::sender()函数来返回发送该信号的对象的指针。但是如果有多个信号关联到了同一个槽上,而该槽中需要对每一个信号进行不同的处理,则使用这种方法就麻烦了,这是可以使用QSignalMapper 类。

QSignalMapper被叫做信号映射器,可以实现对多个相同部件的相同信号进行银树沟和,为其添加字符串或者数值参数,然后再发射出去。

void setMapping(QObject *sender, int id); void setMapping(QObject *sender, const QString &text); void setMapping(QObject *sender, QWidget *widget); void setMapping(QObject *sender, QObject *object); void removeMappings(QObject *sender); Q_SIGNALS: void mapped(int); void mapped(const QString &); void mapped(QWidget *); void mapped(QObject *);

这四种捆绑方式,使用灵活。同一个sender在一个map中可以被捆绑多次;int 型的以及 QString、QWidget等 的Map捆绑互相独立,互不影响。一个 signalMap 的 mapped信号最多可以连接到4个不同类型的槽函数,这四个信号槽相互独立。

举个简单的例子,现在我们有一个私有变量,存放了一个QString的数组,一共5项。我们希望动态地创建一个大小为5的QPushButton数组,实现的功能是点击第i个按键就让label显示第i个QString。

#ifndef MAINWINDOW_H #define MAINWINDOW_H #include #include #include namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = 0); ~MainWindow(); private: Ui::MainWindow *ui; QString list[5] = {"item1", "item2", "item3", "item4", "item5"}; QSignalMapper * myMapper; private slots: void showLabel(int i); }; #endif // MAINWINDOW_H

#include "mainwindow.h" #include "ui_mainwindow.h" #include #include MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); myMapper = new QSignalMapper(); //新建一个按钮数组,id为push[i] QPushButton * push[5]; for (int i = 0; i < 5; i++) { push[i] = new QPushButton(this); push[i]->setGeometry(300, 60 + 30 * i, 89, 24); push[i]->setText(QString("button%1").arg(i)); connect(push[i], SIGNAL(clicked()), myMapper, SLOT(map())); myMapper->setMapping(push[i], i); } connect(myMapper, SIGNAL(mapped(int)), this, SLOT(showLabel(int))); } MainWindow::~MainWindow() { delete ui; } void MainWindow::showLabel(int i) { ui->label->setText(QString("button%1 is clicked").arg(i)); }

其中

connect(push[i], SIGNAL(clicked()), myMapper, SLOT(map()));

这句中的信号是按键的点击事件,槽则可以理解为查询QSignalMapper键值对。也就是每次点击都会触发对QSignalMapper的查询,

myMapper->setMapping(push[i], i);

QSignalMapper的内容就是由这句话来设置。它为其添加了一个映射项,键是按键的id,值是一个int类型的值。这里可以根据需要修改数据类型。这句话执行完以后就建立了一个键值对,将每个按钮喝它们各自的下标关联了起来。

connect(myMapper, SIGNAL(mapped(int)), this, SLOT(showLabel(int)));

槽函数map()查询QSignalMapper成功后会返回一个信号mapped(...),这里的参数是一个int,这个整型变量就是之前映射项中的值。这样就能构造出一个带参数的信号,就可以通过connect传递了。

整个过程大概就是:每建立一个按键,就执行一个connect,让它们的点击信号能触发一个查询QSignalMapper的槽。而QSignalMapper中的内容为按键和整型变量的键值对。根据点击的按键可以查询到唯一一个映射项,并发射一个信号,其参数为按键对应的值。这个信号就可以触发自己定义的槽函数,实现参数的传递。

也就是说,这里所做的全部工作就是让connect的信号函数拥有我们需要的参数,那么如果它本身自带参数不就完美了?这里我给大家推荐一个控件,名为Table Widget。这是一个表格,点击每一个格子都可以触发一个信号 cellEntered(int, int) ,参数分别是格子所在的行号和列号。发现了吗?这个信号函数自带参数,并且可以通过这个参数确定点击的位置。详细操作这里不再赘述。

7. 信号和槽的特色和优越性:

信号和槽机制是类型安全的,相关联的信号和槽的参数必须匹配;

信号和槽的松耦合的,信号发送者不知道也不需要知道接受者的信息;

信号和槽可以使用任意类型数量的参数。

 

参考链接:

1. Qt中connect函数不能传递参数的两种解决方法

2. Qt5中的lambda表达式和使用lambda来写connect

3. QT 使用 lambda来写connect

4. 在connect中使用lambda实现高效的信号/槽关联

5. 如何利用 C++ 的 Lambda 表达式提升 Qt 代码



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3