Qt 事件机制

news/2024/4/27 6:18:29/文章来源:https://blog.csdn.net/QtCompany/article/details/129263303

【1】事件

事件是可以被控件识别的操作。如按下确定按钮、选择某个单选按钮或复选框。

每种控件有自己可识别的事件,如窗体的加载、单击、双击等事件,编辑框(文本框)的文本改变事件等等。

事件就是用户对窗口上各种组件的操作。

【2】Qt事件

由窗口系统或Qt自身产生的,用以响应所发生各类事情的操作。具体点,Qt事件是一个QEvent对象,用于描述程序内部或外部发生的动作。

【3】Qt事件产生类型

1、键盘或鼠标事件:用户按下或松开键盘或鼠标上的按键时,就可以产生一个键盘或者鼠标事件。

2、绘制事件:某个窗口第一次显示的时候,就会产生一个绘制事件,用来告诉窗口需要重新绘制它本身,从而使得该窗口可见。

3、QT事件:Qt自己也会产生很多事件,比如QObject::startTimer()会触发QTimerEvent。

【4】Qt事件分类

基于事件如何被产生与分发,可以把事件分为三类:

1、Spontaneous 事件

由窗口系统产生,它们被放到系统队列中,通过事件循环逐个处理。

本类事件通常是Windows System把从系统得到的消息,比如鼠标按键、键盘按键等, 放入系统的消息队列中。 Qt事件循环的时候读取这些事件,转化为QEvent,再依次逐个处理。

2、Posted 事件

由Qt或应用程序产生,它们被Qt组成队列,再通过事件循环处理。

调用QApplication::postEvent()来产生一个posted类型事件。例如:QWidget::update()函数,当需要重新绘制屏幕时,程序调用update()函数。

其实现的原理是new出一个paintEvent,调用 QApplication::postEvent(),将其放入Qt的消息队列中,等待依次被处理。

3、Send事件

由Qt或应用程序产生,但它们被直接发送到目标对象。

调用QApplication::sendEvent()函数来产生一个send类型事件。

send 类型事件不会放入队列, 而是直接被派发和处理, QWidget::repaint()函数用的就是这种方式。

【5】QObject类

QObject三大职责

1、内存管理

2、内省(intropection)

3、事件处理机制

任何一个想要接受并处理事件的对象均须继承自QObject,可以重写QObject::event() 来处理事件,也可以由父类处理。

【6】事件处理与过滤

Qt提供了5个级别来处理和过滤事件。

1、我们可以重新实现特定的event handler。

重新实现像mousePressEvent(), keyPressEvent()和paintEvent()这样的event Handler是目前处理event最普通的方式。

2、我们可以重新实现QObject::event()。

通过重新实现event(),我们可以在事件到达特定的event handler之前对它们作出处理。

这个方法主要是用来覆写Tab键的缺省实现,也可以用来处理不同发生的事件类型,对它们,就没有特定的event handler。

当重新实现event()的时候,我们必须调用基类的event()来处理我们不显式处理的情况。

3、我们可以安装一个event filter到一个单独的QObject。

一旦一个对象用installEventFilter注册了, 发到目标对象的所有事件都会先发到监测对象的eventFilter()。

如果同一个object安装了多个event filter, filter会依次被激活, 从最近安装的回到第一个。

4、我们可以在QApplication对象上安装event filter。

一旦一个event filter被注册到qApp(唯一的QApplication对象), 程序里发到每个对象的每个事件在发到其他event filter之前,都要首先发到eventFilter()。

这个方法对debugging非常有用,也可以用来处理发到disable的widget上的事件, QApplication通常会丢弃它们。

5、我们可以子类化QApplication并重新实现notify()。

Qt调用QApplication::notify()来发出事件,在任何event filter得到之前, 重新实现这个函数是得到所有事件的唯一方法。

event filter通常更有用, 因为可以有任意数目且同时存在的event filter, 但是只有一个notify()函数。

【7】事件过滤器

Qt创建QEvent事件对象后,会调用QObject的event()函数来分发事件。

但有时,你可能需要在调用event()函数之前做一些自己的操作,比如,对话框上某些组件可能并不需要响应回车键按下的事件,此时,你就需要重新定义组件的event()函数。

如果组件很多,就需要重写很多次event()函数,这显然没有效率。为此,你可以使用一个事件过滤器,来判断是否需要调用组件的event()函数。

QOjbect有一个eventFilter()函数,用于建立事件过滤器。这个函数的声明如下:

virtual bool QObject::eventFilter (QObject * watched, QEvent * event)

在创建了过滤器之后,下面要做的是安装这个过滤器。安装过滤器需要调用installEventFilter()函数。这个函数的声明如下:

void QObject::installEventFilter ( QObject * filterObj)

这个函数是QObject的一个函数,因此可以安装到任何QObject的子类,并不仅仅是UI组件。

这个函数接收一个QObject对象,调用了这个函数安装事件过滤器的组件会调用filterObj定义的eventFilter()函数。

