笔记-信号和槽

笔记-信号和槽

事件驱动模型

事件驱动模型
Qt事件机制

信号和槽的原理和实现

信号和槽的原理和实现
QMetaObject:: activate

  • SLOT和SIGNAL宏会利用预编译器将一些参数转化成字符串,并且在前面添加上编码。

    在调试模式中,如果signal的连接出现问题,提示警告信息的时候还会注明对应的文件位置。qFlagLocation 用于定位代码对应的行信息,会将对应代码的地址信息注册到一个有两个入口的表里。

    Q_CORE_EXPORT const char *qFlagLocation(const char *method);
    ...
    # define SLOT(a)     qFlagLocation("1"#a QLOCATION)
    # define SIGNAL(a)   qFlagLocation("2"#a QLOCATION)
    
    onnect(this, SIGNAL(ageChanged(int)), this, SLOT(onAgeChanged(int)));
    connect(this, SIGNAL(scoreChanged(int)), this, SLOT(onScoreChanged(int)));
    
    connect(this, qFlagLocation("2""ageChanged(int)" "\0" "Object.h" ":" "54"), this, qFlagLocation("1""onAgeChanged(int)" "\0" "Object.h" ":" "54"));
    connect(this, qFlagLocation("2""scoreChanged(int)" "\0" "Object.h" ":" "55"), this, qFlagLocation("1""onScoreChanged(int)" "\0" "Object.h" ":" "55"));
    
  • 类的元对象
    程序编译时make调用MOC对工程源码进行解析,生成相应类的moc_xxx.cpp文件

  • 元数据表
    Qt程序编译时make会调用MOC工具对源文件进行分析,如果某个类包含了Q_OBJECT宏,MOC会生成对应的moc_xxx.cpp文件。

  • 信号的实现
    MOC在生成的moc_xxx.cpp文件中实现了信号,创建了一个指向参数的指针的数组,并将指针数组传给QMetaObject::activate函数。数组的第一个元素是返回值。本例中值是0,因为返回值是void。

    void HANS_iWatch::sig_nalRunOver_Mes()
    {
        QMetaObject::activate(this, &staticMetaObject, 25, nullptr);
    }
    
    // SIGNAL 26
    void HANS_iWatch::sigDatasave(const int & _t1, const S_ProcessData & _t2)
    {
        void *_a[] = { nullptr, const_cast<void*>(reinterpret_cast<const void*>(&_t1)), const_cast<void*>(reinterpret_cast<const void*>(&_t2)) };
        QMetaObject::activate(this, &staticMetaObject, 26, _a);
    }
    
  • 槽函数的调用
    利用槽函数在qt_static_metacall 函数的索引位置来调用槽函数

    void HANS_iWatch::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void **_a) {
        if (_c == QMetaObject::InvokeMetaMethod) {
            HANS_iWatch *_t = static_cast<HANS_iWatch *>(_o);
            Q_UNUSED(_t)
            switch (_id) {
            case 0: _t->startGetM_Input(); break;
            case 1: _t->startGetE1_Input(); break;
            case 7: _t->sig_DisplayPosition_1((*reinterpret_cast< const QString(*)>(_a[1]))); break;
            case 8: _t->sig_DisplayPosition_2((*reinterpret_cast< const QString(*)>(_a[1]))); break;
            default: ;
        }
    } 
    
  • 信号和槽的链接QObject::connect函数
    开始链接, qt再发送者和接收者的元对象中通过字符串找到信号和槽的索引, 然后发送者(信号方)创建一个QObjectPrivate::Connection对象, 对信号和槽的索引添加到QObjectPrivate对象的双向链表中. 在接收对象销毁的时候,相应的连接也能够被自动销毁。所以每一个接收对象都需要知道谁连接到它自己,以便能够清理连接。
    注意的每个类都有一个QObjectConnectionListVector *connectionLists(双向链表), 连接链表容器, 每一个信号都有一个QObjectPrivate::Connection(链表) , 连接链表容器保存每个信号的Connection链表, 所以结构式双向链表中维护的节点式普通链表.

  • 信号的发送
    实际调用的是元对象中生成的信号函数

    void HANS_iWatch::sig_nalRunOver_Mes()
    {
        QMetaObject::activate(this, &staticMetaObject, 25, nullptr);
    }
    

    信号函数中再调用QMetaObject::activate
    activate主要的流程是

    1. 判断发送的信号有无槽连接, 没有直接返回
    2. 判断是否阻塞, 阻塞直接返回
    3. 获取发送者的连接链表容器
    4. 使用信号索引获得连接链表容器中信号的连接链表
    5. 判断连接类型, 通过不同的连接类型对连接中的槽执行不同的操作

信号槽的调用调用过程?

Object::qt_metacall函数内部调用了Object::setAge函数,setAge内部调用Object::ageChanged信号函数,ageChanged信号函数内部调用了QMetaObject::activate函数,activate函数内部调用Object::qt_static_metacall函数,最终qt_static_metacall函数内部调用了槽函数onAgeChanged。

Q_OBJECT对象生成的moc文件

qt利用预编译生成对象的moc文件, moc文件中会生成:
类的函数字符串qt_meta_stringdata_HANS_iWatch_t
元数据表qt_meta_data_Object,保存类的元数据信息, 信号的索引以及信号相关参数的参数表述,槽索引和参数等相关信息, 属性, 枚举
类的元对象staticMetaObject,用来保存该类和相关基类的元数据指针
信号的实现, 信号由参数则创建一个指向参数的指针数组,并将指针传给QMetaObject::activate函数, 函数的参数中由信号的索引和参数指针, 参数指针中的第一个值为返回值
槽的调用qt_static_metacall, 在这个函数中根据传入的索引调用对应的槽函数
外部调用qt_metacall, qt_metacall中实现提供给外部来调用qt_static_metacall, QMetaObject::activate源码中调用qt_metacall来调用槽函数

信号和槽多线程

  • 对象所在线程为创建对象的线程
  • QObject::connect类型
    • Qt::DirectConnection, sender和receiver在同个线程
    • Qt::QueuedConnection, 信号发送后返回,相关槽函数由receiver所在的线程在返回到事件循环后执行

信号和槽机制(signal and slot)

信号和槽机制用于完成界面操作的相应,是完成任意两个Qt对象之间的通信机制。信号会在某个特定情况或动作下被触发,槽是等同于接受并处理信号的函数。若要将一个窗口部件的变换情况告知另一窗口部件,则向另一窗口部件发送信号。每个Qt对象都包含若各个预定义的信号和槽,当某一特定事件发生时,一个信号被发送,与信号相关联的槽则会相应信号并完成相应的操作。
信号和槽可以自定义。

信号和信号相连,槽相应信号

connect(Object1,SIGNAL(signal1),Object2,SIGNAL(signal2));
connect(Object1,SIGNAL(signal1),Object2,SLOT(slot));
signal如clicked(),slot如showarea()

信号和槽的优点

(1)类型安全。关联的信号和槽的签名必须相同。
(2)松散耦合。减弱Qt对象间的耦合度。
但是,增加了对象间同信的灵活度,但是运行速度较回调函数慢。
通过传递一个信号来调用槽函数将会比直接调用非虚函数运行速度慢10倍。
(1)需要定位接受信号的对象。
(2)安全地历遍所有关联。
(3)编组(Marshal)和解组(unmarshal)传递的参数。
(4)多线程时,信号可能需要排队。

所谓信号槽,实际就是观察者模式

connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
    QObject::connect(&pushButton,SIGNAL(clicked(bool)),&w,SLOT(show()));
connect(const QObject *sender, const char *signal, const char *method, Qt::ConnectionType type)
    QObject::connect(&pushButton,&QPushButton::clicked,&w,&QWidget::show);
connect(const QObject *sender, PointerToMemberFunction signal, const QObject *receiver, PointerToMemberFunction method, Qt::ConnectionType type)
    QObject::connect(&pushButton,&QPushButton::clicked,&w,&QWidget::show);
connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)MainWindow *w = new MainWindow;
    QObject::connect(&pushButton,&QPushButton::clicked,[=](){w->show();})
