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()函数,那就是在窗口关闭的时候。
如果你在窗口关闭时需要有个询问对话框,那么就需要这么去写:
void MainWindow::closeEvent(QCloseEvent * event)
{
if (continueToClose())
{
event->accept();
}
else
{
event->ignore();
}
}
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的事件循环,大概来讲,事件循环如下面所示:
while (!exit_was_called)
{
while (!posted_event_queue_is_empty)
{
process_next_posted_event();
}
while (!spontaneous_event_queue_is_empty)
{
process_next_spontaneous_event();
}
while (!posted_event_queue_is_empty)
{
process_next_posted_event();
}
}
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】事件产生
事件产生详细过程:
// section 1-1
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show(); return a.exec();
} // section 1-2
// 源码路径:($QTDIR\Src\qtbase\src\widgets\kernel\qapplication.cpp)
int QApplication::exec()
{
return QGuiApplication::exec();
} // section 1-3
// 源码路径:($QTDIR\Src\qtbase\src\gui\kernel\qguiapplication.cpp)
int QGuiApplication::exec()
{
#ifndef QT_NO_ACCESSIBILITY
QAccessible::setRootObject(qApp);
#endif
return QCoreApplication::exec();
} // section 1-4
// 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp)
int QCoreApplication::exec()
{
if (!QCoreApplicationPrivate::checkInstance("exec"))
return -; QThreadData *threadData = self->d_func()->threadData;
if (threadData != QThreadData::current())
{
qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
return -;
}
if (!threadData->eventLoops.isEmpty())
{
qWarning("QCoreApplication::exec: The event loop is already running");
return -;
} threadData->quitNow = false;
QEventLoop eventLoop;
self->d_func()->in_exec = true;
self->d_func()->aboutToQuitEmitted = false;
int returnCode = eventLoop.exec();
threadData->quitNow = false;
if (self)
{
self->d_func()->in_exec = false;
if (!self->d_func()->aboutToQuitEmitted)
{
emit self->aboutToQuit(QPrivateSignal());
}
self->d_func()->aboutToQuitEmitted = true;
sendPostedEvents(, QEvent::DeferredDelete);
} return returnCode;
} // section 1-5
// 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qeventloop.cpp)
// 声明:int exec(ProcessEventsFlags flags = AllEvents);
int QEventLoop::exec(ProcessEventsFlags flags)
{
Q_D(QEventLoop);
// we need to protect from race condition with QThread::exit
QMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(d->threadData->thread))->mutex);
if (d->threadData->quitNow)
{
return -;
} if (d->inExec)
{
qWarning("QEventLoop::exec: instance %p has already called exec()", this);
return -;
} struct LoopReference
{
QEventLoopPrivate *d;
QMutexLocker &locker; bool exceptionCaught;
LoopReference(QEventLoopPrivate *d, QMutexLocker &locker) : d(d), locker(locker), exceptionCaught(true)
{
d->inExec = true;
d->exit.storeRelease(false);
++d->threadData->loopLevel;
d->threadData->eventLoops.push(d->q_func());
locker.unlock();
} ~LoopReference()
{
if (exceptionCaught)
{
qWarning("Qt has caught an exception thrown from an event handler. Throwing\n"
"exceptions from an event handler is not supported in Qt. You must\n"
"reimplement QApplication::notify() and catch all exceptions there.\n");
}
locker.relock();
QEventLoop *eventLoop = d->threadData->eventLoops.pop();
Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error");
Q_UNUSED(eventLoop); // --release warning
d->inExec = false;
--d->threadData->loopLevel;
}
};
LoopReference ref(d, locker); // remove posted quit events when entering a new event loop
QCoreApplication *app = QCoreApplication::instance();
if (app && app->thread() == thread())
{
QCoreApplication::removePostedEvents(app, QEvent::Quit);
} while (!d->exit.loadAcquire())
{
processEvents(flags | WaitForMoreEvents | EventLoopExec);
} ref.exceptionCaught = false;
return d->returnCode.load();
} // section 1-6
// 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qeventloop.cpp)
bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
Q_D(QEventLoop);
if (!d->threadData->eventDispatcher.load())
{
return false;
}
return d->threadData->eventDispatcher.load()->processEvents(flags);
} // section 1-7
// 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qeventdispatcher_win.cpp)
// 这段代码是完成与windows平台相关的windows c++。
// 以跨平台著称的Qt同时也提供了对Symiban、Unix等平台的消息派发支持,
// 分别封装在QEventDispatcherSymbian和QEventDIspatcherUNIX。
// QEventDispatcherWin32继承QAbstractEventDispatcher。
bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{
Q_D(QEventDispatcherWin32); if (!d->internalHwnd)
{
createInternalHwnd();
wakeUp(); // trigger a call to sendPostedEvents()
} d->interrupt = false;
emit awake(); bool canWait;
bool retVal = false;
bool seenWM_QT_SENDPOSTEDEVENTS = false;
bool needWM_QT_SENDPOSTEDEVENTS = false;
do
{
DWORD waitRet = ;
HANDLE pHandles[MAXIMUM_WAIT_OBJECTS - ];
QVarLengthArray<MSG> processedTimers;
while (!d->interrupt)
{
DWORD nCount = d->winEventNotifierList.count();
Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - ); MSG msg;
bool haveMessage; if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty())
{
// process queued user input events
haveMessage = true;
msg = d->queuedUserInputEvents.takeFirst(); // 逐个处理用户输入队列中的事件
}
else if (!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty())
{
// process queued socket events
haveMessage = true;
msg = d->queuedSocketEvents.takeFirst(); // 逐个处理socket队列中的事件
}
else
{
haveMessage = PeekMessage(&msg, , , , PM_REMOVE);
if (haveMessage && (flags & QEventLoop::ExcludeUserInputEvents)
&& ((msg.message >= WM_KEYFIRST && msg.message <= WM_KEYLAST)
|| (msg.message >= WM_MOUSEFIRST && msg.message <= WM_MOUSELAST)
|| msg.message == WM_MOUSEWHEEL
|| msg.message == WM_MOUSEHWHEEL
|| msg.message == WM_TOUCH
#ifndef QT_NO_GESTURES
|| msg.message == WM_GESTURE
|| msg.message == WM_GESTURENOTIFY
#endif
|| msg.message == WM_CLOSE))
{
// queue user input events for later processing
haveMessage = false;
d->queuedUserInputEvents.append(msg); // 用户输入事件入队列,待以后处理
}
if (haveMessage && (flags & QEventLoop::ExcludeSocketNotifiers)
&& (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd))
{
// queue socket events for later processing
haveMessage = false;
d->queuedSocketEvents.append(msg); // socket 事件入队列,待以后处理
}
}
if (!haveMessage)
{
// no message - check for signalled objects
for (int i = ; i < (int)nCount; i++)
{
pHandles[i] = d->winEventNotifierList.at(i)->handle();
}
waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, , QS_ALLINPUT, MWMO_ALERTABLE);
if ((haveMessage = (waitRet == WAIT_OBJECT_0 + nCount)))
{
// a new message has arrived, process it
continue;
}
}
if (haveMessage)
{
// WinCE doesn't support hooks at all, so we have to call this by hand :(
if (!d->getMessageHook)
{
(void) qt_GetMessageHook(, PM_REMOVE, (LPARAM) &msg);
} if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS)
{
if (seenWM_QT_SENDPOSTEDEVENTS)
{
// when calling processEvents() "manually", we only want to send posted
// events once
needWM_QT_SENDPOSTEDEVENTS = true;
continue;
}
seenWM_QT_SENDPOSTEDEVENTS = true;
}
else if (msg.message == WM_TIMER)
{
// avoid live-lock by keeping track of the timers we've already sent
bool found = false;
for (int i = ; !found && i < processedTimers.count(); ++i)
{
const MSG processed = processedTimers.constData()[i];
found = (processed.wParam == msg.wParam && processed.hwnd == msg.hwnd && processed.lParam == msg.lParam);
}
if (found)
{
continue;
}
processedTimers.append(msg);
}
else if (msg.message == WM_QUIT)
{
if (QCoreApplication::instance())
{
QCoreApplication::instance()->quit();
}
return false;
} if (!filterNativeEvent(QByteArrayLiteral("windows_generic_MSG"), &msg, ))
{
// 将事件打包成message调用Windows API派发出去
TranslateMessage(&msg);
// 分发一个消息给窗口程序。消息被分发到回调函数,将消息传递给windows系统,windows处理完毕,会调用回调函数。
DispatchMessage(&msg);
}
}
else if (waitRet - WAIT_OBJECT_0 < nCount)
{
d->activateEventNotifier(d->winEventNotifierList.at(waitRet - WAIT_OBJECT_0));
}
else
{
// nothing todo so break
break;
}
retVal = true;
} // still nothing - wait for message or signalled objects
canWait = (!retVal
&& !d->interrupt
&& (flags & QEventLoop::WaitForMoreEvents));
if (canWait)
{
DWORD nCount = d->winEventNotifierList.count();
Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - );
for (int i = ; i < (int)nCount; i++)
{
pHandles[i] = d->winEventNotifierList.at(i)->handle();
} emit aboutToBlock();
waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);
emit awake();
if (waitRet - WAIT_OBJECT_0 < nCount)
{
d->activateEventNotifier(d->winEventNotifierList.at(waitRet - WAIT_OBJECT_0));
retVal = true;
}
}
} while (canWait); if (!seenWM_QT_SENDPOSTEDEVENTS && (flags & QEventLoop::EventLoopExec) == )
{
// when called "manually", always send posted events
sendPostedEvents();
} if (needWM_QT_SENDPOSTEDEVENTS)
{
PostMessage(d->internalHwnd, WM_QT_SENDPOSTEDEVENTS, , );
} return retVal;
} // section1~7的过程:Qt进入QApplication的event loop,经过层层委任,
// 最终QEventLoop的processEvent将通过与平台相关的AbstractEventDispatcher的子类QEventDispatcherWin32
// 获得用户的输入事件,并将其打包成message后,通过标准的Windows API传递给Windows OS。
// Windows OS得到通知后回调QtWndProc,至此事件的分发与处理完成了一半的路程。
// 事件的产生、分发、接受和处理,并以视窗系统鼠标点击QWidget为例,对代码进行了剖析,向大家分析了Qt框架如何通过Event
// Loop处理进入处理消息队列循环,如何一步一步委派给平台相关的函数获取、打包用户输入事件交给视窗系统处理,函数调用栈如下:
// 1.main(int, char **)
// 2.QApplication::exec()
// 3.QCoreApplication::exec()
// 4.QEventLoop::exec(ProcessEventsFlags )
// 5.QEventLoop::processEvents(ProcessEventsFlags )
// 6.QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags)
【13】事件分发
事件分发详细过程:
// 1.QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) bool QETWidget::translateMouseEvent(const MSG &msg)
// 2.bool QApplicationPrivate::sendMouseEvent(...)
// 3.inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)
// 4.bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event)
// 5.bool QApplication::notify(QObject *receiver, QEvent *e)
// 6.bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)
// 7.bool QWidget::event(QEvent *event)
// 下面介绍Qt app在视窗系统回调后,事件又是怎么一步步通过QApplication分发给最终事件的接受和处理者QWidget::event,
// QWidget继承自Object,重载其虚函数event。
// section 2-1
// windows窗口回调函数
QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
// ...
// 检查message是否属于Qt可转义的鼠标事件
if (qt_is_translatable_mouse_event(message))
{
if (QApplication::activePopupWidget() != )
{ // in popup mode
POINT curPos = msg.pt;
// 取得鼠标点击坐标所在的QWidget指针,它指向我们在main创建的widget实例
QWidget* w = QApplication::widgetAt(curPos.x, curPos.y);
if (w)
{
widget = (QETWidget*)w;
}
} if (!qt_tabletChokeMouse)
{
// 对,就在这里。Windows的回调函数将鼠标事件分发回给了Qt Widget
// => Section 2-2
result = widget->translateMouseEvent(msg); // mouse event
}
} // ...
} // section 2-2 ($QTDIR\src\gui\kernel\qapplication_win.cpp)
// 该函数所在与Windows平台相关,主要职责就是把已用windows格式打包的鼠标事件解包、翻译成QApplication可识别的QMouseEvent。
bool QETWidget::translateMouseEvent(const MSG &msg)
{
// ...这里有很长的一段代码可以忽略
// 让我们看一下sendMouseEvent的声明
// widget是事件的接受者;e是封装好的QMouseEvent
// ==> Section 2-3
res = QApplicationPrivate::sendMouseEvent(target,
&e, alienWidget, this, &qt_button_down,
qt_last_mouse_receiver);
} // section 2-3
// 源码路径:($QTDIR\Src\qtbase\src\widgets\kernel\qapplication.cpp)
bool QApplicationPrivate::sendMouseEvent(QWidget *receiver, QMouseEvent *event,
QWidget *alienWidget, QWidget *nativeWidget,
QWidget **buttonDown, QPointer<QWidget> &lastMouseReceiver,
bool spontaneous)
{
// ...
// 至此与平台相关代码处理完毕
// MouseEvent默认的发送方式是spontaneous, 所以将执行sendSpontaneousEvent。
// sendSpontaneousEvent() 与 sendEvent的代码实现几乎相同
// 除了将QEvent的属性spontaneous标记不同。 这里是解释什么是spontaneous事件:事件由应用程序之外产生的,比如一个系统事件。
// 显然MousePress事件是由视窗系统产生的一个的事件(详见上文Section 1~ Section 7),因此它是spontaneous事件
if (spontaneous)
{
result = QApplication::sendSpontaneousEvent(receiver, event);
}
else
{
result = QApplication::sendEvent(receiver, event);//TODO
} ... return result;
} // section 2-4
// 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp)
inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)
{
// 将event标记为自发事件
// 进一步调用 2-5 QCoreApplication::notifyInternal
if (event)
{
event->spont = true;
}
return self ? self->notifyInternal(receiver, event) : false;
} // section 2-5:
// 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp)
bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event)
{
// 几行代码对于Qt Jambi (QT Java绑定版本) 和QSA (QT Script for Application)的支持
// ...
// 以下代码主要意图为Qt强制事件只能够发送给当前线程里的对象,
// 也就是说receiver->d_func()->threadData应该等于QThreadData::current()。
// 注意,跨线程的事件需要借助Event Loop来派发
QObjectPrivate *d = receiver->d_func();
QThreadData *threadData = d->threadData;
++threadData->loopLevel; // 哇,终于来到大名鼎鼎的函数QCoreApplication::nofity()了 ==> Section 2-6
QT_TRY
{
returnValue = notify(receiver, event);
}
QT_CATCH (...)
{
--threadData->loopLevel;
QT_RETHROW;
} ... return returnValue;
} // section 2-6:
// 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp)
// QCoreApplication::notify和它的重载函数QApplication::notify在Qt的派发过程中起到核心的作用,Qt的官方文档时这样说的:
// 任何线程的任何对象的所有事件在发送时都会调用notify函数。
bool QCoreApplication::notify(QObject *receiver, QEvent *event)
{
Q_D(QCoreApplication);
// no events are delivered after ~QCoreApplication() has started
if (QCoreApplicationPrivate::is_app_closing)
{
return true;
} if (receiver == )
{ // serious error
qWarning("QCoreApplication::notify: Unexpected null receiver");
return true;
} #ifndef QT_NO_DEBUG
d->checkReceiverThread(receiver);
#endif return receiver->isWidgetType() ? false : d->notify_helper(receiver, event);
} // section 2-7:
// 源码路径:($QTDIR\Src\qtbase\src\corelib\kernel\qcoreapplication.cpp)
// notify 调用 notify_helper()
bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
{
// send to all application event filters
if (sendThroughApplicationEventFilters(receiver, event))
{
return true;
}
// 向事件过滤器发送该事件,这里介绍一下Event Filters. 事件过滤器是一个接受即将发送给目标对象所有事件的对象。
//如代码所示它开始处理事件在目标对象行动之前。过滤器的QObject::eventFilter()实现被调用,能接受或者丢弃过滤
//允许或者拒绝事件的更进一步的处理。如果所有的事件过滤器允许更进一步的事件处理,事件将被发送到目标对象本身。
//如果他们中的一个停止处理,目标和任何后来的事件过滤器不能看到任何事件。
if (sendThroughObjectEventFilters(receiver, event))
{
return true;
}
// deliver the event
// 递交事件给receiver => Section 2-8
return receiver->event(event);
} // section 2-8
// 源码路径:($QTDIR\Src\qtbase\src\widgets\kernel\qwidget.cpp)
// QApplication通过notify及其私有类notify_helper,将事件最终派发给了QObject的子类- QWidget.
bool QWidget::event(QEvent *event)
{
// ...
switch (event->type())
{
case QEvent::MouseMove:
mouseMoveEvent((QMouseEvent*)event);
break; case QEvent::MouseButtonPress:
// Don't reset input context here. Whether reset or not is
// a responsibility of input method. reset() will be
// called by mouseHandler() of input method if necessary
// via mousePressEvent() of text widgets.
#if 0
resetInputContext();
#endif
mousePressEvent((QMouseEvent*)event);
break;
}
// ...
}
【14】Qt5.3.2版本事件机制源码调试
事件产生于分发调试堆栈图如下:
【15】总结
到此为止。
Good Good Study, Day Day Up.
顺序 选择 循环 总结
Qt 事件机制的更多相关文章
- Qt事件机制浅析(定义,产生,异步事件循环,转发,与信号的区别。感觉QT事件与Delphi的事件一致,而信号则与Windows消息一致)
Qt事件机制 Qt程序是事件驱动的, 程序的每个动作都是由幕后某个事件所触发.. Qt事件的发生和处理成为程序运行的主线,存在于程序整个生命周期. Qt事件的类型很多, 常见的qt的事件如下: 键盘事 ...
- 详解 QT 源码之 Qt 事件机制原理
QT 源码之 Qt 事件机制原理是本文要介绍的内容,在用Qt写Gui程序的时候,在main函数里面最后依据都是app.exec();很多书上对这句的解释是,使 Qt 程序进入消息循环.下面我们就到ex ...
- Qt事件机制---信号通过事件实现,事件可以过滤,事件更底层,事件是基础,信号是扩展。
转:http://www.cnblogs.com/findumars/p/8001484.html Qt事件机制(是动作发生后,一种通知对象的消息,是被动与主动的总和.先处理自己队列中的消息,然后再处 ...
- Qt事件机制(是动作发生后,一种通知对象的消息,是被动与主动的总和。先处理自己队列中的消息,然后再处理系统消息队列中的消息)
Qt事件机制 Qt程序是事件驱动的, 程序的每个动作都是由幕后某个事件所触发.. Qt事件的发生和处理成为程序运行的主线,存在于程序整个生命周期. Qt事件的类型很多, 常见的qt的事件如下: 键盘事 ...
- Qt事件机制浅析
Qt事件机制 Qt程序是事件驱动的, 程序的每个动作都是由幕后某个事件所触发.. Qt事件的发生和处理成为程序运行的主线,存在于程序整个生命周期. Qt事件的类型很多, 常见的qt的事件如下: 键盘事 ...
- qt事件机制(转)
学习了一段时间的Qt之后,发现Qt的事件机制和其他语言的机制有些不同.Qt除了能够通过信号和槽机制来实现一些Action动作之外,还可以用对象所带的事件,或者用户自定义的事件来实现对象的一些行为处理. ...
- [Qt] 事件机制(一)
事件主要分为两种: 在与用户交互时发生.比如按下鼠标(mousePressEvent),敲击键盘(keyPressEvent)等 系统自动发生,比如计时器事件(timerEvent)等 每种事件对应一 ...
- qt事件机制---事件范例
在笔记qt课程04笔记中
- [Qt] 事件机制(四)
滚轮事件:滚动滚轮实现窗口大小缩放 widget.h中增加: protected: void wheelEvent(QWheelEvent *event) Q_DECL_OVERRIDE; void ...
随机推荐
- Javascript 面向对象编程(一):封装 作者:yuan一峰
学习Javascript,最难的地方是什么? 我觉得,Object(对象)最难.因为Javascript的Object模型很独特,和其他语言都不一样,初学者不容易掌握. 下面就是我的学习笔记,希望对大 ...
- 网页制作中规范使用DIV+CSS命名规则,可以改善优化功效特别是团队合作时候可以提供合作制作效率,具体DIV CSS命名规则CSS命名大全内容如下:
页头:header 如:#header{属性:属性值;}或.header{属性:属性值;},也许你需要了解class与id区别及用法登录条:loginBar 标志:logo ...
- 【Mock】mock基础、简单的单元测试代码练习。
说到接口测试,必问 mock,mock 通俗一点来说就是模拟接口返回.解决接口的依赖关系,主要是为了解耦,单元测试用的多. 什么是Mock unittest.mock 是一个用于在 Python 中进 ...
- python wmi模块 获取windows内部信息
WMI (Windows Management Instrumentation) 模块可用于获取 Windows 内部信息,在使用Python获取Windows系统上的相关的信息可以使用WMI接口来获 ...
- 20190316 Python - Pandas
1. python 安装3.7版本 2. 第三方包进行数据加工和呈现 需要注意的是,你安装过程中会有很多依赖包问题,如果网络异常,那么就使用https://pypi.org/ 地址去找对应的包下载 ...
- darknet的安装及报错解决
darknet 是YOLO网络的一个框架,安装见官网:https://pjreddie.com/darknet/ 跟着步骤就可以安装好了. 由于官网是全英文的,所以本文根据官网进行中文释义. 本人在按 ...
- IconMoon图标字体制作
官网:https://icomoon.io/ 点击右上角“IconMoon APP” 点击左上角“import Icons”按钮 选中小图标 - 选择右下角“Generate Font”生成图标字体 ...
- Spark SQL DataFrame新增一列的四种方法
方法一:利用createDataFrame方法,新增列的过程包含在构建rdd和schema中 方法二:利用withColumn方法,新增列的过程包含在udf函数中 方法三:利用SQL代码,新增列的过程 ...
- [django]前后端分离之JWT用户认证
在前后端分离开发时为什么需要用户认证呢?原因是由于HTTP协定是不储存状态的(stateless),这意味着当我们透过帐号密码验证一个使用者时,当下一个request请求时它就把刚刚的资料忘了.于是我 ...
- Java 基础 面向对象之关键字内部类代码块修饰符
final final概念 继承的出现提高了代码的复用性,并方便开发.但随之也有问题,有些类在描述完之后,不想被继承,或者有些类中的部分方法功能是固定的,不想让子类重写.可是当子类继承了这些特殊类之后 ...