例如,textField.installEventFilter(obj),则如果有事件发送到textField组件时,会先调用obj->eventFilter()函数,然后才会调用textField.event()。

当然,你也可以把事件过滤器安装到QApplication上面,这样就可以过滤所有的事件,已获得更大的控制权。不过,这样做的后果就是会降低事件分发的效率。

我们可以把Qt的事件传递看成链状:如果子类没有处理这个事件,就会继续向其他类传递。其实,Qt的事件对象都有一个accept()函数和ignore()函数。

正如它们的名字,前者用来告诉Qt,事件处理函数“接收”了这个事件,不要再传递;后者则告诉Qt,事件处理函数“忽略”了这个事件,需要继续传递,寻找另外的接受者。

在事件处理函数中,可以使用isAccepted()来查询这个事件是不是已经被接收了。

事实上,我们很少使用accept()和ignore()函数,而是像上面的示例一样,如果希望忽略事件,只要调用父类的响应函数即可。

记得我们曾经说过,Qt中的事件大部分是protected的,因此,重写的函数必定存在着其父类中的响应函数,这个方法是可行的。

为什么要这么做呢?因为我们无法确认父类中的这个处理函数没有操作,如果我们在子类中直接忽略事件,Qt不会再去寻找其他的接受者,那么父类的操作也就不能进行,这可能会有潜在的危险。

不过,事情也不是绝对的。在一个情形下,我们必须使用accept()和ignore()函数,那就是在窗口关闭的时候。

如果你在窗口关闭时需要有个询问对话框,那么就需要这么去写:

 1 void MainWindow::closeEvent(QCloseEvent * event)2 {3     if (continueToClose())4     {5         event->accept();6     }7     else8     {9         event->ignore();
10     }
11 }

non-GUI的Qt程序,是由QCoreApplication负责将QEvent分发给QObject的子类-Receiver. Qt GUI程序,由QApplication来负责。

【8】事件和信号的区别

Qt的事件很容易和信号槽混淆。signal由具体对象发出,然后会马上交给由connect函数连接的slot进行处理;

而对于事件,Qt使用一个事件队列对所有发出的事件进行维护,当新的事件产生时,会被追加到事件队列的尾部,前一个事件完成后,取出后面的事件接着再进行处理。

但是,必要的时候,Qt的事件也是可以不进入事件队列,而是直接处理的。并且,事件还可以使用“事件过滤器”进行过滤。

比如一个按钮对象, 我们使用这个按钮对象的时候, 我们只关心它被按下的信号, 至于这个按钮如何接收处理鼠标事件,再发射这个信号,我们是不用关心的。

但是如果我们要重载一个按钮的时候,我们就要面对event了。 比如我们可以改变它的行为,在鼠标按键按下的时候(mouse press event) 就触发clicked()的signal而不是通常在释放的( mouse release event)时候。

总结的说,Qt的事件和Qt中的signal不一样。 后者通常用来使用widget, 而前者用来实现widget。 如果我们使用系统预定义的控件,那我们关心的是信号,如果自定义控件我们关心的是事件。

【9】自定义事件

为什么需要自定义事件?

事件既可用于同步也可用于异步(依赖于你是调用sendEvent()或是postEvents()),函数调用或是槽调用总是同步的。事件的另外一个好处是它可以被过滤。

阻塞型事件:事件发送后需要等待处理完成

[static] bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)

事件生命周期由应用程序自身管理,同时支持栈事件对象和堆事件对象的发送。

非阻塞型发送:事件发送后立刻返回,事件被发送到事件队列等待处理

[static] void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority)

只能发送堆事件对象,事件被处理后由Qt平台销毁

当我们在main()函数的末尾调用QApplication::exec()时,程序进入了Qt的事件循环,大概来讲,事件循环如下面所示:

 1 while (!exit_was_called)2 {3     while (!posted_event_queue_is_empty)4     {5         process_next_posted_event();6     }7     while (!spontaneous_event_queue_is_empty)8     {9         process_next_spontaneous_event();
10     }
11     while (!posted_event_queue_is_empty)
12     {
13         process_next_posted_event();
14     }
15 }

Qt事件循环的过程

首先,事件循环处理所有的posted事件,直到队列空。然后再处理所有spontaneous事件,最后它处理所有的因为处理spontaneous事件而产生的posted事件。

send 事件并不在事件循环内处理,它们都直接被发送到了目标对象。

当一个widget第一次可见,或被遮挡后再次变为可见,窗口系统产生一个(spontaneous) paint事件,要求程序重绘widget。

事件循环最终会从事件队列中捡选这个事件并把它分发到那个需要重画的widget。

并不是所有的paint事件都是由窗口系统产生的。当你调用QWidget::update()去强行重画widget,这个widget会post 一个paint事件给自己。这个paint事件被放入队列,最终被事件循环分发之。

如果等不及事件循环去重画一个widget, 理论上,应该直接调用paintEvent()强制进行立即的重画。但实际上这不总是可行的,因为paintEvent()函数是protected的(很可能访问不了)。