connect(const QObject *sender, PointerToMemberFunction signal, const QObject *context, Functor functor, Qt::ConnectionType type)

关键点

class Newspaper : public QObject{
    Q_OBJECT
public:
    Newspaper(const QString & name):m_name(name) {}
    void send(){emit newPaper(m_name);}
signals:
    void newPaper(const QString &name);
private:
    QString m_name;
}
  • Q_OBJECT:发送者和接收者都需要是QObject的子类(当然,槽函数是全局函数、Lambda 表达式等无需接收者的时候除外);
  • signal:使用 signals 标记信号函数,信号是一个函数声明,返回 void,不需要实现函数代码;
  • 槽函数是普通的成员函数,作为成员函数,会受到 public、private、protected 的影响;
  • emit:使用 emit 在恰当的位置发送信号;
  • 使用QObject::connect()函数连接信号和槽。
  • 由于 moc 只处理头文件中的标记了Q_OBJECT的类声明,不会处理 cpp 文件中的类似声明。因此,如果我们的Newspaper和Reader类位于 main.cpp 中,是无法得到 moc 的处理的。解决方法是,我们手动调用 moc 工具处理 main.cpp,并且将 main.cpp 中的#include "newspaper.h"改为#include "moc_newspaper.h"就可以了。
  • signals 块所列出的,就是该类的信号。信号就是一个个的函数名,返回值是 void(因为无法获得信号的返回值,所以也就无需返回任何值),参数是该类需要让外界知道的数据。信号作为函数名,不需要在 cpp 函数中添加任何实现(我们曾经说过,Qt 程序能够使用普通的 make 进行编译。没有实现的函数名怎么会通过编译?原因还是在 moc,moc 会帮我们实现信号函数所需要的函数体,所以说,moc 并不是单纯的将 Q_OBJECT 展开,而是做了很多额外的操作)。

