『 QT 』信号-槽 补充: Qt信号槽断开连接与Lambda槽技巧
文章目录
- 信号槽断开连接
- Lambda 表达式定义槽函数
信号槽断开连接
通常情况下, 信号和槽是多对多的关系;
但在某些情况下我们并不期望一个信号同时引发多个槽的执行, 因此我们希望能使用某些方法使得在一定条件下能够断开某个信号与某个槽的连接并希望该信号与另一个槽进行连接;
这就涉及到了disconnect()
断开连接;
[static] bool QObject::disconnect(const QObject *sender, const QMetaMethod &signal, const QObject *receiver, const QMetaMethod &method)
这个即为disconnect
的函数定义, 为QObject
的成员函数;
通常为断开某个信号与某个槽之间的联系;
其使用方式与connect
类似, 基本上相同, 主要存在四个参数:
-
const QObject *sender
即信号发送者;
-
const QMetaMethod &signal
信号函数;
-
const QObject *receiver
信号接受者;
-
const QMetaMethod &method
槽;
与connect
相同, disconnect
函数同样存在新旧版本的语法差异, 其中QT4
及以前的disconnect
语法使用的是字符串风格, 也为上述所提到的风格, 所返回的返回值为一个bool
值来判断一个控件的某个信号是否与对应的槽成功断开连接, 其返回值结果如下:
-
true
连接成功断开;
-
false
连接断开失败(槽与信号不一定匹配, 或是该信号为连接任何槽);
除此之外在QT5/6
开始, 在disconnect
引入了函数模板的版本, 可以直接通过传入指针而非字符串对对应的信号和槽进行连接的断开;
template <typename Func1, typename Func2>static inline bool disconnect(const typename QtPrivate::FunctionPointer<Func1>::Object *sender, Func1 signal,const typename QtPrivate::FunctionPointer<Func2>::Object *receiver, Func2 slot)
其参数传入与connect
相同, 此处不进行赘述;
通常情况下, 两种版本的connect
与disconnect
逻辑并不相同, 如果使用字符串版本的connect
进行连接, 一般需要采用字符串(宏)版本的disconnect
进行断联, 否则可能会导致disconnect
失败;
假设存在一个示例, 一个widget
框架中存在两个按钮, 其中一个按钮clicked
信号对应的槽为改变Widget
的title
为"Hello QT"
, 另一个按钮的clicked
信号对应的槽为改变另一个按钮clicked
信号的槽, 使其点击时让widget
的title
变为"Hello world"
;
由于connectSlotsByName
依靠命名进行连接的方式通常是采用字符串(宏)版本的connect
进行连接, 因此如果使用了connectSlotsByName
时需要使用字符串(宏)版本的disconnect
来断开连接;
/////////// Widget.cpp ///////////#include "widget.h"
#include "ui_widget.h"
#include <QPushButton>Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget)
{ui->setupUi(this);
}Widget::~Widget()
{delete ui;
}void Widget::on_change_title_clicked()
{this->setWindowTitle("Hello QT");qDebug() << "Change title to Hello QT";
}void Widget::on_change_button_slot_clicked()
{if(disconnect(ui->change_title, SIGNAL(clicked()), this, SLOT(on_change_title_clicked()))){// 连接的是 Widget::on_change_title_clicked 且已经断开aconnect(ui->change_title, SIGNAL(clicked()), this, SLOT(changeTitleToHelloWorld()));qDebug()<<"Change to Hello world";}else{// 连接的是&Widget::changeTitleToHelloWorld, 需要手动断开该槽并连接disconnect(ui->change_title, SIGNAL(clicked()), this, SLOT(changeTitleToHelloWorld()));connect(ui->change_title, SIGNAL(clicked()), this, SLOT(on_change_title_clicked()));qDebug()<<"Change to Hello QT";}
}void Widget::changeTitleToHelloWorld()
{this->setWindowTitle("Hello World");qDebug() << "Change title to Hello World";
}
这段代码中使用了字符串版的disconnect
对connectSlotsByName
所连接的槽进行断联, 由于控件change_title
的clicked
信号需要来回对两个槽进行连接与断联, 因此为了避免字符串版的disconnect
和模板函数版的connect
混用, 因此该处的连接与断开连接都统一使用字符串版本的;
运行结果为如下:
Lambda 表达式定义槽函数
Lamdba表达式本质上是一个匿名函数, 其允许捕获外部变量, 可以通过捕获的变量来对表达式内部进行传参;
由于其是一个匿名函数, 其可以很好的作为回调函数, 即用即弃;
在QT使用Lambda表达式作为槽函数可以减少connect
的绑定从而简化代码;
同样以上面的代码作为基础, 可以对该段代码进行Lambda版化;
即修改widget
的 title
;
-
首先创建两个
QPushButton
按钮控件change Title
按钮的objectName
为changeTitle
;change Slot for 'change Title'
按钮的objectName
为changeTitleslot
; -
为
changeTitle
创建两个槽并连接其中一个槽Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);connect(ui->changeTitle, &QPushButton::clicked, this, &Widget::change_title_hello_world); }void Widget::change_title_hello_world(){ // 槽1this->setWindowTitle("Hello World");qDebug() << "changed title to hello world"; }void Widget::change_title_hello_qt(){ // 槽2this->setWindowTitle("Hello QT");qDebug() << "changed title to hello qt"; }
-
为
changeTitleslot
连接Lambda槽Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);connect(ui->changeTitle, &QPushButton::clicked, this, &Widget::change_title_hello_world);// Lamdba 表达式作为槽connect(ui->changeTitleslot, &QPushButton::clicked, this, /* Lambda 表达式 */[=/* 值捕获外部变量 */](){if(disconnect(ui->changeTitle, &QPushButton::clicked, this, &Widget::change_title_hello_world)) {connect(ui->changeTitle, &QPushButton::clicked, this, &Widget::change_title_hello_qt);qDebug()<<"The slot is changed for changeTitle - Hello QT";}else{disconnect(ui->changeTitle, &QPushButton::clicked, this, &Widget::change_title_hello_qt);connect(ui->changeTitle, &QPushButton::clicked, this, &Widget::change_title_hello_world);qDebug()<<"The slot is changed for changeTitle - Hello World";}}); }
即尝试断联槽1, 如果断开成功则连接槽2, 如果断开失败说明当前所连接的为槽2, 使用
disconnect
断开槽2并连接槽1; -
运行结果
-
完整代码
-
Widget.h
#ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QDebug>QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACEclass Widget : public QWidget {Q_OBJECTpublic:Widget(QWidget *parent = nullptr);~Widget();void change_title_hello_world();void change_title_hello_qt();private:Ui::Widget *ui; }; #endif // WIDGET_H
-
widget.cpp
#include "widget.h" #include "ui_widget.h"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui->setupUi(this);connect(ui->changeTitle, &QPushButton::clicked, this, &Widget::change_title_hello_world);// Lamdba 表达式作为槽connect(ui->changeTitleslot, &QPushButton::clicked, this, /* Lambda 表达式 */[=/* 值捕获外部变量 */](){if(disconnect(ui->changeTitle, &QPushButton::clicked, this, &Widget::change_title_hello_world)) {connect(ui->changeTitle, &QPushButton::clicked, this, &Widget::change_title_hello_qt);qDebug()<<"The slot is changed for changeTitle - Hello QT";}else{disconnect(ui->changeTitle, &QPushButton::clicked, this, &Widget::change_title_hello_qt);connect(ui->changeTitle, &QPushButton::clicked, this, &Widget::change_title_hello_world);qDebug()<<"The slot is changed for changeTitle - Hello World";}}); }Widget::~Widget() {delete ui; }void Widget::change_title_hello_world(){this->setWindowTitle("Hello World");qDebug() << "changed title to hello world"; }void Widget::change_title_hello_qt(){this->setWindowTitle("Hello QT");qDebug() << "changed title to hello qt"; }
-
为了生命周期的管理, 我们一般在QT中使用Lambda表达式进行变量捕获时通常使用值捕获而不使用引用捕获, 本质是防止在Lambda表达式中出现对非法内存的访问;
同时QT5开始编译时使用的是c++11
, 若是QT4或之前的版本需要手动修改编译选项, 使其支持Lamdba表达式;
通常要在.pro
文件中手动添加或者修改CONFIG += c++11
;