它也绕开了任何存在的事件过滤器。因为这些原因,Qt提供了一个机制,直接sending事件而不是posting。 QWidget::repaint()就使用了这个机制来强制进行立即重画。

posting 相对于sending的一个优势是,它给了Qt一个压缩(compress)事件的机会。

假如你在一个widget上连续地调用update() 十次,因update()而产生的这十个事件,将会自动地被合并为一个单独的事件,但是QPaintEvents事件附带的区域信息也合并了。

可压缩的事件类型包括:paint、move、resize、layout hint、language change。

最后要注意,你可以在任何时候调用QApplication::sendPostedEvent(),强制Qt产生一个对象的posted事件。

【10】事件转发

对于某些类别的事件, 如果在整个事件的派发过程结束后还没有被处理, 那么这个事件将会向上转发给它的父widget,直到最顶层窗口。

比如:事件可能最先发送给QCheckBox, 如果QCheckBox没有处理, 那么由QGroupBox接着处理;

如果QGroupBox仍然没有处理, 再送到QDialog, 因为QDialog已经是最顶层widget, 所以如果QDialog再不处理, QEvent将停止转发。

如何判断一个事件是否被处理了呢? Qt中和事件相关的函数通过两种方式相互通信。

QApplication::notify(), QObject::eventFilter(), QObject::event() 通过返回bool值来表示是否已处理。“真”表示已经处理, “假”表示事件需要继续传递。

另一种是调用QEvent::ignore() 或 QEvent::accept() 对事件进行标识。这种方式只用于event() 函数和特定事件处理函数之间的沟通。

而且只有用在某些类别事件上是有意义的, 这些事件就是上面提到的那些会被转发的事件, 包括: 鼠标、滚轮、按键等事件。

【11】事件的传播(propogation)

如果事件在目标对象上得不到处理,事件向上一层进行传播,直到最顶层的widget为止。

如果得到事件的对象,调用了accept(),则事件停止继续传播;如果调用了ignore(),事件向上一级继续传播。

Qt对自定义事件处理函数的默认返回值是accept(),但默认的事件处理函数是ingore()。

因此,如果要继续向上传播,调用QWidget的默认处理函数即可。到此为止的话,不必显式调用accept()。

但在event处理函数里,返回true表示accept,返回false表示向上级传播。

但closeEvent是个特殊情形,accept表示quit,ignore表示取消,所以最好在closeEvent显式调用accept和ignore。

【12】事件产生

事件产生详细过程:

  1 // section 1-12 int main(int argc, char *argv[])3 {4     QApplication a(argc, argv);5     MainWindow w;6     w.show();7 8     return a.exec();9 }10 11 // section 1-212 // 源码路径:($QTDIR\Src\qtbase\src\widgets\kernel\qapplication.cpp)13 int QApplication::exec()14 {15     return QGuiApplication::exec();16 }17 18 // section 1-319 // 源码路径:($QTDIR\Src\qtbase\src\gui\kernel\qguiapplication.cpp)20 int QGuiApplication::exec()21 {22 #ifndef QT_NO_ACCESSIBILITY23     QAccessible::setRootObject(qApp);24 #endif25     return QCoreApplication::exec();26 }27 28 // section 1-429 // 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp)30 int QCoreApplication::exec()31 {32     if (!QCoreApplicationPrivate::checkInstance("exec"))33         return -1;34 35     QThreadData *threadData = self->d_func()->threadData;36     if (threadData != QThreadData::current())37     {38         qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());39         return -1;40     }41     if (!threadData->eventLoops.isEmpty())42     {43         qWarning("QCoreApplication::exec: The event loop is already running");44         return -1;45     }46 47     threadData->quitNow = false;48     QEventLoop eventLoop;49     self->d_func()->in_exec = true;50     self->d_func()->aboutToQuitEmitted = false;51     int returnCode = eventLoop.exec();52     threadData->quitNow = false;53     if (self)54     {55         self->d_func()->in_exec = false;56         if (!self->d_func()->aboutToQuitEmitted)57         {58             emit self->aboutToQuit(QPrivateSignal());59         }60         self->d_func()->aboutToQuitEmitted = true;61         sendPostedEvents(0, QEvent::DeferredDelete);62     }63 64     return returnCode;65 }66 67 // section 1-568 // 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qeventloop.cpp)69 // 声明:int exec(ProcessEventsFlags flags = AllEvents);70 int QEventLoop::exec(ProcessEventsFlags flags)71 {72     Q_D(QEventLoop);73     // we need to protect from race condition with QThread::exit74     QMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(d->threadData->thread))->mutex);75     if (d->threadData->quitNow)76     {77         return -1;78     }79 80     if (d->inExec)81     {82         qWarning("QEventLoop::exec: instance %p has already called exec()", this);83         return -1;84     }85 86     struct LoopReference87     {88         QEventLoopPrivate *d;89         QMutexLocker &locker;90 91         bool exceptionCaught;92         LoopReference(QEventLoopPrivate *d, QMutexLocker &locker) : d(d), locker(locker), exceptionCaught(true)93         {94             d->inExec = true;95             d->exit.storeRelease(false);96             ++d->threadData->loopLevel;97             d->threadData->eventLoops.push(d->q_func());98             locker.unlock();99         }
