当前位置: 首页 > news >正文

Qt 事件机制

【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】总结

到此为止。

相关文章:

Qt 事件机制

【1】事件 事件是可以被控件识别的操作。如按下确定按钮、选择某个单选按钮或复选框。 每种控件有自己可识别的事件&#xff0c;如窗体的加载、单击、双击等事件&#xff0c;编辑框&#xff08;文本框&#xff09;的文本改变事件等等。 事件就是用户对窗口上各种组件的操作。…...

【Python】Numpy--np.linalg.eig()求对称矩阵的特征值和特征向量

【Python】Numpy–np.linalg.eig()求对称矩阵的特征值和特征向量 文章目录【Python】Numpy--np.linalg.eig()求对称矩阵的特征值和特征向量1. 介绍2. API3. 代码示例1. 介绍 特征分解&#xff08;Eigendecomposition&#xff09;&#xff0c;又称谱分解&#xff08;Spectral d…...

医疗床头卡(WIFI方案)

一、产品特性 7.5寸墨水屏显示WIFI无线通信&#xff0c;极简部署&#xff0c;远程控制按键及高亮LED指示灯指示800*480点阵屏幕锂电池供电&#xff0c;支持USB充电DIY界面支持文本/条码/二维码/图片超低功耗/超长寿命&#xff0c;一次充电可用一年基于现有Wifi环境&#xff0c…...

[YOLO] yolo博客笔记汇总(自用

pip下载速度太慢&#xff0c;国内镜像&#xff1a; 国内镜像解决pip下载太慢https://blog.csdn.net/weixin_51995286/article/details/113972534​​​​​​​ YOLO v2和V3 关于设置生成anchorbox&#xff0c;Boundingbox边框回归的过程详细解读 YOLO v2和V3 关于设置生成an…...

Linux 常用 API 函数

文章目录1. 系统调用与库函数1.1 什么是系统调用1.2 系统调用的实现1.3 系统调用和库函数的区别2. 虚拟内存空间3. 错误处理函数4. C 库中 IO 函数工作流程5. 文件描述符6. 常用文件 IO 函数6.1 open 函数6.2 close 函数6.3 write 函数6.4 read 函数6.5 lseek 函数7. 文件操作相…...

【转载】bootstrap自定义样式-bootstrap侧边导航栏的实现

bootstrap自带的响应式导航栏是向下滑动的&#xff0c;但是有时满足不了个性化的需求: 侧滑栏使用定位fixed 使用bootstrap响应式使用工具类 visible-sm visible-xs hidden-xs hidden-sm等对不同屏幕适配 侧滑栏的侧滑效果不使用jquery方法来实现&#xff0c;使用的是css3 tr…...

奇瑞x华为纯电智选车来了,新版ADS成本将大幅下降

作者 | 德新 编辑 | 于婷HiEV获悉&#xff0c;问界M5将在4月迎来搭载高阶辅助驾驶的新款&#xff0c;而M9将在今年秋天发布。 奇瑞一侧&#xff0c;华为将与奇瑞首先推出纯电轿车&#xff0c;代号EH3。新车将在奇瑞位于芜湖江北新区的智能网联超级二工厂组装下线。目前超级二工…...

机器学习的特征归一化Normalization

为什么需要做归一化&#xff1f; 为了消除数据特征之间的量纲影响&#xff0c;就需要对特征进行归一化处理&#xff0c;使得不同指标之间具有可比性。对特征归一化可以将所有特征都统一到一个大致相同的数值区间内。 为了后⾯数据处理的⽅便&#xff0c;归⼀化可以避免⼀些不…...

程序员看过都说好的资源网站,看看你都用过哪些?

程序员必备的相关资源网站一.图片专区1.表情包&#xff08;1&#xff09;发表情&#xff08;2&#xff09;逗比拯救世界&#xff08;3&#xff09;搞怪图片生成&#xff08;4&#xff09;哇咔工具2.图标库&#xff08;1&#xff09;Font Awesome&#xff08;2&#xff09;iconf…...

Win11的两个实用技巧系列之设置系统还原点的方法、安全启动状态开启方法

Win11如何设置系统还原点?Win11设置系统还原点的方法很多用户下载安装win11后应该如何创建还原点呢&#xff1f;现在我通过这篇文章给大家介绍一下Win11如何设置系统还原点&#xff1f;在Windows系统中有一个系统还原功能可以帮助我们在电脑出现问题的时候还原到设置的时间上&…...

【Linux】项目的自动化构建-make/makefile

&#x1f4a3;1.背景会不会写makefile&#xff0c;从一个侧面说明了一个人是否具备完成大型工程的能力 一个工程中的源文件不计数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;makefile定义了一系列的 规则来指定&#xff0c;哪些文件需要先编译&#xff…...

【Redis学习2】Redis常用数据结构与应用场景

Redis常用数据结构与应用场景 redis中存储数据是以key-value键值对的方式去存储的&#xff0c;其中key为string字符类型&#xff0c;value的数据类型可以是string(字符串)、list(列表)、hash(字典)、set(集合) 、 zset(有序集合)。 这5种数据类型在开发中可以应对大部分场景的…...

踩了大坑:https 证书访问错乱

文章目录一、问题排查及解决问题一&#xff1a;证书加载错乱问题二&#xff1a;DNS 解析污染问题问题三&#xff1a;浏览器校验问题二、终极解决方法2.1 可外网访问域名2.2 只能内网访问域名2.3 内网自动化配置2.4 错误解决一、问题排查及解决 今天遇到这样一个问题&#xff0…...

大数据技术之Hive(四)分区表和分桶表、文件格式和压缩

一、分区表和分桶表1.1 分区表partitionhive中的分区就是把一张大表的数据按照业务需要分散的存储到多个目录&#xff0c;每个目录就称为该表的一个分区。在查询时通过where子句中的表达式选择式选择查询所需要的分区&#xff0c;这样的查询效率辉提高很多。1.1.1 分区表基本语…...

环形缓冲区(c语言)

1、概念介绍 在我们需要处理大量数据的时候&#xff0c;不能存储所有的数据&#xff0c;只能先处理先来的&#xff0c;然后将这个数据释放&#xff0c;再去处理下一个数据。 如果在一个线性的缓冲区中&#xff0c;那些已经被处理的数据的内存就会被浪费掉。因为后面的数据只能…...

创建自助服务知识库的指南

在SaaS领域&#xff0c;自助文档是你可以在客户登录你的网站时为他们提供的最灵活的帮助方式&#xff0c;简单来说&#xff0c;一个自助知识库是一个可以帮助许多客户的文档&#xff0c;拥有出色的自助服务知识库&#xff0c;放在官网或者醒目的地方&#xff0c;借助自助服务知…...

分层测试(1)分层测试是什么?【必备】

1. 什么是分层测试&#xff1f; 分层测试是通过对质量问题分类、分层来保证整体系统质量的测试体系。 模块内通过接口测试保证模块质量&#xff0c;多模块之间通过集成测试保证通信路径和模块间交互质量&#xff0c;整体系统通过端到端用例对核心业务场景进行验证&#xff0c…...

开源ZYNQ AD9361软件无线电平台

&#xff08;1&#xff09; XC7Z020-CLG400 &#xff08;2&#xff09; AD9363 &#xff08;3&#xff09; 单发单收&#xff0c;工作频率400MHz-2.7GHz &#xff08;4&#xff09; 发射带PA&#xff0c;最大输出功率约20dbm &#xff08;5&#xff09; 接收带LNA&#xff0c;低…...

第四阶段-12关于Spring Security框架,RBAC,密码加密原则

关于csmall-passport项目 此项目主要用于实现“管理员”账号的后台管理功能&#xff0c;主要实现&#xff1a; 管理员登录添加管理员删除管理员显示管理员列表启用 / 禁用管理员 关于RBAC RBAC&#xff1a;Role-Based Access Control&#xff0c;基于角色的访问控制 在涉及…...

JPA——Date拓展之Calendar

Java Calendar 是时间操作类,Calendar 抽象类定义了足够的方法&#xff0c;在某一特定的瞬间或日历上&#xff0c;提供年、月、日、小时之间的转换提供方法 一、获取具体时间信息 1. 当前时间 获取此刻时间的年月日时分秒 Calendar calendar Calendar.getInstance(); int …...

R语言AI模型部署方案:精准离线运行详解

R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地

借阿里云中企出海大会的东风&#xff0c;以**「云启出海&#xff0c;智联未来&#xff5c;打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办&#xff0c;现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...

基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容

基于 ​UniApp + WebSocket​实现多端兼容的实时通讯系统,涵盖WebSocket连接建立、消息收发机制、多端兼容性配置、消息实时监听等功能,适配​微信小程序、H5、Android、iOS等终端 目录 技术选型分析WebSocket协议优势UniApp跨平台特性WebSocket 基础实现连接管理消息收发连接…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)

