细谈QT信号与槽机制
本文最后更新于 143 天前,其中的信息可能已经有所发展或是发生改变。

信号与槽是我个人认为QT中最牛的机制之一,最近没有其他的内容可写,今天就来细细总结一下这个信号与槽机制。

1. 信号与槽机制概述

信号与槽机制可以理解为QT中的一种通信手段,在运行相关代码前,分别声明信号和槽,再利用connect()方法将信号和对应的槽连接起来,之后再需要的地方使用emit触发信号,那么就可以让槽响应。

其中,槽可以是特定的槽函数,也可以是其他各种普通函数;信号是一种特定对象,其结构类似函数,也可以像函数那样带有参数,将值通过参数传递槽函数,便于槽函数处理变量,如果使用参数,则信号和槽的参数要保持一致。

示例:

class MyClass : public QObject 
{
protected:
    MyClass(QObject *parent = nullptr);
    ~MyClass();
    void myFunc();

signals:
    void dataArrived(string data);

slots:
    void onDataArrived(string data);
}

///////////////////////////////////

MyClass::MyClass(QObject *parent = nullptr)
{
    // 在使用信号与槽函数之前连接信号和槽
    connect(this, &MyClass::dataArrived, this, &myClass::onDataArrived);
}
MyClass::~MyClass()
{
    // 在不用的时候释放连接
    disconnect(this, &MyClass::dataArrived, this, &myClass::onDataArrived);
}

void MyClass::onDataArrived(string data)
{
    // 处理data或其他操作
}

void MyClass::myFunc()
{
    ....
    // 在需要的地方触发信号,执行onDataArrived槽函数,并传递数据
    emit dataArrived(data);
}

这个示例中,只在一个类中使用信号和槽传递数据,其效果与直接调用函数无异,但信号与槽的功能远超如此。

可以使用信号与槽实现跨类通信、跨线程通信,保证线程不会阻塞,实现异步处理。一个槽可以同时与多个信号连接,便于代码中应对不同情况,不用命名各种函数。而且信号与槽可以任意连接、解耦,也可以实现在不同的地方将一个信号与不同槽函数连接,轻松改写结构,而不会影响代码其他部分。

2. QT中信号与槽

2.1 基础用法

我这里只提QT5中的用法,旧版本的用法几乎不再使用。

2.1.1 信号signal

  1. 声明的格式

    需要在头文件中使用signal关键字修饰单一对象,以指定信号对象,或者使用signals:来批量修饰对象,以批量指定信号对象。另外,Q_SIGNAL等同于signalQ_SIGNALS:等同于signals:

    信号本体格式等同于函数,但不需要再在CPP文件中定义“函数体”,返回值为void。也可声明参数,以传递数据,但参数格式与槽保持一致,至少参数数目一样多,并且参数之间可以隐式转换,好比函数的调用。

    示例:

    class MyClass : public QObject 
    {
       // 修饰单一信号对象
       // Q_SIGNAL等同signal
       Q_SIGNAL void fileDeleted(); 
    
    // 批量修饰信号对象
    // signals等同Q_SIGNALS
    signals: 
       // 可声明参数
       void fileCreated(string filePath);
       void fileModifed();
    }
  2. 使用

    先使用connect()方法连接信号与槽,在需要的地方,使用emit触发信号,如有参数,就需要保证格式的正确。

    emit fileDeleted();
    Q_EMIT fileCreated(path);

2.1.2 槽slot

  1. 声明的格式

    可选的在头文件中使用slot关键字修饰单一对象,以指定槽函数,或者使用slots:来批量修饰对象,以批量指定槽函数。另外,Q_SLOT等同于slotQ_SLOTS:等同于slots:

    槽函数的本身就是函数,即使用函数格式,需要在CPP文件中定义函数体,任意定义返回值以应对其他需要调用此函数的地方。不同于信号必须使用signal关键字告诉编译器这是信号对象,槽函数也可以直接使用普通函数作为连接对象。

    示例:

    class MyClass : public QObject 
    {
    // 普通函数可直接被当作槽函数用connect方法与信号连接
    // 可以有返回值
    bool onFileModifed();
    
    public: // 需要修饰访问权限
       // Q_SLOT等同slot
       Q_SLOT void onFileDeleted();
    
    // 需要修饰访问权限, slots等同Q_SLOTS
    public slots:
       // 可声明参数
       void onFileCreated(string filePath);
    }
  2. 使用

    作为一个函数,需要在CPP文件中定义函数体,在需要的时候使用connect()方法连接信号与槽,以便之后被信号调用。

    bool MyClass::onFileModifed()
    {
    /// 函数体...
    }
    
    void MyClass::onFileCreated(string filePath)
    {
    /// 函数体...   
    }