100 
101         ~LoopReference()
102         {
103             if (exceptionCaught)
104             {
105                 qWarning("Qt has caught an exception thrown from an event handler. Throwing\n"
106                          "exceptions from an event handler is not supported in Qt. You must\n"
107                          "reimplement QApplication::notify() and catch all exceptions there.\n");
108             }
109             locker.relock();
110             QEventLoop *eventLoop = d->threadData->eventLoops.pop();
111             Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error");
112             Q_UNUSED(eventLoop); // --release warning
113             d->inExec = false;
114             --d->threadData->loopLevel;
115         }
116     };
117     LoopReference ref(d, locker);
118 
119     // remove posted quit events when entering a new event loop
120     QCoreApplication *app = QCoreApplication::instance();
121     if (app && app->thread() == thread())
122     {
123         QCoreApplication::removePostedEvents(app, QEvent::Quit);
124     }
125 
126     while (!d->exit.loadAcquire())
127     {
128         processEvents(flags | WaitForMoreEvents | EventLoopExec);
129     }
130 
131     ref.exceptionCaught = false;
132     return d->returnCode.load();
133 }
134 
135 // section 1-6
136 // 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qeventloop.cpp)
137 bool QEventLoop::processEvents(ProcessEventsFlags flags)
138 {
139     Q_D(QEventLoop);
140     if (!d->threadData->eventDispatcher.load())
141     {
142         return false;
143     }
144     return d->threadData->eventDispatcher.load()->processEvents(flags);
145 }
146 
147 // section 1-7
148 // 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qeventdispatcher_win.cpp)
149 // 这段代码是完成与windows平台相关的windows c++。
150 // 以跨平台著称的Qt同时也提供了对Symiban、Unix等平台的消息派发支持,
151 // 分别封装在QEventDispatcherSymbian和QEventDIspatcherUNIX。
152 // QEventDispatcherWin32继承QAbstractEventDispatcher。
153 bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
154 {
155     Q_D(QEventDispatcherWin32);
156 
157     if (!d->internalHwnd)
158     {
159         createInternalHwnd();
160         wakeUp(); // trigger a call to sendPostedEvents()
161     }
162 
163     d->interrupt = false;
164     emit awake();
165 
166     bool canWait;
167     bool retVal = false;
168     bool seenWM_QT_SENDPOSTEDEVENTS = false;
169     bool needWM_QT_SENDPOSTEDEVENTS = false;
170     do
171     {
172         DWORD waitRet = 0;
173         HANDLE pHandles[MAXIMUM_WAIT_OBJECTS - 1];
174         QVarLengthArray<MSG> processedTimers;
175         while (!d->interrupt)
176         {
177             DWORD nCount = d->winEventNotifierList.count();
178             Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1);
179 
180             MSG msg;
181             bool haveMessage;
182 
183             if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty())
184             {
185                 // process queued user input events
186                 haveMessage = true;
187                 msg = d->queuedUserInputEvents.takeFirst(); // 逐个处理用户输入队列中的事件
188             }
189             else if (!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty())
190             {
191                 // process queued socket events
192                 haveMessage = true;
193                 msg = d->queuedSocketEvents.takeFirst(); // 逐个处理socket队列中的事件
194             }
195             else
196             {
197                 haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
198                 if (haveMessage && (flags & QEventLoop::ExcludeUserInputEvents)
199                     && ((msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST)
200                         || (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST)
201                         || msg.message == WM_MOUSEWHEEL
202                         || msg.message == WM_MOUSEHWHEEL
203                         || msg.message == WM_TOUCH
204 #ifndef QT_NO_GESTURES
205                         || msg.message == WM_GESTURE
206                         || msg.message == WM_GESTURENOTIFY
207 #endif
208                         || msg.message == WM_CLOSE))
209                 {
210                     // queue user input events for later processing
211                     haveMessage = false;
212                     d->queuedUserInputEvents.append(msg); // 用户输入事件入队列,待以后处理
213                 }
214                 if (haveMessage && (flags & QEventLoop::ExcludeSocketNotifiers)
215                     && (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd))
216                 {
217                     // queue socket events for later processing
218                     haveMessage = false;
219                     d->queuedSocketEvents.append(msg); // socket 事件入队列,待以后处理
220                 }
221             }
222             if (!haveMessage)
223             {
224                 // no message - check for signalled objects
225                 for (int i = 0; i < (int)nCount; i++)
226                 {
227                     pHandles[i] = d->winEventNotifierList.at(i)->handle();
228                 }
229                 waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, 0, QS_ALLINPUT, MWMO_ALERTABLE);
230                 if ((haveMessage = (waitRet == WAIT_OBJECT_0 + nCount)))
231                 {
232                     // a new message has arrived, process it
233                     continue;
234                 }
235             }
236             if (haveMessage)
237             {
238                 // WinCE doesn't support hooks at all, so we have to call this by hand :(
239                 if (!d->getMessageHook)
240                 {
241                     (void) qt_GetMessageHook(0, PM_REMOVE, (LPARAM) &msg);
242                 }
243 
244                 if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS)
245                 {
246                     if (seenWM_QT_SENDPOSTEDEVENTS)
247                     {
248                         // when calling processEvents() "manually", we only want to send posted
249                         // events once
250                         needWM_QT_SENDPOSTEDEVENTS = true;
251                         continue;
252                     }
253                     seenWM_QT_SENDPOSTEDEVENTS = true;
254                 }
255                 else if (msg.message == WM_TIMER)
256                 {
257                     // avoid live-lock by keeping track of the timers we've already sent
258                     bool found = false;
259                     for (int i = 0; !found && i < processedTimers.count(); ++i)
260                     {
261                         const MSG processed = processedTimers.constData()[i];
262                         found = (processed.wParam == msg.wParam && processed.hwnd == msg.hwnd && processed.lParam == msg.lParam);
263                     }
264                     if (found)
265                     {
266                         continue;
267                     }
268                     processedTimers.append(msg);
269                 }
270                 else if (msg.message == WM_QUIT)
271                 {
272                     if (QCoreApplication::instance())
273                     {
274                         QCoreApplication::instance()->quit();
275                     }
276                     return false;
277                 }
278 
279                 if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, 0))
280                 {
281                     // 将事件打包成message调用Windows API派发出去
282                     TranslateMessage(&msg);
283                     // 分发一个消息给窗口程序。消息被分发到回调函数,将消息传递给windows系统,windows处理完毕,会调用回调函数。
284                     DispatchMessage(&msg);
285                 }
286             }
287             else if (waitRet - WAIT_OBJECT_0 < nCount)
288             {
289                 d->activateEventNotifier(d->winEventNotifierList.at(waitRet - WAIT_OBJECT_0));
290             }
291             else
292             {
293                 // nothing todo so break
294                 break;
295             }
296             retVal = true;
297         }
298 
299         // still nothing - wait for message or signalled objects
300         canWait = (!retVal
301                    && !d->interrupt
302                    && (flags & QEventLoop::WaitForMoreEvents));
303         if (canWait)
304         {
305             DWORD nCount = d->winEventNotifierList.count();
306             Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1);
307             for (int i = 0; i < (int)nCount; i++)
308             {
309                 pHandles[i] = d->winEventNotifierList.at(i)->handle();
310             }
311 
312             emit aboutToBlock();
313             waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);
314             emit awake();
315             if (waitRet - WAIT_OBJECT_0 < nCount)
316             {
317                 d->activateEventNotifier(d->winEventNotifierList.at(waitRet - WAIT_OBJECT_0));
318                 retVal = true;
319             }
320         }
321     } while (canWait);
322 
323     if (!seenWM_QT_SENDPOSTEDEVENTS && (flags & QEventLoop::EventLoopExec) == 0)
324     {
325         // when called "manually", always send posted events
326         sendPostedEvents();
327     }
328 
329     if (needWM_QT_SENDPOSTEDEVENTS)
330     {
331         PostMessage(d->internalHwnd, WM_QT_SENDPOSTEDEVENTS, 0, 0);
332     }
333 
334     return retVal;
335 }
336 
337 // section1~7的过程:Qt进入QApplication的event loop,经过层层委任,
338 // 最终QEventLoop的processEvent将通过与平台相关的AbstractEventDispatcher的子类QEventDispatcherWin32
339 // 获得用户的输入事件,并将其打包成message后,通过标准的Windows API传递给Windows OS。
340 // Windows OS得到通知后回调QtWndProc,至此事件的分发与处理完成了一半的路程。
341 // 事件的产生、分发、接受和处理,并以视窗系统鼠标点击QWidget为例,对代码进行了剖析,向大家分析了Qt框架如何通过Event
342 // Loop处理进入处理消息队列循环,如何一步一步委派给平台相关的函数获取、打包用户输入事件交给视窗系统处理,函数调用栈如下:
343 // 1.main(int, char **)
344 // 2.QApplication::exec()
345 // 3.QCoreApplication::exec()
346 // 4.QEventLoop::exec(ProcessEventsFlags )
347 // 5.QEventLoop::processEvents(ProcessEventsFlags )
348 // 6.QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags)