服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

cf2117E

原题链接&#xff1a;https://codeforces.com/contest/2117/problem/E 题目背景&#xff1a; 给定两个数组a,b&#xff0c;可以执行多次以下操作&#xff1a;选择 i (1 < i < n - 1)&#xff0c;并设置 或&#xff0c;也可以在执行上述操作前执行一次删除任意 和 。求…...

TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案

一、TRS收益互换的本质与业务逻辑 &#xff08;一&#xff09;概念解析 TRS&#xff08;Total Return Swap&#xff09;收益互换是一种金融衍生工具&#xff0c;指交易双方约定在未来一定期限内&#xff0c;基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...

uniapp微信小程序视频实时流+pc端预览方案

方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度​WebSocket图片帧​定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐​RTMP推流​TRTC/即构SDK推流❌ 付费方案 &#xff08;部分有免费额度&#x…...

根据万维钢·精英日课6的内容,使用AI(2025)可以参考以下方法:

根据万维钢精英日课6的内容&#xff0c;使用AI&#xff08;2025&#xff09;可以参考以下方法&#xff1a; 四个洞见 模型已经比人聪明&#xff1a;以ChatGPT o3为代表的AI非常强大&#xff0c;能运用高级理论解释道理、引用最新学术论文&#xff0c;生成对顶尖科学家都有用的…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

rnn判断string中第一次出现a的下标

# coding:utf8 import torch import torch.nn as nn import numpy as np import random import json""" 基于pytorch的网络编写 实现一个RNN网络完成多分类任务 判断字符 a 第一次出现在字符串中的位置 """class TorchModel(nn.Module):def __in…...