2.1.3 connect()方法

1. QT5中的connect()方法:

使用connect()方法将信号和槽建立连接,以便信号触发时,执行指定槽函数。

connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction slot, Qt::ConnectionType type = Qt::AutoConnection)

  • 参数一sender:信号发出的对象
  • 参数二signal:信号所在类名和信号名称,格式为&类名::信号名
  • 参数三receiver:信号接收的对象,槽函数所在的对象
  • 参数四slot:槽函数所在类名和槽函数名称,格式为&类名::槽函数名
  • 参数五(在2.2中详细介绍)

示例:

QLabel *label = new QLabel;
QLineEdit *lineEdit = new QLineEdit;
QObject::connect(lineEdit, &QLineEdit::textChanged, label, &QLabel::setText);

2. disconnect()方法:

使用disconnect()方法释放信号和槽连接,便于之后此信号和不同槽函数建立连接,一般不使用此信号和槽时就要记得释放连接。

disconnect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction slot)

格式与connect时保持一致,但disconnect()方法没有第五参数,即便connect()时指定了第五参数,disconnect()时直接省略。

示例:

// 建立连接
QObject::connect(lineEdit, &QLineEdit::textChanged, label, &QLabel::setText);
// 释放此连接
QObject::disconnect(lineEdit, &QLineEdit::textChanged, label, &QLabel::setText);

3. 使用Lambda表达式作为槽函数:

当然,前面又说到槽可以为其他函数,也就可以使用Lambda表达式来作为槽函数,使用Lambda表达式可以简化槽函数的声明,也可以通过Lambda表达式随着信号调用参数格式与信号不一致的函数,或增加更多的使用条件。

关于C++ Lambda表达式可见我另一篇文章:C++ Lambda表达式

示例:

class MyClass : public QObject 
{
protected:
    MyClass(QObject *parent = nullptr);

    void myFunc(string data, int size);

signals:
    void dataArrived(string data);
}

///////////////////////////////////
MyClass::MyClass(QObject *parent = nullptr)
{
    connect(this, &MyClass::dataArrived, this, [&](){
        myFunc(data, data.lenght());
        /// 或进行其他处理
    });
}

2.2 connect()第五参数与多线程

connnect()方法通过使用第五个参数来设定槽函数的调用关系,此参数有五个固定的枚举值:

  1. Qt::AutoConnection:自动连接,默认模式,根据信号和槽调用的线程自动进行选择。如果信号和槽在同一线程中,则使用Qt::DirectConnection类型连接;如果在不同线程,则使用Qt::QueuedConnection连接。
  2. Qt::DirectConnection:直接连接。当信号和槽运行在同一线程中时使用,其效果类似函数调用,直接在信号发送的位置调用槽函数。
  3. Qt::QueuedConnection:队列连接。当信号和槽运行在不同线程中时使用,信号发出时,不会立即调用槽,而是将其放入接收者所在线程的事件队列中,等到接收者当前函数执行完,进入事件循环之后,槽函数才会被调用。
  4. Qt::BlockingQueuedConnection:阻塞队列连接。当信号和槽运行在不同线程中时使用,类似于 QueuedConnection,但在调用槽时会阻塞信号的发送者,直到槽执行完成。所以如果信号和槽在同一线程时,就会造成死锁,适用于需要确保槽完成执行再继续其他操作的场景。
  5. Qt::UniqueConnection:唯一连接,可以与上述四个通过或字符|结合使用。此枚举值与上述四个不同,其不指定槽函数的调用方式,而是确保该连接在相同的信号和槽组合中是唯一的。如果尝试将同一信号和槽再次连接,连接将不会生效。这可以防止重复连接,避免一次触发信号多次运行槽函数。