【13】事件分发

事件分发详细过程:

  1 // 1.QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) bool QETWidget::translateMouseEvent(const MSG &msg)2 // 2.bool QApplicationPrivate::sendMouseEvent(...)3 // 3.inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)4 // 4.bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event)5 // 5.bool QApplication::notify(QObject *receiver, QEvent *e)6 // 6.bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)7 // 7.bool QWidget::event(QEvent *event)8 // 下面介绍Qt app在视窗系统回调后,事件又是怎么一步步通过QApplication分发给最终事件的接受和处理者QWidget::event,9 // QWidget继承自Object,重载其虚函数event。10 // section 2-111 // windows窗口回调函数12 QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)13 {14     // ...15     // 检查message是否属于Qt可转义的鼠标事件16     if (qt_is_translatable_mouse_event(message))17     {18         if (QApplication::activePopupWidget() != 0)19         { // in popup mode20             POINT curPos = msg.pt;21             // 取得鼠标点击坐标所在的QWidget指针,它指向我们在main创建的widget实例22             QWidget* w = QApplication::widgetAt(curPos.x, curPos.y);23             if (w)24             {25                 widget = (QETWidget*)w;26             }27         }28 29         if (!qt_tabletChokeMouse)30         {31             // 对,就在这里。Windows的回调函数将鼠标事件分发回给了Qt Widget32             // => Section 2-233             result = widget->translateMouseEvent(msg);  // mouse event34         }35     }36 37     // ...38 }39 40 // section 2-2 ($QTDIR\src\gui\kernel\qapplication_win.cpp)41 // 该函数所在与Windows平台相关,主要职责就是把已用windows格式打包的鼠标事件解包、翻译成QApplication可识别的QMouseEvent。42 bool QETWidget::translateMouseEvent(const MSG &msg)43 {44     // ...这里有很长的一段代码可以忽略45     // 让我们看一下sendMouseEvent的声明46     // widget是事件的接受者;e是封装好的QMouseEvent47     // ==> Section 2-348     res = QApplicationPrivate::sendMouseEvent(target,49                                               &e, alienWidget, this, &qt_button_down,50                                               qt_last_mouse_receiver);51 }52 53 // section 2-354 // 源码路径:($QTDIR\Src\qtbase\src\widgets\kernel\qapplication.cpp)55 bool QApplicationPrivate::sendMouseEvent(QWidget *receiver, QMouseEvent *event,56                                          QWidget *alienWidget, QWidget *nativeWidget,57                                          QWidget **buttonDown, QPointer<QWidget> &lastMouseReceiver,58                                          bool spontaneous)59 {60     // ...61     // 至此与平台相关代码处理完毕62     // MouseEvent默认的发送方式是spontaneous, 所以将执行sendSpontaneousEvent。63     // sendSpontaneousEvent() 与 sendEvent的代码实现几乎相同64     // 除了将QEvent的属性spontaneous标记不同。 这里是解释什么是spontaneous事件:事件由应用程序之外产生的,比如一个系统事件。65     // 显然MousePress事件是由视窗系统产生的一个的事件(详见上文Section 1~ Section 7),因此它是spontaneous事件66     if (spontaneous)67     {68         result = QApplication::sendSpontaneousEvent(receiver, event);69     }70     else71     {72         result = QApplication::sendEvent(receiver, event);//TODO73     }74 75     ...76 77     return result;78 }79 80 // section 2-481 // 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp)82 inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)83 {84     // 将event标记为自发事件85     // 进一步调用 2-5 QCoreApplication::notifyInternal86     if (event)87     {88         event->spont = true;89     }90     return self ? self->notifyInternal(receiver, event) : false;91 }92 93 // section 2-5:94 // 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp)95 bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event)96 {97     // 几行代码对于Qt Jambi (QT Java绑定版本) 和QSA (QT Script for Application)的支持98     // ...99     // 以下代码主要意图为Qt强制事件只能够发送给当前线程里的对象,