常见问题

  • 有重载的信号

    • 解决方法:使用一个函数指针来说明指向哪个信号
      • 定义函数指针
        void (Newspaper:: *newPaperNameDate)(const QString &, const QDate &) = &Newspaper::newPaper;
        QObject::connect(&newspaper, newPaperNameDate, &reader, &Reader::receiveNewspaper);

      • C强制类型转换

            QObject::connect(&newspaper,(void (Newspaper:: *)(const QString &, const QDate &))&Newspaper::newPaper,&reader, &Reader::receiveNewspaper);
        
      • C++类型转换

        QObject::connect(&newspaper, static_cast<void (Newspaper:: *)(const QString &, const QDate &)>(&Newspaper::newPaper),&reader,&Reader::receiveNewspaper);
        
  • 带有默认参数的槽函数

    • 解决方法:使用lambda表达式。

      Object::connect(&newspaper,static_cast<void (Newspaper:: *)(const QString &)>(&Newspaper::newPaper),[=](const QString &name) { /* Your code here. */ });
      

信号和槽的参数传递

  • 当信号和槽函数的参数数量相同时,它们点的参数类型要完全一致。
  • 当信号和槽函数的参数数量不同时,只能是信号的参数数量大于槽的参数数量而且,前面相同数量的参数类型应该一致,信号中多余的参数被忽略。
  • 在不进行参数传递时信号的参数也应该大于槽的参数。
  • 当槽参数带有默认值的时候,槽的参数可以 大于信号的参数。

信号和槽的连接方式

  1. Qt::AutoConnection
    (Default) If the receiver lives in the thread that emits the signal, Qt::DirectConnection is used. Otherwise, Qt::QueuedConnection is used. The connection type is determined when the signal is emitted.
    自动方式,当信号和槽在同一个线程的时候采用DirectConnection,不同线程的时候异步采用QueuedConnection
  2. Qt::DirectConnection
    The slot is invoked immediately when the signal is emitted. The slot is executed in the signalling thread.
    直接连接,在同一个线程的时候使用,信号槽直接触发不需要排队
  3. Qt::QueuedConnection
    The slot is invoked when control returns to the event loop of the receiver’s thread. The slot is executed in the receiver’s thread.
    队列连接,信号和槽不同线程的时候使用,将信号所对应的的槽的执行放入队列之中,等待槽所在线程空闲时执行
  4. Qt::BlockingQueuedConnection
    Same as Qt::QueuedConnection, except that the signalling thread blocks until the slot returns. This connection must not be used if the receiver lives in the signalling thread, or else the application will deadlock.
  5. Qt::UniqueConnection
    This is a flag that can be combined with any one of the above connection types, using a bitwise OR. When Qt::UniqueConnection is set, QObject::connect() will fail if the connection already exists (i.e. if the same signal is already connected to the same slot for the same pair of objects). This flag was introduced in Qt 4.6.

在使用lambda连接信号槽的时候有一个注意点即

connect(this, &HousingAirLeakTest::Signal_TE_Logs_AppendText, [=](const QString &message) {
        QDateTime datatime = QDateTime::currentDateTime();
        QString time = datatime.toString("yyyy-MM-dd HH:mm:ss:zzz");
        if (ui.TE_Logs->document()->lineCount() > 3000)
        {
            ui.TE_Logs->clear();
        }
        ui.TE_Logs->append("[" + time + "] " + message);
    });

connect(this, &HousingAirLeakTest::Signal_TE_Logs_AppendText,this, [=](const QString &message) {
        QDateTime datatime = QDateTime::currentDateTime();
        QString time = datatime.toString("yyyy-MM-dd HH:mm:ss:zzz");
        if (ui.TE_Logs->document()->lineCount() > 3000)
        {
            ui.TE_Logs->clear();
        }
        ui.TE_Logs->append("[" + time + "] " + message);
    });

的连接方式是不同的
前者的连接方式Qt::DirectConnection而后者的连接方式Qt::AutoConnection,所以在多线程使用的时候要注意,保险使用添加槽的对象

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在QML中,信号函数是一种用于实现对象间通信的机制,类似于Qt中的信号信号是来自QML对象的通知,而函数则是由信号触发的表达式或函数。在QML中,可以使用signal关键字定义有参或无参的信号。发送信号时,可以使用emit关键字加上信号名和参数进行发送。 为了让信号函数连接起来,需要使用connect函数进行连接。在Qt/C中,连接信号函数需要保持参数顺序和类型一致。而在QML中,连接信号函数更加方便,可以直接在信号之间使用Connections组件进行连接。 例如,在一个QML的Button组件中,可以定义一个信号testSig,然后在按钮点击事件中使用emit关键字发送信号。接着可以使用Connections组件来绑定信号函数,通过onTestSig函数来接收信号函数可以在函数体内进行任意的处理,例如打印参数值。 总结来说,QML中的信号函数机制与Qt中类似,可以用于实现对象间的通信和事件处理。通过定义信号、发送信号和连接信号函数,可以实现灵活的交互和逻辑处理。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [【QML事件处理】信号](https://blog.csdn.net/m0_60259116/article/details/129265884)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [Qml中信号的发送与接收信号函数的使用](https://blog.csdn.net/Shado_walker/article/details/51243408)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [QML学习笔记【05】:QML信号](https://blog.csdn.net/m0_37845735/article/details/128511282)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值