事件驱动模型
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)" "" "Object.h" ":" "54"), this, qFlagLocation("1""onAgeChanged(int)" "" "Object.h" ":" "54"));connect(this, qFlagLocation("2""scoreChanged(int)" "" "Object.h" ":" "55"), this, qFlagLocation("1""onScoreChanged(int)" "" "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 26void HANS_iWatch::sigDatasave(const int & _t1, const S_ProcessData & _t2){ void *_a[] = { nullptr, const_cast
槽函数的调用
利用槽函数在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
信号和槽的链接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主要的流程是
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_OBJECTpublic: 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
带有默认参数的槽函数
解决方法:使用lambda表达式。
Object::connect(&newspaper,static_cast
当信号和槽函数的参数数量相同时,它们点的参数类型要完全一致。当信号和槽函数的参数数量不同时,只能是信号的参数数量大于槽的参数数量而且,前面相同数量的参数类型应该一致,信号中多余的参数被忽略。在不进行参数传递时信号的参数也应该大于槽的参数。当槽参数带有默认值的时候,槽的参数可以 大于信号的参数。 信号和槽的连接方式 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,不同线程的时候异步采用QueuedConnectionQt::DirectConnection
The slot is invoked immediately when the signal is emitted、The slot is executed in the signalling thread.
直接连接,在同一个线程的时候使用,信号槽直接触发不需要排队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.
队列连接,信号和槽不同线程的时候使用,将信号所对应的的槽的执行放入队列之中,等待槽所在线程空闲时执行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.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,所以在多线程使用的时候要注意,保险使用添加槽的对象