100     // 也就是说receiver->d_func()->threadData应该等于QThreadData::current()。
101     // 注意,跨线程的事件需要借助Event Loop来派发
102     QObjectPrivate *d = receiver->d_func();
103     QThreadData *threadData = d->threadData;
104     ++threadData->loopLevel;
105 
106     // 哇,终于来到大名鼎鼎的函数QCoreApplication::nofity()了 ==> Section 2-6
107     QT_TRY
108     {
109         returnValue = notify(receiver, event);
110     }
111     QT_CATCH (...)
112     {
113         --threadData->loopLevel;
114         QT_RETHROW;
115     }
116 
117     ...
118 
119     return returnValue;
120 }
121 
122 // section 2-6:
123 // 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp)
124 // QCoreApplication::notify和它的重载函数QApplication::notify在Qt的派发过程中起到核心的作用,Qt的官方文档时这样说的:
125 // 任何线程的任何对象的所有事件在发送时都会调用notify函数。
126 bool QCoreApplication::notify(QObject *receiver, QEvent *event)
127 {
128     Q_D(QCoreApplication);
129     // no events are delivered after ~QCoreApplication() has started
130     if (QCoreApplicationPrivate::is_app_closing)
131     {
132         return true;
133     }
134 
135     if (receiver == 0)
136     {                        // serious error
137         qWarning("QCoreApplication::notify: Unexpected null receiver");
138         return true;
139     }
140 
141 #ifndef QT_NO_DEBUG
142     d->checkReceiverThread(receiver);
143 #endif
144 
145     return receiver->isWidgetType() ? false : d->notify_helper(receiver, event);
146 }
147 
148 // section 2-7:
149 // 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp)
150 // notify 调用 notify_helper()
151 bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
152 {
153     // send to all application event filters
154     if (sendThroughApplicationEventFilters(receiver, event))
155     {
156         return true;
157     }
158     // 向事件过滤器发送该事件,这里介绍一下Event Filters. 事件过滤器是一个接受即将发送给目标对象所有事件的对象。
159     //如代码所示它开始处理事件在目标对象行动之前。过滤器的QObject::eventFilter()实现被调用,能接受或者丢弃过滤
160     //允许或者拒绝事件的更进一步的处理。如果所有的事件过滤器允许更进一步的事件处理,事件将被发送到目标对象本身。
161     //如果他们中的一个停止处理,目标和任何后来的事件过滤器不能看到任何事件。
162     if (sendThroughObjectEventFilters(receiver, event))
163     {
164         return true;
165     }
166     // deliver the event
167     // 递交事件给receiver  => Section 2-8
168     return receiver->event(event);
169 }
170 
171 // section 2-8
172 // 源码路径:($QTDIR\Src\qtbase\src\widgets\kernel\qwidget.cpp)
173 // QApplication通过notify及其私有类notify_helper,将事件最终派发给了QObject的子类- QWidget.
174 bool QWidget::event(QEvent *event)
175 {
176     // ...
177     switch (event->type())
178     {
179     case QEvent::MouseMove:
180         mouseMoveEvent((QMouseEvent*)event);
181         break;
182 
183     case QEvent::MouseButtonPress:
184         // Don't reset input context here. Whether reset or not is
185         // a responsibility of input method. reset() will be
186         // called by mouseHandler() of input method if necessary
187         // via mousePressEvent() of text widgets.
188 #if 0
189         resetInputContext();
190 #endif
191         mousePressEvent((QMouseEvent*)event);
192         break;
193     }
194     // ...
195 }

