Qt 事件机制 qt事件优先级
liebian365 2024-10-26 12:59 24 浏览 0 评论
【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 else
8 {
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-1
2 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-2
12 // 源码路径:($QTDIR\Src\qtbase\src\widgets\kernel\qapplication.cpp)
13 int QApplication::exec()
14 {
15 return QGuiApplication::exec();
16 }
17
18 // section 1-3
19 // 源码路径:($QTDIR\Src\qtbase\src\gui\kernel\qguiapplication.cpp)
20 int QGuiApplication::exec()
21 {
22 #ifndef QT_NO_ACCESSIBILITY
23 QAccessible::setRootObject(qApp);
24 #endif
25 return QCoreApplication::exec();
26 }
27
28 // section 1-4
29 // 源码路径:($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-5
68 // 源码路径:($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::exit
74 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 LoopReference
87 {
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-1
11 // 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 mode
20 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 Widget
32 // => Section 2-2
33 result = widget->translateMouseEvent(msg); // mouse event
34 }
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是封装好的QMouseEvent
47 // ==> Section 2-3
48 res = QApplicationPrivate::sendMouseEvent(target,
49 &e, alienWidget, this, &qt_button_down,
50 qt_last_mouse_receiver);
51 }
52
53 // section 2-3
54 // 源码路径:($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 else
71 {
72 result = QApplication::sendEvent(receiver, event);//TODO
73 }
74
75 ...
76
77 return result;
78 }
79
80 // section 2-4
81 // 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp)
82 inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)
83 {
84 // 将event标记为自发事件
85 // 进一步调用 2-5 QCoreApplication::notifyInternal
86 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】总结
到此为止。
相关推荐
- go语言也可以做gui,go-fltk让你做出c++级别的桌面应用
-
大家都知道go语言生态并没有什么好的gui开发框架,“能用”的一个手就能数的清,好用的就更是少之又少。今天为大家推荐一个go的gui库go-fltk。它是通过cgo调用了c++的fltk库,性能非常高...
- 旧电脑的首选系统:TinyCore!体积小+精简+速度极快,你敢安装吗
-
这几天老毛桃整理了几个微型Linux发行版,准备分享给大家。要知道可供我们日常使用的Linux发行版有很多,但其中的一些发行版经常会被大家忽视。其实这些微型Linux发行版是一种非常强大的创新:在一台...
- codeblocks和VS2019下的fltk使用中文
-
在fltk中用中文有点问题。英文是这样。中文就成这个样子了。我查了查资料,说用UTF-8编码就行了。edit->Fileencoding->UTF-8然后保存文件。看下下边的编码指示确...
- FLTK(Fast Light Toolkit)一个轻量级的跨平台Python GUI库
-
FLTK(FastLightToolkit)是一个轻量级的跨平台GUI库,特别适用于开发需要快速、高效且简单界面的应用程序。本文将介绍Python中的FLTK库,包括其特性、应用场景以及如何通过代...
- 中科院开源 RISC-V 处理器“香山”流片,已成功运行 Linux
-
IT之家1月29日消息,去年6月份,中科院大学教授、中科院计算所研究员包云岗,发布了开源高性能RISC-V处理器核心——香山。近日,包云岗在社交平台晒出图片,香山芯片已流片,回片后...
- Linux 5.13内核有望合并对苹果M1处理器支持的初步代码
-
预计Linux5.13将初步支持苹果SiliconM1处理器,不过完整的支持工作可能还需要几年时间才能完全完成。虽然Linux已经可以在苹果SiliconM1上运行,但这需要通过一系列的补丁才能...
- Ubuntu系统下COM口测试教程(ubuntu port)
-
1、在待测试的板上下载minicom,下载minicom有两种方法:方法一:在Ubuntu软件中心里面搜索下载方法二:按“Ctrl+Alt+T”打开终端,打开终端后输入“sudosu”回车;在下...
- 湖北嵌入式软件工程师培训怎么选,让自己脱颖而出
-
很多年轻人毕业即失业、面试总是不如意、薪酬不满意、在家躺平。“就业难”该如何应对,参加培训是否能改变自己的职业走向,在湖北,有哪些嵌入式软件工程师培训怎么选值得推荐?粤嵌科技在嵌入式培训领域有十几年经...
- 新阁上位机开发---10年工程师的Modbus总结
-
前言我算了一下,今年是我跟Modbus相识的第10年,从最开始的简单应用到协议了解,从协议开发到协议讲解,这个陪伴了10年的协议,它一直没变,变的只是我对它的理解和认识。我一直认为Modbus协议的存...
- 创建你的第一个可运行的嵌入式Linux系统-5
-
@ZHangZMo在MicrochipBuildroot中配置QT5选择Graphic配置文件增加QT5的配置修改根文件系统支持QT5修改output/target/etc/profile配置文件...
- 如何在Linux下给zigbee CC2530实现上位机
-
0、前言网友提问如下:粉丝提问项目框架汇总下这个网友的问题,其实就是实现一个网关程序,内容分为几块:下位机,通过串口与上位机相连;下位机要能够接收上位机下发的命令,并解析这些命令;下位机能够根据这些命...
- Python实现串口助手 - 03串口功能实现
-
串口调试助手是最核心的当然是串口数据收发与显示的功能,pzh-py-com借助的是pySerial库实现串口收发功能,今天痞子衡为大家介绍pySerial是如何在pzh-py-com发挥功能的。一、...
- 为什么选择UART(串口)作为调试接口,而不是I2C、SPI等其他接口
-
UART(通用异步收发传输器)通常被选作调试接口有以下几个原因:简单性:协议简单:UART的协议非常简单,只需设置波特率、数据位、停止位和校验位就可以进行通信。相比之下,I2C和SPI需要处理更多的通...
- 同一个类,不同代码,Qt 串口类QSerialPort 与各种外设通讯处理
-
串口通讯在各种外设通讯中是常见接口,因为各种嵌入式CPU中串口标配,工业控制中如果不够还通过各种串口芯片进行扩展。比如spi接口的W25Q128FV.对于软件而言,因为驱动接口固定,软件也相对好写,因...
- 嵌入式linux为什么可以通过PC上的串口去执行命令?
-
1、uboot(负责初始化基本硬bai件,如串口,网卡,usb口等,然du后引导系统zhi运行)2、linux系统(真正的操作系统)3、你的应用程序(基于操作系统的软件应用)当你开发板上电时,u...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- go语言也可以做gui,go-fltk让你做出c++级别的桌面应用
- 旧电脑的首选系统:TinyCore!体积小+精简+速度极快,你敢安装吗
- codeblocks和VS2019下的fltk使用中文
- FLTK(Fast Light Toolkit)一个轻量级的跨平台Python GUI库
- 中科院开源 RISC-V 处理器“香山”流片,已成功运行 Linux
- Linux 5.13内核有望合并对苹果M1处理器支持的初步代码
- Ubuntu系统下COM口测试教程(ubuntu port)
- 湖北嵌入式软件工程师培训怎么选,让自己脱颖而出
- 新阁上位机开发---10年工程师的Modbus总结
- 创建你的第一个可运行的嵌入式Linux系统-5
- 标签列表
-
- wireshark怎么抓包 (75)
- qt sleep (64)
- cs1.6指令代码大全 (55)
- factory-method (60)
- sqlite3_bind_blob (52)
- hibernate update (63)
- c++ base64 (70)
- nc 命令 (52)
- wm_close (51)
- epollin (51)
- sqlca.sqlcode (57)
- lua ipairs (60)
- tv_usec (64)
- 命令行进入文件夹 (53)
- postgresql array (57)
- statfs函数 (57)
- .project文件 (54)
- lua require (56)
- for_each (67)
- c#工厂模式 (57)
- wxsqlite3 (66)
- dmesg -c (58)
- fopen参数 (53)
- tar -zxvf -c (55)
- 速递查询 (52)