Qt 之connect 信号和槽函数连接的几种方法

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()<<qString;
}

//信号槽
connect(this, SIGNAL(iSignal(int, float)), this, SLOT(iSlot()));


//发送信号
emit iSignal(5, 0.3);

3. connect()函数基于函数指针的重载形式:

[static] QMetaObject::Connection QObject::connect(const QObject *sender, PointerToMemberFunction signal, 
const QObject *receiver,
PointerToMemberFunction method,
Qt::ConnectionType type = Qt::AutoConnection)

这是QT5中加入的一种重载形式,指定信号和槽两个参数不再使用SIGNAL()和 SLOT()宏,并且槽函数不再必须是使用slots关键字声明的函数,可以是任意能和信号关联的成员函数。要使一个成员函数可以和信号关联,那么这个函数的参数数目不能超过信号的参数数目,但是并不要求该函数拥有的参数类型和信号中对应的参数类型完全一致,只需要可以进行隐式转换即可。如:

connect(dlg, &mydialog::dlgReturn, this, &Widget::showValue);

4. connect 应用c++11 lamda

另外依据上一种形式,还支持C++11 中的lambda表达式,可以在关联时直接编写信号发射后要执行的代码,例如程序中的关联可以写为:

connect(dlg, &MyDialog::dlgReturn, [ = ](int value){
ui->label->setText(tr("获取的值是:%1"),arg(value));});

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

/*
* 作者:张建伟
* 时间:2018年4月1日
* 简述:该Demo仅仅用于测试和演示Qt5与Qt4的连接方式以及最新的槽函数支持lambda表达式
*/

#include "widget.h"
#include "ui_widget.h"
#include <QDebug>
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()<<"lambda 表达式";});
}

Widget::~Widget()
{
delete ui;
}

void Widget::qT4_slot()
{
qDebug()<< "This is Qt 4 Connect method";
}

void Widget::qT5_slot()
{
qDebug()<< "This is Qt 5 Connect method";
}

下面通过一个例子介绍lambda表达式的方法:

<mainwindow.h>

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QString>

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
Q_OBJECT

public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();

private:
Ui::MainWindow *ui;
void showLabel(int i);
};

#endif // MAINWINDOW_H

<mainwindow.cpp>

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPushButton>

MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);

//新建一个按钮数组,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], &QPushButton::clicked, this, [ = ] {
showLabel(i);
});
}

}

MainWindow::~MainWindow()
{
delete ui;
}

void MainWindow::showLabel(int i)
{
ui->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 <QPushButton>

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 <QWidget>

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。

<mainwindow.h>

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QString>
#include <QSignalMapper>

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

<mainwindow.cpp>

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QPushButton>
#include <QSignalMapper>

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. 信号和槽的特色和优越性:
信号和槽机制是类型安全的,相关联的信号和槽的参数必须匹配;

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

信号和槽可以使用任意类型数量的参数。
————————————————
版权声明:本文为CSDN博主「ppipp1109」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/p942005405/article/details/100717064