【14】Qt5.3.2版本事件机制源码调试

事件产生于分发调试堆栈图如下:

【15】总结

到此为止。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_75505.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LibAFL的安装及基本使用

本教程安装LibAFL使用的是Ubuntu 22.04 操作系统 1. 安装 1.1 Rust 安装 Rust的安装&#xff0c;参照Rust官网&#xff1a;https://www.rust-lang.org/tools/install curl --proto https --tlsv1.2 -sSf https://sh.rustup.rs | sh1.2 LLVM安装 直接apt安装&#xff0c;安…

表格形式的Sarsa与Q_learning算法

环境如下&#xff1a; 这是一个简单的环境&#xff0c;绿色方块代表终点&#xff0c;白色方块代表可行点&#xff0c;灰色方块代表陷阱 用Sarsa算法和Q_learning算法训练得到value表格 代码如下&#xff1a; (jupyter notebook上的代码&#xff0c;所以顺序看起来有点儿奇怪) …

Java8 新特性强大的Stream API

一、Stream API 说明 Java8中有两大最为重要的改变。第一个是 Lambda 表达式&#xff1b;另外一个则是 Stream API。 Stream API ( java.util.stream) 把真正的函数式编程风格引入到Java中。这是目前为止对Java类库最好的补充&#xff0c;因为Stream API可以极大提供Ja…

【博学谷学习记录】超强总结,用心分享丨人工智能 特征工程 特征变换 分箱学习总结

目录概念分箱的作用等频分箱等距分箱*卡方分箱公式例子概念 特征构造的过程中&#xff0c;对特征做分箱处理时必不可少的过程分箱就是将连续变量离散化&#xff0c;合并成较少的状态 分箱的作用 离散特征的增加和减少都很容易&#xff0c;易于模型的快速迭代&#xff1b;稀疏…

基于自定义训练函数的BP神经网络回归分析

目录 背影 BP神经网络的原理 BP神经网络的定义 BP神经网络的神经元 BP神经网络的激活函数 BP神经网络的传递函数 基于自定义训练函数的BP神经网络回归分析 背影 BP神经网络是一种成熟的神经网络&#xff0c;拥有很多训练函数&#xff0c;传递函数&#xff0c;激活函数&#x…

数学小课堂:无穷小(用动态和极限的眼光看世界)

文章目录 引言I 极限1.1 柯西对极限的认知1.2 极限准确的定义1.3 数列极限的定义1.4 函数极限的定义1.5 无穷小(特殊的极限)1.6 定量和逆向思维1.7 认知升级的过程引言 身处于渐变世界的人类,难以理解瞬间突变。 老师的作用,就是用大白话,把数学语言所写的知识,翻译成大…

腾讯安全与锐捷网络战略合作,威胁情报能力“被集成”

2月28日&#xff0c;腾讯安全和锐捷网络在北京联合举办“威胁情报”战略合作发布会。双方发布了一款集成了腾讯安全威胁情报的新一代防火墙&#xff0c;并举办战略合作签约仪式。会上&#xff0c;锐捷网络安全产品事业部总经理项小升、腾讯安全总经理陈龙代表双方签署战略合作协…

Python可视化界面编程入门