示例:

QObject::connect(sender, &Sender::signal, receiver, &Receiver::slot, Qt::QueuedConnection | Qt::UniqueConnection);

3. 于QML中使用信号与槽

QML中的信号与槽机制与C++中的使用方法类似。但QML中定义的任意属性变量都有自己的信号,无需用户自己定义,几乎所有组件都是如此,一般使用时,更多情况会使用QML为这些对象设定的信号。当然也可以自定义信号和槽函数以及相互的连接。

3.1 监控QML默认定义的信号

QML中为组件默认属性或自定义属性都默认设置了信号,定义对应槽函数就可以在信号触发后调用。槽函数的格式为on+信号名(首字母大写),可由此自定义触发信号后的执行代码。

1. 组件属性

大多数QML组件都有内置信号,这些信号可以直接连接到槽函数来响应特定事件。

一般的默认属性都有自己的指定触发方式,比如Button组件通过点击触发clicked()信号,当然也可以直接调用clicked()手动触发信号。

示例:

// Button组件的clicked信号在用户点击按钮时触发
// 通过onClicked自定义代码,在信号触发时输出指定内容
Button {  
    onClicked: {  
        console.log("Button was clicked!");  
    }  
}  

2. 自定义属性变量

QML中的自定义属性变量也会自动创建变化信号,当属性值发生变化时会触发相应的信号。

对于QML中使用 property 声明的自定义属性,首字母必须小写,其发生改变时触发信号的槽函数格式为 on+属性名(首字母大写)+Changed

示例:

property int num: 0  

// 自动创建的信号,当num发生变化时触发  
onNumChanged: {  
    console.log("num changed to:", counter);  
}    

3.2 自定义信号与槽

1. 自定义信号signal

QML中也可以直接使用 signal 关键字定义专门的信号属性,可以带有参数,格式参考下方示例,同样首字母小写。

槽函数格式即为on+信号名(首字母大写)

触发信号直接使用信号名调用信号即可,触发信号后就直接执行槽函数。

注意: signal 关键字定义信号只能在QML文件的根组件使用

示例:

ColumeLayout { // 根组件
    signal mySignal(string data)  

    //信号触发时调用,同槽函数
    onMySignal: {  
        console.log(data)  
    }

    Button: {  
        text: "Emit mySignal"  
        onClicked: {
            // 触发信号
            mySignal("Hello World!"); 
        }
    }  
}  

2. Connections{}

Connections 组件用于处理信号的连接。重要的是它可以在不同作用域之间连接信号和槽,适合在运行时动态处理信号,特别是在定义和使用信号的组件不在同一上下文时。

格式上,使用target属性指定信号发出对象,然后就可以监听发出对象内部的信号,信号对应的槽函数必须用function修饰

注意:

  1. 因为是跨作用域连接信号,对方根组件的信号自然是能监听,但子组件的信号就监听不了了。就好比可以调用根组件的属性变量,调用不了子组件的属性变量
  2. 这个方法同样可以运用于C++注册给QML的类,监听C++中暴露的方法。

示例1:

// MyButton.qml
Button {
    property string myText: ''
    signal doubleClicked()

    onDoubleClicked: {
        console.log(myText + "double clicked")
    }
}

// MyItem.qml
ColumeLayout { // 根组件
    MyButton {
        id: myButton
    }

    Connections{
        target: myButton // 信号发出对象
        // 注意变量的信号名格式,注意function修饰
        function onTextChanged(){
           myFunc() // 执行本作用域的函数
        }
        function onDoubleClicked(){
           // 在本作用域再定义一个此信号的槽函数
        }
    }
    function myFunc(){
        // ...
    }
}

示例2:

Connections{
    target: qwidget // C++注册给QML的名称
    // 监听C++中暴露给QML的变量的变化
    function onWidthChanged(){
        // ...
    }
    function onHeightChanged(){
        // ...
    }
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