相同点
- 目的都是为了解决对象间通信的问题。
- 事件发生或信号发射时会有相应的处理函数。
不同之处
-
返回值
- 事件有返回值,并且有意义,我们要根据这个返回值来确定是否还要继续事件的处理,比如在QT中,事件处理函数如果返回true,则这个事件处理 已完成,QApplication会接着处理下一个事件,而如果返回false,那么事件分派函数会继续向上寻找下一个可以处理该事件的注册方法。
- 信号处理函数没有返回值。
-
响应时优先级
- 在QT中,事件使用了一个事件队列来维护,如果事件的处理中又产生了新的事件,那么新的事件会加入到队列尾,直到当前事件处理完毕后, QApplication再去队列头取下一个事件来处理。
- 信号类似于硬件中的中断,当一个信号产生后,他上面注册的所有槽都会被按顺序立即执行。相应的,产生递归调用的问题,比如在某个信号处理槽中又产生了一个信号,当前处理会被打断,程序会去处理之。
-
有多个响应处理函数时顺序
- 事件因为都是与窗口相关的,所以事件回调时都是从当前窗口开始,一级一级向上派发,直到有一个窗口返回true,截断了事件的处理为止。
- 信号可以在注册时显示地指明槽的位置,如果没有指明,默认是随机顺序。
信号和槽的机制
当某个事件发生之后,比如,按钮检测到自己被点击了一下,它就会发出一个信号(signal)。这种发出是没有目的的,类似广播。如果有对象对这个信号感兴趣,它就会使用连接(connect)函数,将想要处理的信号和自己的一个函数(槽函数(slot))绑定并处理这个信号。
- 槽函数类似于回调函数。
- 调用时机
一般情况下,信号发出后会立即执行。
特殊情况:当使用queued connections时,情况略有不同,在这种情况下,emit关键字后面的代码将立即继续,插槽将稍后执行。
属性
声明
参数
连接与断开
信号连接信号
信号与槽(信号)个数的关系
信号的触发
参考
https://blog.csdn.net/maizousidemao/article/details/104199773
Qt信号槽-原理分析
信号与槽的连接方式
- 事件是对象。可以被任何QObject的实例收到和处理。
如何传递事件
事件发生时,qt创建了一个代表它的对象,
事件类型
事件处理
一般的事件传递方式是调用virtual函数。可以把自己想做的处理写在当前函数,然后把其他的都传递给base类处理。
void MyCheckBox::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
// handle left mouse button here
} else {
// pass on other buttons to base class
QCheckBox::mousePressEvent(event);
}
}
更通用的处理方式:
bool MyWidget::event(QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *ke = static_cast<QKeyEvent *>(event);
if (ke->key() == Qt::Key_Tab) {
// special tab handling here
return true;
}
} else if (event->type() == MyCustomEventType) {
MyCustomEvent *myEvent = static_cast<MyCustomEvent *>(event);
// custom event handling here
return true;
}
return QWidget::event(event);
}
- 注意返回值是true代表着事件处理完毕,不再传递给下一个处理函数。
事件过滤
可以在事件处理之前先通过filter,阻止特定的event传递给target widget,通过installEventFilter和removeEventFilter控制。
bool FilterObject::eventFilter(QObject *object, QEvent *event)
{
if (object == target && event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Tab) {
// Special tab handling
return true;
} else
return false;
}
return false;
}
发送事件
用户可以自定义自己的事件,并生成一个实例,通过sendEvent()和postEvent()发送它们。
多线程
两种方式
1. 利用moveToThread()(推荐使用)
原型:void QObject::moveToThread(QThread *targetThread)
myObject->moveToThread(QApplication::instance()->thread());
改变该对象与线程之间执行的关系,并不改变谁拥有该对象,只是让该对象执行成员函数时利用了target thread。「在哪创建就属于哪」这句话放在任何地方都是适用的。
- 注意
使用 moveToThread() 有一些需要注意的地方,首先就是类对象不能有父对象,否则无法将该对象“移动”到新线程。如果类对象保存在栈上,自然销毁由操作系统自动完成;如果是保存在堆上,没有父对象的指针要想正常销毁,需要将线程的 finished() 信号关联到 QObject 的 deleteLater() 让其在正确的时机被销毁。其次是该对象一旦“移动”到新线程,那么该对象中的计时器(如果有 QTimer 等计时器成员变量)将重新启动。
2. 继承QThread类,并重写run()。
- 全局线程(和UI一直存在的线程)
- 局部线程(用完即释放的线程)
它们的结束方式不一样。
注意如何正确退出一个线程
参考
https://blog.csdn.net/czyt1988/article/details/64441443
线程池
- 使用QRunnable+QThreadPool
QRunnable表示一个可执行的任务,把它放到QThreadPool里的线程中执行。
消息队列
一、什么是Qt消息循环
Qt消息循环,就是从一个队列中不断取出消息,并响应消息的过程。窗体的鼠标、键盘、输入法、绘制,各种消息,都来自于Qt的消息循环。以Windows操作系统为例,Qt接管Windows原生窗口消息,并翻译成Qt的消息,派发给程序下的各个子对象、子QWidget等,通过接管层,可以很好屏蔽不同平台之间的差异性,开发人员不需要关心Windows或者X11的消息的差异性,只需要搞清楚各个QEvent之间是什么含义。
最开始的Qt消息循环开始于QCoreApplication::exec()。用户创建出一个QCoreApplication,或者说更多情况下是QApplication,执行QCoreApplication::exec(),一个应用程序便开始了。QCoreApplication会不断从操作系统获取消息,并且分发给QObject。
如果没有消息循环,那么Qt的信号和槽无法完全使用,有些函数也无法正确执行。举个例子,通过QueuedConnection连接的信号,其实是将一个事件压入了消息循环,如果没有QCoreApplication::exec(),那么这个消息循环将永远无法派发到指定的对象。
二、什么是线程相关性
准确点来说,应该是指QObject的线程相关性。以Qt文档中的示意图来作说明:
当我们创建一个QObject时,它会与创建自己所在的线程绑定。它参与的消息循环,其实是它所在线程的消息循环,如上图所示。假如某个线程没有默认的QThread::exec(),那么该线程上的QObject则无法接收到事件。另外,如果两个不同线程的QObject需要相互通信,那么只能通过QueuedConnection的方式,异步通知对方线程,在下一轮消息循环处理QObject的消息。
QObject的线程相关性默认会和它的parent保持一致。如果一个QObject没有parent,那么可以通过moveToThread,将它的线程相关性切换到指定线程。
了解QObject的线程相关性非常重要,很多初学者常常分不清一个多线程中哪些QObject应该由主线程创建,哪些应该由工作线程创建,我的观点是,它参与哪个消息循环,就由哪个来创建。
正因为这样的特性,我们才可以理解什么叫做AutoConnection。通过AutoConnect连接的两个QObject,如果是在同一个线程,那么可以直接调用(DirectConnection),如果不是在同一个线程,那么就通过事件通知的方式(QueuedConnection)来调用。通过信号和槽、事件或者QueuedConnection方式来进行线程间的通讯,尤其是与UI线程通讯,永远是最优雅的方式之一。