Python可视化界面编程入门具体实现代码如所示&#xff1a; &#xff08;1&#xff09;普通可视化界面编程代码入门&#xff1a; import sys from PyQt5.QtWidgets import QWidget,QApplication #导入两个类来进行程序界面编程if __name__"__main__":#创建一个Appl…

k8s学习之路 | Day17 k8s 工作负载

文章目录工作负载的定义工作负载资源分类工作负载的定义 官方参考链接&#xff1a;https://kubernetes.io/docs/concepts/workloads/ A workload is an application running on Kubernetes. Whether your workload is a single component or several that work together, on K…

《C++ Primer Plus》(第6版)第6章编程练习

《C Primer Plus》&#xff08;第6版&#xff09;第6章编程练习《C Primer Plus》&#xff08;第6版&#xff09;第6章编程练习1. 大小写转换2. 平均值3. 菜单4. 成员5. 收入所得税6. 捐款7. 统计单词8. 统计文件字符数9. 重写编程练习6《C Primer Plus》&#xff08;第6版&…

创建对象的方式和对属性的操作

javaScript支持多种编程范式&#xff0c;包括函数式编程和面向对象编程&#xff0c;javaScript的对象被设计成一组属性的无序集合&#xff0c;由key和value组成。 创建对象的两种方式 早期使用创建对象方式最多的是使用Object类&#xff0c;使用new关键字来创建一个对象&…

docker-compose安装kafka和php简单测试

docker-compose.yml内容&#xff1a; version: 3.1 services: zookeeper: container_name: zookeeper image: zookeeper:3.6 ports: - 2181:2181 kafka: image: wurstmeister/kafka container_name: kafka depends_on: - zookeeper …

解决Spring Data Jpa 实体类自动创建数据库表失败问题

先说一下我遇到的这个问题&#xff0c;首先我是通过maven创建了一个spring boot的工程&#xff0c;引入了Spring data jpa&#xff0c;结果实体类创建好之后&#xff0c;运行工程却没有在数据库中自动创建数据表。 找了半天发现是一个配置的问题! hibernate.ddl-auto节点的配…

【Python实战】激情澎湃,2023极品劲爆舞曲震撼全场,爬虫一键采集DJ大串烧,一曲醉人女声DJ舞曲,人人都听醉~(排行榜采集,妙啊~)

导语 哈喽&#xff01;大家好。我是木木子吖~今天给大家带来爬虫的内容哈。 所有文章完整的素材源码都在&#x1f447;&#x1f447; 粉丝白嫖源码福利&#xff0c;请移步至CSDN社区或文末公众hao即可免费。 今天教大家Python爬虫实战一键采集大家喜欢的DJ舞曲哦&#xff01; …

01-Oracle入门基础知识讲解

本章内容主要是讲解Oracle基础知识&#xff0c;安装完Oracle后第一次使用所必须了解的一些常用软件及命令&#xff0c;Oracle的体系结构等知识。 一、进入SQL Plus客户端软件 1.进入SQLPLUS客户端windows界面 2.进入DOS窗口界面 普通用户登录&#xff1a;conn 用户名称/密码 …

taobao.user.avatar.get

&#xffe5;开放平台基础API不需用户授权 根据混淆nick查询用户头像 公共参数 请求地址: HTTP地址 http://gw.api.taobao.com/router/rest 公共请求参数: 公共响应参数: 点击获取key和secret 请求参数 请求示例 TaobaoClient client new DefaultTaobaoClient(url, appkey,…

实现RecyclerView二级列表

自定义RecyclerView的adapter实现二级列表 图片大于5MB&#xff0c;CSDN不让上传&#xff0c;使用github链接&#xff0c;如果看不到请使用科学上网 https://github.com/nanjolnoSat/PersonalProject/blob/recyclerexpandableadapter/Recyclerexpanableadapter/pic/pic1.gif 源…

Kotlin学习:5.2、异步数据流 Flow

Flow一、Flow1、Flow是什么东西&#xff1f;2、实现功能3、特点4、冷流和热流5、流的连续性6、流的构建器7、流的上下文8、指定流所在协程9、流的取消9.1、超时取消9.2、主动取消9.3、密集型任务的取消10、背压和优化10.1、buffer 操作符10.2、 flowOn10.3、conflate 操作符10.…

同为(TOWE)防雷产品助力福建移动南平分公司防雷改造

01 公司简介中国移动通信集团福建有限公司南平分公司属于福建移动地级分公司&#xff0c;所属行业为电信、广播电视和卫星传输服务。现已建成覆盖范围广、业务品种多、通信质量高的综合通信网络&#xff0c;具备行业领先的经营管理制度。移动通信大楼的综合防雷及地接系统&…

Fedora系统安装KubeVela

话不多说直接看命令 Docker安装 Vela安装需要先安装Docker sudo yum -y install docker只需这行命令便可以自动添加 yum和dnf理论上都能成功&#xff0c;但是很看网速&#xff0c;&#xff0c;&#xff0c;实践证明yum是最好的。 如果发生报错mirrors trieds大概率就是网速超…