首先看我上篇博客准备好环境:
现在进入主题。
先说答案:
- 事件包含事件内容,线程号,接收者,等各种信息,从我们经常写的event->pos就可以看出,还包括鼠标坐标等各种属性的。可以打开QEvent类的头文件看看就知道了。
QT事件循环原理_caicai_xiaobai的博客-CSDN博客
* 事件在qt中存放在一个队列里(准确来说是2个,操作系统产生的事件在一个队列,qt自己产生出的事件在另一个队列 postEventList),事件是逐个处理的。而一个事件的处理会派发给目标对象,如果没有被目标对象的事件处理函数返回true,且accept,那么还会继续派发给目标对象的父对象,爷爷对象,太爷爷对象,一直下去……因为qt源码的代码是这也写的: eventAccepted = (w == receiver ? mouse : &me)->isAccepted();
if (res && eventAccepted)
break;
w = w->parentWidget(); //不断寻找父窗口对象继续传递事件 源码详情,见我下面的图,就清楚了。
* 事件处理是异步的,也就是说你产生的事件(QWidget::update()函数,new出来一个paintEvent事件对象)会被QApplication::postEvent()放入Qt的消息队列队尾中,等待依次被处理。所以只有事件循环到这个事件的时候,期望的动作才会得到对应的执行。如果调用sendEvent()函数. 这时候事件不会放入队列, 而是 直接被派发实现立即处理(qt直接调用QApplication::notify()了),此时就是同步的了。QWidget::repaint()函数用的就是这种方式。
* 事件event默认是否已接收状态是不确定的,我们可以在处理函数里调用setAccept()或者ignore()函数来改变这个状态
* 重写事件函数event()时,函数返回true,而且事件是接收状态,则事件不会继续往父对象传递了, 进行队列里的下一个事件的处理,否则会传递给父对象。
注:有些特定事件是否传递给父对象不依赖于返回值,而是依赖与这个接收者类型,以及属性。比如鼠标移动事件 QEvent::MouseMove
if (w->isWindow() || w->testAttribute(Qt::WA_NoMousePropagation))
break
- 当我们重载event()函数时, 需要调用父类的event()函数来处理我们 不需要处理或是 不清楚如何处理的事件,实际开发中往往都是这样干的,而且不要随便去accept(),或者ignore()它,因为父类的这个函数自己知道应该怎么办,除非我们自己非常清楚这个事件的行为(比如我们 自定义的事件,或者 自己post给qt队列的事件)。
下面这个例子演示了如何重载event()函数, 改变Tab键的默认动作: (默认的是键盘焦点移动到
下一个控件上. )(注意返回值)
//下面这个例子演示了如何重载event()函数, 改变Tab键的默认动作: (默认的是键盘焦点移动到下一个控件上. )
bool CodeEditor::event(QEvent * event)
{
if (event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = (QKeyEvent *) event;
if (keyEvent->key() == Key_Tab)
{
insertAtCurrentPosition('\t');
return true;
}
}
return QWidget::event(event);
}
我们来看看 QWidget 的event()函数是如何定义的:(所以可以注意一下事件的accept状态的对返回值的影响)因此,上面的代码其实应该在第2个 if 里再加一句,event->accept();,当然好像 事件有默认值的,虽 传入event()函数前事件状态没有指定,但是打开 QEvent构造函数,可知, m_accept变量是初始化为true的,也就是默认状态是accept的,所以只要都没有调用accept()函数和ignore()函数,那么 事件状态默认就一直是accept状态的。而作为所有组件的父类 QWidget的默认实现则是 调用了ignore(),即鼓励继续向父对象(父控件)传递,所以QPushButton等控件,都是自己重新实现了事件处理函数,且调用的是accept()函数,就是不想给按钮的父控件传递了,这个确实应该这样,因为比如我们点击了按钮,当然不希望mainwindow等父控件也去响应一下,而是仅仅按钮自己响应这个事件即可。QAbstractButton部分源码:
void QAbstractButton::mouseReleaseEvent(QMouseEvent *e)
{
Q_D(QAbstractButton);
xxxxxxxxxx....;
if (e->button() != Qt::LeftButton) {
e->ignore();
return;
}
//检测mouseReleaseEvent事件发生时,坐标是否在按键区域内
if (hitButton(e->pos())) {
d->repeatTimer.stop();
d->click();
e->accept();//accept该事件,停止对事件往父对象(也就是父控件)转发
} else {
setDown(false);
e->ignore();
}
所以:重写event()等函数是时,如果要忽略事件,需要调用QWidget的默认实现函数一下,否则就等于接受了事件。
/*!
Contructs an event object of type \a type.
*/
QEvent::QEvent(Type type)
: d(0), t(type), posted(false), spont(false), m_accept(true)
{}
bool QWidget::event(QEvent *event)
{
switch (e->type()) {
case QEvent::KeyPress:
keyPressEvent((QKeyEvent *)event);
if (!((QKeyEvent *)event)->isAccepted())
return false;
break;
case QEvent::KeyRelease:
keyReleaseEvent((QKeyEvent *)event);
if (!((QKeyEvent *)event)->isAccepted())
return false;
break;
// more...
}
return true;
}
- 还能解释为什么一个最简单的qt窗口程序,也会有2个线程存在。c++ – Qt’s default threads – Stack Overflow
-
qt是事件驱动型,我们可以看到,如果没有消息的输入(也就是qt就没有事件了),此时qt的事件队列就是空的,就不会有那个事件处理大循环了,因此qt就不运行了,正是这样的特性,所以qt的效率高,因为不吃cpu的性能。
-
安装事件过滤器时,eventFilter(receiver, event))函数返回true,则目标对象的event()函数(这个就是目标对象的事件处理函数)得不到执行了。
- 事件过滤器和被安装过滤器的组件必须在同一线程,否则,过滤器将不起作用。另外,如果在安装过滤器之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。
- 我们可以给qApp(每个程序中唯一的QApplication对象)装上过滤器,那么所有的事件在发往任何其他的过滤器时,都要先经过当前这个eventFilter()。这种行为会严重降低整个应用程序的事件分发效率,要看具体情况使用(比如debug时候,就有人这么干)。
- 拿到一个对象的事件,有两种方案,继承这个类,重写这个类的事件函数event(),当然我们如果没有奇怪的需求,那么重写event()函数里面的已有的特定虚函数比如mousePressEvent()等即可。但是这个方案太重了。另一个方案是,另一个对象安装为这个对象的事件过滤器,这样那个对象就能优先拿到这个对象的事件,我们在那个对象的事件过滤器虚函数里进行操作事件即可,这个方案比较轻量化。
比如 B->installEventFilter(A),那么此时A就能优先拿到即将派发给B的事件
C->installEventFilter(A),一个对象可以给多个对象安装事件过滤器的。此时A就能优先拿到B和C的事件。
同样, 一个对象能同时被安装多个过滤器, 在事件到达之后, 这些过滤器以安装次序的反序被调用。
然后, A要重载QObject::eventFilter()函数, 在eventFilter() 中书写对事件进行处理的代码。
用这种方法改写上面的例子: (假设我们将CodeEditor 放在MainWidget中)(注意返回值)
MainWidget::MainWidget()
{
CodeEditor * ce = new CodeEditor( this, "code editor");
ce->installEventFilter( this );
}
bool MainWidget::eventFilter( QOject * target , QEvent * event )
{
if( target == ce )
{
if( event->type() == QEvent::KeyPress )
{
QKeyEvent *ke = (QKeyEvent *) event;
if( ke->key() == Key_Tab )
{
ce->insertAtCurrentPosition('\t');
return true;
}
}
}
return false;
}
值得参考博客:Qt Event-暗夜linux-ChinaUnix博客
- QMessageBox虽然是模特运行,但是它里面的QMessageBox::exec()程序维护的事件循环队列和QApplication::exec()维护的是 同一个事件循环队列,并且 Qt程序仅此一个事件循环队列,所以主窗口的事件(比如定时器事件)仍然能得到处理的。看这个博客的最后一个例子:QT-qevent 事件的accept()和ignore()_luckyone906的博客-CSDN博客_event->accept()
qt事件循环和处理过程(我跟踪源码得到的)如下图所示:只需要好好看这个图,你就能明白一切了
(目前,我仍然还有个疑问,关于windows的消息编程的那几个windows的api函数,我还是不清楚消息是怎么传递的???莫非一个是界面消息(比如鼠标点击),一个是系统异步事件消息(比如socket)???不知道我画的这个箭头传递方向对不对(我调试发现过程好像是这样的,但是原理是啥我不知道),知道的记得评论区告诉我喔)
熟悉了这个过程后,我们再来看几个例子练习一下:QT-qevent 事件的accept()和ignore()_luckyone906的博客-CSDN博客_event->accept()
threadData:记录自己线程上的事件派发器(eventDispatcher)、事件队列、对象列表等信息。是独一份的,每个派发器,派发对象它们都有它的指针
QApplication::exec()
QGuiApplication::exec();
QCoreApplication::exec();
QEventLoop eventLoop;
eventLoop.exec();
eventLoop.processEvents(flags | WaitForMoreEvents | EventLoopExec);
threadData->eventDispatcher.load()->processEvents(flags);
//eventDispatcher是基类指针,子类化的有QEventDispatcherWin32、QEventDispatcherBlackberry、QEventDispatcherUNIX等
//这个是在函数QCoreApplicationPrivate::createEventDispatcher()里面根据平台宏定义来创建的
QEventDispatcherWin32::createInternalHwnd()
//创建一个windows系统的隐形窗口,用于接收windows系统所有派发事件
static HWND qt_create_internal_window(const QEventDispatcherWin32 *eventDispatcher)
//里面为它注册了一个叫做qt_internal_proc的WNDPROC函数
QEventDispatcherWin32::installMessageHook()
//注册系统钩子qt_GetMessageHook函数,截获操作系统系统所有事件(注意:这个钩子函数是在操作系统自己的线程执行的)
while(canWait)
反复查询PeekMessage(&msg, 0, 0, 0, PM_REMOVE)的消息(这个就是上面钩子函数截获的消息)
相应放入用户输入事件的队列 queuedUserInputEvents
相应放入用户输入事件的队列 queuedSocketEvents
canWait = (!retVal && !d->interrupt && (flags & QEventLoop::WaitForMoreEvents));
//如果消息处理完了,则阻塞自己,等到操作系统有消息发送过来,才会继续往下执行
MsgWaitForMultipleObjectsEx(nCount, pHandles, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);
LRESULT QT_WIN_CALLBACK qt_GetMessageHook(int code, WPARAM wp, LPARAM lp)
//由于这个函数是在操作系统线程里执行的???所以要尽量的快
消息是PM_REMOVE,则
PostMessage(d->internalHwnd, WM_QT_SENDPOSTEDEVENTS, 0, 0);
//通过PostMessage()函数将事件发送到那个隐形窗口对来处理,对象窗口专门处理
LRESULT QT_WIN_CALLBACK qt_internal_proc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
//处理上面说的隐形窗口的消息
QWindowsGuiEventDispatcher::sendPostedEvents()
QEventDispatcherWin32::sendPostedEvents();
QWindowSystemInterface::sendWindowSystemEvents(m_flags);
QGuiApplicationPrivate::processWindowSystemEvent(event);
QGuiApplicationPrivate::processWheelEvent(static_cast
QGuiApplicationPrivate::processMouseEvent(static_cast
QGuiApplication::sendSpontaneousEvent(window, &ev);
notifyInternal2(receiver, event)
QCoreApplication::notify(QObject receiver, QEvent event)
//这个是虚函数,会对应到子类的具体函数,一般是QApplication::notify
bool eventAccepted = mouse->isAccepted(); //也就是事件默认是接收状态
QPointer
while (w)
res = d->notify_helper(w, w == receiver ? mouse : &me);
// 这里让事件过滤器先执行了
if (sendThroughObjectEventFilters(receiver, e))
return true;
// 遍历安装到receiver的事件过滤器对象们,先让它们的eventFilter()函数执行
for (int i = 0; i < receiver->d_func()->extraData->eventFilters.size(); ++i)
if (obj->eventFilter(receiver, event)) //这儿就是我们熟悉的事件过滤器函数了
return true;
// deliver the event
bool consumed = receiver->event(e); //这儿就是我们熟悉的可以重写的事件处理函数了
//这里以继承Qobject的Qwidget为例:
bool QWidget::event(QEvent event)
switch (event->type()) {
case QEvent::MouseMove:
mouseMoveEvent((QMouseEvent)event);
break;
case QEvent::MouseButtonPress:
mousePressEvent((QMouseEvent*)event);
break;
。。。
QCoreApplicationPrivate::setEventSpontaneous(e, false);
return consumed;
eventAccepted = (w == receiver ? mouse : &me)->isAccepted();
if (res && eventAccepted)
break;
w = w->parentWidget(); //不断寻找父窗口对象
参考博客:
【QT】深入了解QT消息循环及线程相关性_伐尘的博客-CSDN博客_qt 消息循环
另外一些参考博客:
Original: https://blog.csdn.net/kangkanglhb88008/article/details/127801104
Author: 我是标同学
Title: qt源码解析1–事件循环原理(重写事件函数,事件过滤器等)
原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/656274/
转载文章受原作者版权保护。转载请注明原作者出处!