zoukankan      html  css  js  c++  java
  • Qt信号槽技术小结

     一、connect:String-based和Functor-based写法比较

    1.1 概述

    从Qt 5.0开始,Qt、C++支持两种信号槽connect写法:string-based、functor-based。

    示例代码:

    1 ClassA *pClassA = new ClassA();
    2 Classb *pClassB = new ClassB();
    3 
    4 /* string-based */
    5 connect(pClassA, SIGNAL(signalA(int)), pClassB, SLOT(slotB(int)));
    6
    7 /* functor -based*/
    8 connect(pClassA, &ClassA::signalA, pClassB, &ClassB::slotB);
    
    

    下面列表概述了两者的优缺点:

      String-based Functor-based
    类型检查时机 运行时 编译时
    隐式类型转换   Yes
    连接signals和lambda表达式   Yes
    连接signals和包含更多参数的slots(使用默认参数) Yes  
    连接C++函数和QML函数 Yes  

    1.2 类型检查、隐式转换

    string-based连接通过运行时比较字符串作类型检查,因此有以下缺点:

    1. 连接错误只能在程序开始运行后提示;
    2. 信号槽之间无法作隐式类型转换;
    3. 无法处理typdefs和namespaces。

    1、2是因为字符串比较无法获取C++类型信息,因此string-based依赖于直接用字符串匹配。

    与string-based相反,functor-based通过编译时检查,支持可比较类型的隐式转换,并且可以识别同种类型的不同名称。 

    示例代码: 

    1 auto slider = new QSlider(this);
    2 auto doubleSpinBox = new QDoubleSpinBox(this);
    3 
    4 /* OK: The compiler can convert an int into a double */
    5 connect(slider, &QSlider::valueChanged, doubleSpinBox, &QDoubleSpinBox::setValue);
    6 
    7 /* ERROR: The string table doesn't contain conversion information */
    8 connect(slider, SIGNAL(valueChanged(int)), doubleSpinBox, SLOT(setValue(double)));

    上述例子中,演示了functor-based连接了参数为int的信号和参数为double的槽函数。

    注意:string-based信号槽类型不匹配connect不起效。应用程序输出中可以看到以下错误提示:

    QObject::connect: Incompatible sender/receiver arguments

    QSlider::valueChanged(int) --> QDoubleSpinBox::setValue(double)

    下面代码说明string-based连接无法处理同一个类型用不同名称表示的情况。比如,

    QAudioInput::stateChanged()声明的时候参数类型是“QAudio::State”,string-based连接要求connect时必须指定“QAudio::State”,而不能是“State”。functor-based连接由于连接时无需指定参数类型,因此不存在这种问题。

    示例代码:

    auto audioInput = new QAudioInput(QAudioFormat(), this);
    auto widget = new QWidget(this);
    
    /* OK */
    connect(audioInput, SIGNAL(stateChanged(QAudio::State)), widget, SLOT(show()));
    
    /* ERROR: The strings "State" and "QAudio::State" don't match using namespace QAudio; */
    connect(audioInput, SIGNAL(stateChanged(State)), widget, SLOT(show()));
     

    1.3 连接lambda表达式

    functor-based写法支持C++11的lambda表达式,可以写出高效、内联的槽函数。

    string-based写法不支持上述特性。

    下面以一个名叫TextSender的类为例。

    示例代码:

    TextSender.h

     1 class TextSender : public QWidget
     2 {
     3       Q_OBJECT
     4 
     5       QLineEdit *lineEdit;
     6       QPushButton *button;
     7 
     8 signals:
     9       void textCompleted(const QString& text) const;
    10 
    11 public:
    12       TextSender(QWidget *parent = nullptr);
    13 };

    TextSender.cpp

    TextSender::TextSender(QWidget *parent) : QWidget(parent)
    {
          lineEdit = new QLineEdit(this);
          button = new QPushButton("Send", this);
    
          connect(button, &QPushButton::clicked, [=] {
              emit textCompleted(lineEdit->text());
          });
    
          /* ... */
     }

    在上述例子里,虽然QPushButton::clicked()和TextSender::textCompleted()的参数是不相容的,但是通过lambda表达式就可以相对容易地“connect”两者。

    注意:虽然functor-based接收所有指向函数的指针,但是Qt中signals只能connect到slots、lambda表达式和其它signals。

    1.4 连接C++对象和QML对象

    因为QML类型是运行时处理的,而非在C++编译时,所以无法应用到functor-based连接。

    下面演示了点击QML对象(C++对象),使得C++对象(QML对象)打印消息。

    示例代码:

    QmlGui.qml

     1 Rectangle {
     2        100; height: 100
     3 
     4       signal qmlSignal(string sentMsg)
     5       function qmlSlot(receivedMsg) {
     6           console.log("QML received: " + receivedMsg)
     7       }
     8 
     9       MouseArea {
    10           anchors.fill: parent
    11           onClicked: qmlSignal("Hello from QML!")
    12       }
    13   }

    .h(C++)

     1 class CppGui : public QWidget {
     2       Q_OBJECT
     3 
     4       QPushButton *button;
     5 
     6   signals:
     7       void cppSignal(const QVariant& sentMsg) const;
     8 
     9   public slots:
    10       void cppSlot(const QString& receivedMsg) const {
    11           qDebug() << "C++ received:" << receivedMsg;
    12       }
    13 
    14   public:
    15       CppGui(QWidget *parent = nullptr) : QWidget(parent) {
    16           button = new QPushButton("Click Me!", this);
    17           connect(button, &QPushButton::clicked, [=] {
    18               emit cppSignal("Hello from C++!");
    19           });
    20       }
    21   };

    .cpp

    1 auto cppObj = new CppGui(this);
    2 auto quickWidget = new QQuickWidget(QUrl("QmlGui.qml"), this);
    3 auto qmlObj = quickWidget->rootObject();
    4 
    5 /* Connect QML signal to C++ slot */
    6 connect(qmlObj, SIGNAL(qmlSignal(QString)), cppObj, SLOT(cppSlot(QString)));
    7 
    8 /* Connect C++ signal to QML slot */
    9 connect(cppObj, SIGNAL(cppSignal(QVariant)), qmlObj, SLOT(qmlSlot(QVariant)));

    QML中的所有JavaScript函数的参数类型都是var,对应C++中的QVariant。

    1.5 连接signals和包含更多参数的slots(使用默认参数)

    通常情况下,connect的slot参数数量小于等于signal,且所有参数类型都得是相容的。

    示例代码:

    1 /* signal和slot参数数目相同 */
    2 connect(pClassA, SIGNAL(signalA(QString str1, int i1)), pClassB, SLOT(slot(QString str1, int i1)));
    3 
    4 /* slot参数比signal少 */
    5 connect(pClassA, SIGNAL(signalA(QString str1, int i1)), pClassB, SLOT(slot(QString str1)));

    注意:slot参数比signal少,必须是后边的参数缺省,不能是前面的或者中间的;string-based要求的参数匹配,必须类型完全一致,若不一致,即使是QVariant也不行,否则都会运行时提示连接错误,不生效。

    string-based连接写法支持一种场景:

    当slot有默认参数时,signal可以省略这些默认参数;

    当emit省略部分参数的signal时,slot会用默认参数代替省略部分。

    相反,functor-based写法不支持上述场,不过functor-based可以通过lambda表达式实现相同的效果。

    .h

    1 public slots:
    2 /* 带默认参数的槽函数 */
    3 void printNumber(int number = 42)
    4 {
    5   qDebug() << "Lucky number" << number;
    6 }

    .cpp

    1 DemoWidget::DemoWidget(QWidget *parent) : QWidget(parent)
    2 {
    3 
    4 /* OK: printNumber() 会传入默认参数 42 */
    5 connect(qApp, SIGNAL(aboutToQuit()), this, SLOT(printNumber()));
    6 
    7 /* 编译报错: 编译器需要相容的参数 */
    8 connect(qApp, &QCoreApplication::aboutToQuit, this, &DemoWidget::printNumber);
    9 }

    1.6 连接重载的信号和槽

    由于string-based写法要求指明参数类型,因此可以用于连接重载的信号和槽。

    例如连接以下信号和槽:

    信号:

    QSlider::valueChanged()

    槽:

    QLCDNumber::display(int)
    QLCDNumber::display(double)
    QLCDNumber::display(QString)

    string-based连接写法:

    1 auto slider = new QSlider(this);
    2 auto lcd = new QLCDNumber(this);
    3 
    4 /* String-based syntax */
    5 connect(slider, SIGNAL(valueChanged(int)), lcd, SLOT(display(int)));

     换作functor-based写法,得这样:

     1 /* 方法一 */
     2 connect(slider, &QSlider::valueChanged, lcd, static_cast<void (QLCDNumber::*)(int)>(&QLCDNumber::display));
     3 
     4 /* 方法二 */
     5 void (QLCDNumber::*mySlot)(int) = &QLCDNumber::display;
     6 connect(slider, &QSlider::valueChanged, lcd, mySlot);
     7 
     8 /* 方法三 */
     9 connect(slider, &QSlider::valueChanged, lcd, QOverload<int>::of(&QLCDNumber::display));
    10 
    11 /* 方法四 (需要C++14) */
    12 connect(slider, &QSlider::valueChanged, lcd, qOverload<int>(&QLCDNumber::display));

    1.7 参考资料

    Qt Assistant搜索 Differences between String-Based and Functor-Based Connections

    Hunter药药
  • 相关阅读:
    php array_flip() 删除数组重复元素——大彻大悟
    深入理解HTTP协议之POST方法——ajax实例
    RHEL/CentOS/Fedora各种源(EPEL、Remi、RPMForge、RPMFusion)配置
    SpringMVC注解校验
    Mybatis源码解析(二)
    MyBatist庖丁解牛(三)
    MyBatist庖丁解牛(二)
    linux模拟http请求命令
    清空文件内容
    git上如何删除已有项目
  • 原文地址:https://www.cnblogs.com/fengyaoyao/p/13586867.html
Copyright © 2011-2022 走看看