『 QT 』信号-槽 补充: Qt信号槽断开连接与Lambda槽技巧

文章目录


信号槽断开连接

通常情况下, 信号和槽是多对多的关系;

但在某些情况下我们并不期望一个信号同时引发多个槽的执行, 因此我们希望能使用某些方法使得在一定条件下能够断开某个信号与某个槽的连接并希望该信号与另一个槽进行连接;

这就涉及到了disconnect()断开连接;

cpp 复制代码
[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引入了函数模板的版本, 可以直接通过传入指针而非字符串对对应的信号和槽进行连接的断开;

cpp 复制代码
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相同, 此处不进行赘述;

通常情况下, 两种版本的connectdisconnect逻辑并不相同, 如果使用字符串版本的connect进行连接, 一般需要采用字符串(宏)版本的disconnect进行断联, 否则可能会导致disconnect失败;

假设存在一个示例, 一个widget框架中存在两个按钮, 其中一个按钮clicked信号对应的槽为改变Widgettitle"Hello QT", 另一个按钮的clicked信号对应的槽为改变另一个按钮clicked信号的槽, 使其点击时让widgettitle变为"Hello world";

由于connectSlotsByName依靠命名进行连接的方式通常是采用字符串(宏)版本的connect进行连接, 因此如果使用了connectSlotsByName时需要使用字符串(宏)版本的disconnect来断开连接;

cpp 复制代码
/////////// 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 且已经断开a
        connect(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";
}

这段代码中使用了字符串版的disconnectconnectSlotsByName所连接的槽进行断联, 由于控件change_titleclicked信号需要来回对两个槽进行连接与断联, 因此为了避免字符串版的disconnect和模板函数版的connect混用, 因此该处的连接与断开连接都统一使用字符串版本的;

运行结果为如下:


Lambda 表达式定义槽函数

Lamdba表达式本质上是一个匿名函数, 其允许捕获外部变量, 可以通过捕获的变量来对表达式内部进行传参;

由于其是一个匿名函数, 其可以很好的作为回调函数, 即用即弃;

在QT使用Lambda表达式作为槽函数可以减少connect的绑定从而简化代码;

同样以上面的代码作为基础, 可以对该段代码进行Lambda版化;

即修改widgettitle;

  • 首先创建两个QPushButton按钮控件

    change Title按钮的objectNamechangeTitle;

    change Slot for 'change Title'按钮的objectNamechangeTitleslot;

  • changeTitle创建两个槽并连接其中一个槽

    cpp 复制代码
    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(){ // 槽1
        this->setWindowTitle("Hello World");
        qDebug() << "changed title to hello world";
    }
    
    void Widget::change_title_hello_qt(){ // 槽2
        this->setWindowTitle("Hello QT");
        qDebug() << "changed title to hello qt";
    }
  • changeTitleslot连接Lambda槽

    cpp 复制代码
    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

      cpp 复制代码
      #ifndef WIDGET_H
      #define WIDGET_H
      
      #include <QWidget>
      #include <QDebug>
      
      
      QT_BEGIN_NAMESPACE
      namespace Ui {
      class Widget;
      }
      QT_END_NAMESPACE
      
      class Widget : public QWidget
      {
          Q_OBJECT
      
      public:
          Widget(QWidget *parent = nullptr);
          ~Widget();
      
          void change_title_hello_world();
          void change_title_hello_qt();
      
      
      private:
          Ui::Widget *ui;
      };
      #endif // WIDGET_H
    • widget.cpp

      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;

相关推荐
Source.Liu5 小时前
【CMakeLists.txt】 Qt 自动化构建配置详解
qt·自动化·librecad
新青年5795 小时前
Go语言项目打包上线流程
开发语言·后端·golang
学习编程的Kitty5 小时前
JavaEE初阶——多线程(2)线程的使用
java·开发语言·java-ee
counting money5 小时前
JAVAEE阶段学习指南
java·开发语言
是Yu欸5 小时前
【仓颉语言】原生智能、全场景与强安全的设计哲学
开发语言·安全·鸿蒙·鸿蒙系统·仓颉语言
Source.Liu5 小时前
【CMakeLists.txt】CMake 编译定义带值参数详解
c++·qt·librecad
杨福瑞5 小时前
数据结构:顺序表讲解(1)
c语言·开发语言·数据结构
凡间客5 小时前
5、Python3编程之面向对象
java·服务器·数据库
涛思数据(TDengine)5 小时前
TDengine TSDB 3.3.8.0 上线:SMA、TLS、TDgpt、taosX、taosgen 一次全进化
大数据·数据库·时序数据库·tdengine