赞
踩
事件类型:
1、窗口系统事件spontaneous。
2、异步事件PostEvent。
3、同步事件SendEvent
窗体系统事件spontaneous
代表事件有:各类外设事件,如鼠标单双击、滚轮、移动,键盘按键等事件。
使用场景:该类事件都是窗体系统进行捕获的,我们一般不需要进行操作,除非我们需要模拟鼠标、键盘的事件。
异步事件PostEvent
代表事件有:绘图时的update事件。
使用场景:不需要马上回应的异步情况下,模拟各种事件。
例子:
//模拟按键点击
QTextEdit* text = new QTextEdit(this);
QPushButton* btn = new QPushButton("模拟",this);
text->setGeometry(100,0,400,300);
connect(btn,&QPushButton::clicked,this,[=]()
{
QKeyEvent *key = new QKeyEvent(QEvent::KeyPress, ' ', Qt::NoModifier, "A");
QApplication::postEvent(text, key); //_currentLineEdit
// QKeyEvent *key = new QKeyEvent(QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier);
// QApplication::postEvent(text, key); //_currentLineEdit
});
同步事件PostEvent
代表事件有:绘图时的repaint事件。
使用场景:需要发送后立刻执行的情况下,模拟各种事件。
例子:
//模拟按键点击
QTextEdit* text = new QTextEdit(this);
QPushButton* btn = new QPushButton("模拟",this);
text->setGeometry(100,0,400,300);
connect(btn,&QPushButton::clicked,this,[=]()
{
QKeyEvent *key = new QKeyEvent(QEvent::KeyPress, ' ', Qt::NoModifier, "A");
QApplication::sendEvent(text, key); //_currentLineEdit
// QKeyEvent *key = new QKeyEvent(QEvent::KeyPress, Qt::Key_Backspace, Qt::NoModifier);
// QApplication::sendEvent(text, key); //_currentLineEdit
});
sendEvent、PostEvent、自定义事件:详细实例。
sendEvent的原理需要查看它的源代码就能发现它同步执行的方式了。
1、
bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
{
Q_TRACE(QCoreApplication_sendEvent, receiver, event, event->type());
// 如果事件不为空,则将 spont 标志设置为 false
if (event)
event->spont = false;
// 调用内部函数 notifyInternal2 发送事件,并返回结果
return notifyInternal2(receiver, event);
}
直接调用notifyInternal2接口.
2、
bool QCoreApplication::notifyInternal2(QObject *receiver, QEvent *event)
{
bool selfRequired = QCoreApplicationPrivate::threadRequiresCoreApplication();
if (!self && selfRequired)
return false;
bool result = false;
void *cbdata[] = { receiver, event, &result };
if (QInternal::activateCallbacks(QInternal::EventNotifyCallback, cbdata)) {
return result;
}
QObjectPrivate *d = receiver->d_func();
QThreadData *threadData = d->threadData;
QScopedScopeLevelCounter scopeLevelCounter(threadData);
if (!selfRequired)
return doNotify(receiver, event);
return self->notify(receiver, event);
}
如果selfRequired的请求不存在则调用doNotify,如果存在则调用到notify。
3、
bool QCoreApplication::notify(QObject *receiver, QEvent *event)
{
// ~QCoreApplication()析构调用后不会传递任何事件
if (QCoreApplicationPrivate::is_app_closing)
return true;
return doNotify(receiver, event);
}
判断程序打开时才调用doNotify。
4、
static bool doNotify(QObject *receiver, QEvent *event)
{
if (receiver == 0) { // serious error
qWarning("QCoreApplication::notify: Unexpected null receiver");
return true;
}
#ifndef QT_NO_DEBUG
QCoreApplicationPrivate::checkReceiverThread(receiver);
#endif
return receiver->isWidgetType() ? false : QCoreApplicationPrivate::notify_helper(receiver, event);
}
调用notify_helper。
5、
bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
{
Q_TRACE(QCoreApplication_notify_entry, receiver, event, event->type());
// send to all application event filters (only does anything in the main thread)
if (QCoreApplication::self
&& receiver->d_func()->threadData->thread == mainThread()
&& QCoreApplication::self->d_func()->sendThroughApplicationEventFilters(receiver, event)) {
Q_TRACE(QCoreApplication_notify_event_filtered, receiver, event, event->type());
return true;
}
// send to all receiver event filters
if (sendThroughObjectEventFilters(receiver, event)) {
Q_TRACE(QCoreApplication_notify_event_filtered, receiver, event, event->type());
return true;
}
Q_TRACE(QCoreApplication_notify_before_delivery, receiver, event, event->type());
// deliver the event
const bool consumed = receiver->event(event);
Q_TRACE(QCoreApplication_notify_after_delivery, receiver, event, event->type(), consumed);
return consumed;
}
bool QCoreApplicationPrivate::sendThroughApplicationEventFilters(QObject *receiver, QEvent *event)
{
// We can't access the application event filters outside of the main thread (race conditions)
Q_ASSERT(receiver->d_func()->threadData->thread == mainThread());
if (extraData) {
// application event filters are only called for objects in the GUI thread
for (int i = 0; i < extraData->eventFilters.size(); ++i) {
QObject *obj = extraData->eventFilters.at(i);
if (!obj)
continue;
if (obj->d_func()->threadData != threadData) {
qWarning("QCoreApplication: Application event filter cannot be in a different thread.");
continue;
}
if (obj->eventFilter(receiver, event))
return true;
}
}
return false;
}
bool QCoreApplicationPrivate::sendThroughObjectEventFilters(QObject *receiver, QEvent *event)
{
if (receiver != QCoreApplication::instance() && receiver->d_func()->extraData) {
for (int i = 0; i < receiver->d_func()->extraData->eventFilters.size(); ++i) {
QObject *obj = receiver->d_func()->extraData->eventFilters.at(i);
if (!obj)
continue;
if (obj->d_func()->threadData != receiver->d_func()->threadData) {
qWarning("QCoreApplication: Object event filter cannot be in a different thread.");
continue;
}
if (obj->eventFilter(receiver, event))
return true;
}
}
return false;
}
在这里可以看到事件是如何处理的:
先送入Application的事件过滤器,看看是否在事件过滤器中处理,再查看receiver是否有此事件的过滤器,最后将事件送入receiver的event接口。从整个过程来看,可以认为sendEvent直接调用了receiver的event接口。因此,可以认为处理方式为同步处理方式。
说到spontaneous事件和postevent事件的原理,就需要了解main函数中“a.exec()”,因为他们的执行都是在exec这个函数中执行的。
1、spontaneous窗体系统事件一般都是通过系统层面的中断来捕获事件的。
postEvent源码:
void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
{
Q_TRACE(QCoreApplication_postEvent_entry, receiver, event, event->type());
if (receiver == 0) {
qWarning("QCoreApplication::postEvent: Unexpected null receiver");
delete event;
return;
}
QThreadData * volatile * pdata = &receiver->d_func()->threadData;
QThreadData *data = *pdata;
if (!data) {
// posting during destruction? just delete the event to prevent a leak
delete event;
return;
}
// lock the post event mutex
data->postEventList.mutex.lock();
// 判断事件是否在另外一个线程
while (data != *pdata) {
data->postEventList.mutex.unlock();
data = *pdata;
if (!data) {
// 发送期间如果事件已经不存在了,则销毁它,防止内存泄漏。
delete event;
return;
}
data->postEventList.mutex.lock();
}
QMutexUnlocker locker(&data->postEventList.mutex);
// 如果这是可一个压缩事件,请执行压缩,例如短时间多次触发update就会触发压缩事件。
if (receiver->d_func()->postedEvents
&& self && self->compressEvent(event, receiver, &data->postEventList)) {
Q_TRACE(QCoreApplication_postEvent_event_compressed, receiver, event);
return;
}
if (event->type() == QEvent::DeferredDelete)
receiver->d_ptr->deleteLaterCalled = true;
if (event->type() == QEvent::DeferredDelete && data == QThreadData::current()) {
// remember the current running eventloop for DeferredDelete
// events posted in the receiver's thread.
// Events sent by non-Qt event handlers (such as glib) may not
// have the scopeLevel set correctly. The scope level makes sure that
// code like this:
// foo->deleteLater();
// qApp->processEvents(); // without passing QEvent::DeferredDelete
// will not cause "foo" to be deleted before returning to the event loop.
// If the scope level is 0 while loopLevel != 0, we are called from a
// non-conformant code path, and our best guess is that the scope level
// should be 1. (Loop level 0 is special: it means that no event loops
// are running.)
int loopLevel = data->loopLevel;
int scopeLevel = data->scopeLevel;
if (scopeLevel == 0 && loopLevel != 0)
scopeLevel = 1;
static_cast<QDeferredDeleteEvent *>(event)->level = loopLevel + scopeLevel;
}
// 删除异常上的事件以防止内存泄漏,直到该事件在postEventList中正确拥有为止
QScopedPointer<QEvent> eventDeleter(event);
Q_TRACE(QCoreApplication_postEvent_event_posted, receiver, event, event->type());
//将事件添加到postEventList中,注意这里的优先级第一个最高,最后一个优先级最低
data->postEventList.addEvent(QPostEvent(receiver, event, priority));
eventDeleter.take();
event->posted = true;
++receiver->d_func()->postedEvents;
data->canWait = false;
locker.unlock();
QAbstractEventDispatcher* dispatcher = data->eventDispatcher.loadAcquire();
if (dispatcher)
dispatcher->wakeUp();
}
从上面可以看出,postEvent实际上是将事件添加到receiver所在线程中的一个队列中,至于这个队列所在的线程什么时候处理这个事件,postEvent是无法控制的。
2、再来看下main函数中“a.exec()”。
int QApplication::exec()
{
return QGuiApplication::exec();
}
int QCoreApplication::exec()
{
if (!QCoreApplicationPrivate::checkInstance("exec"))
return -1;
QThreadData *threadData = self->d_func()->threadData;
if (threadData != QThreadData::current()) {
qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
return -1;
}
if (!threadData->eventLoops.isEmpty()) {
qWarning("QCoreApplication::exec: The event loop is already running");
return -1;
}
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()->execCleanup();
return returnCode;
}
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 -1;
if (d->inExec) {
qWarning("QEventLoop::exec: instance %p has already called exec()", this);
return -1;
}
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.\n"
"You must not let any exception whatsoever propagate through Qt code.\n"
"If that is not possible, in Qt 5 you must at least reimplement\n"
"QCoreApplication::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);
#ifdef Q_OS_WASM
// Partial support for nested event loops: Make the runtime throw a JavaSrcript
// exception, which returns control to the browser while preserving the C++ stack.
// Event processing then continues as normal. The sleep call below never returns.
// QTBUG-70185
if (d->threadData->loopLevel > 1)
emscripten_sleep(1);
#endif
while (!d->exit.loadAcquire())
processEvents(flags | WaitForMoreEvents | EventLoopExec);
ref.exceptionCaught = false;
return d->returnCo
}
bool QEventLoop::processEvents(ProcessEventsFlags flags)
{
Q_D(QEventLoop);
if (!d->threadData->hasEventDispatcher())
return false;
return d->threadData->eventDispatcher.load()->processEvents(flags);
}
调用到了这个位置时,就需要区分系统了,processEvents这个函数是来源于QAbstractEventDispatcher的一个纯虚函数,在Windows中是调用继承QAbstractEventDispatcher的QEventDispatcherWin32的processEvents;在linux系统中是继承QAbstractEventDispatcher的QEventDIspatcherUNIX的processEvents。
bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
{
Q_D(QEventDispatcherWin32);
if (!d->internalHwnd) {
createInternalHwnd();
wakeUp(); // trigger a call to sendPostedEvents()
}
d->interrupt.store(false);
emit awake();
bool canWait;
bool retVal = false;
bool seenWM_QT_SENDPOSTEDEVENTS = false;
bool needWM_QT_SENDPOSTEDEVENTS = false;
do {
DWORD waitRet = 0;
DWORD nCount = 0;
HANDLE *pHandles = nullptr;
if (d->winEventNotifierActivatedEvent) {
nCount = 1;
pHandles = &d->winEventNotifierActivatedEvent;
}
QVarLengthArray<MSG> processedTimers;
while (!d->interrupt.load()) {
MSG msg;
bool haveMessage;
if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {
// 处理排队的用户输入事件。
haveMessage = true;
msg = d->queuedUserInputEvents.takeFirst();
} else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) {
// 处理排队的套接字事件
haveMessage = true;
msg = d->queuedSocketEvents.takeFirst();
} else {
haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
if (haveMessage) {
if (flags.testFlag(QEventLoop::ExcludeUserInputEvents)
&& isUserInputMessage(msg.message)) {
// 将用户输入事件排队以供稍后处理
d->queuedUserInputEvents.append(msg);
continue;
}
if ((flags & QEventLoop::ExcludeSocketNotifiers)
&& (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) {
// 将套接字事件排队以供稍后处理
d->queuedSocketEvents.append(msg);
continue;
}
}
}
if (!haveMessage) {
// no message - check for signalled objects
waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, 0, 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(0, PM_REMOVE, reinterpret_cast<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 = 0; !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, 0)) {
// 将事件打包成message调用Windows API派发出去
TranslateMessage(&msg);
// 分发一个消息给窗口程序。消息被分发到回调函数,将消息传递给windows系统,windows处理完毕,会调用回调函数。
DispatchMessage(&msg);
}
} else if (waitRet - WAIT_OBJECT_0 < nCount) {
activateEventNotifiers();
} else {
// nothing todo so break
break;
}
retVal = true;
}
// still nothing - wait for message or signalled objects
canWait = (!retVal
&& !d->interrupt.load()
&& (flags & QEventLoop::WaitForMoreEvents));
if (canWait) {
emit aboutToBlock();
waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);
emit awake();
if (waitRet - WAIT_OBJECT_0 < nCount) {
activateEventNotifiers();
retVal = true;
}
}
} while (canWait);
if (!seenWM_QT_SENDPOSTEDEVENTS && (flags & QEventLoop::EventLoopExec) == 0) {
// when called "manually", always send posted events
sendPostedEvents();
}
if (needWM_QT_SENDPOSTEDEVENTS)
PostMessage(d->internalHwnd, WM_QT_SENDPOSTEDEVENTS, 0, 0);
return retVal;
}
TranslateMessage(&msg)和DispatchMessage(&msg)这两个函数将事件打包成msg分发给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() != 0)
{ // 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
}
}
// ...
}
以上就是windows窗口回调函数了。通过translateMouseEvent来将事件分发出去。
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);
}
bool QApplicationPrivate::sendMouseEvent(QWidget *receiver, QMouseEvent *event,
QWidget *alienWidget, QWidget *nativeWidget,
QWidget **buttonDown, QPointer<QWidget> &lastMouseReceiver,
bool spontaneous, bool onlyDispatchEnterLeave)
{
Q_ASSERT(receiver);
Q_ASSERT(event);
Q_ASSERT(nativeWidget);
Q_ASSERT(buttonDown);
if (alienWidget && !isAlien(alienWidget))
alienWidget = 0;
QPointer<QWidget> receiverGuard = receiver;
QPointer<QWidget> nativeGuard = nativeWidget;
QPointer<QWidget> alienGuard = alienWidget;
QPointer<QWidget> activePopupWidget = QApplication::activePopupWidget();
const bool graphicsWidget = nativeWidget->testAttribute(Qt::WA_DontShowOnScreen);
bool widgetUnderMouse = QRectF(receiver->rect()).contains(event->localPos());
// Clear the obsolete leaveAfterRelease value, if mouse button has been released but
// leaveAfterRelease has not been updated.
// This happens e.g. when modal dialog or popup is shown as a response to button click.
if (leaveAfterRelease && !*buttonDown && !event->buttons())
leaveAfterRelease = 0;
if (*buttonDown) {
if (!graphicsWidget) {
// Register the widget that shall receive a leave event
// after the last button is released.
if ((alienWidget || !receiver->internalWinId()) && !leaveAfterRelease && !QWidget::mouseGrabber())
leaveAfterRelease = *buttonDown;
if (event->type() == QEvent::MouseButtonRelease && !event->buttons())
*buttonDown = 0;
}
} else if (lastMouseReceiver && widgetUnderMouse) {
// Dispatch enter/leave if we move:
// 1) from an alien widget to another alien widget or
// from a native widget to an alien widget (first OR case)
// 2) from an alien widget to a native widget (second OR case)
if ((alienWidget && alienWidget != lastMouseReceiver)
|| (isAlien(lastMouseReceiver) && !alienWidget)) {
if (activePopupWidget) {
if (!QWidget::mouseGrabber())
dispatchEnterLeave(alienWidget ? alienWidget : nativeWidget, lastMouseReceiver, event->screenPos());
} else {
dispatchEnterLeave(receiver, lastMouseReceiver, event->screenPos());
}
}
}
#ifdef ALIEN_DEBUG
qDebug() << "QApplicationPrivate::sendMouseEvent: receiver:" << receiver
<< "pos:" << event->pos() << "alien" << alienWidget << "button down"
<< *buttonDown << "last" << lastMouseReceiver << "leave after release"
<< leaveAfterRelease;
#endif
// We need this quard in case someone opens a modal dialog / popup. If that's the case
// leaveAfterRelease is set to null, but we shall not update lastMouseReceiver.
const bool wasLeaveAfterRelease = leaveAfterRelease != 0;
bool result = true;
// This code is used for sending the synthetic enter/leave events for cases where it is needed
// due to other events causing the widget under the mouse to change. However in those cases
// we do not want to send the mouse event associated with this call, so this enables us to
// not send the unneeded mouse event
if (!onlyDispatchEnterLeave) {
if (spontaneous)
result = QApplication::sendSpontaneousEvent(receiver, event);
else
result = QApplication::sendEvent(receiver, event);
}
if (!graphicsWidget && leaveAfterRelease && event->type() == QEvent::MouseButtonRelease
&& !event->buttons() && QWidget::mouseGrabber() != leaveAfterRelease) {
// Dispatch enter/leave if:
// 1) the mouse grabber is an alien widget
// 2) the button is released on an alien widget
QWidget *enter = 0;
if (nativeGuard)
enter = alienGuard ? alienWidget : nativeWidget;
else // The receiver is typically deleted on mouse release with drag'n'drop.
enter = QApplication::widgetAt(event->globalPos());
dispatchEnterLeave(enter, leaveAfterRelease, event->screenPos());
leaveAfterRelease = 0;
lastMouseReceiver = enter;
} else if (!wasLeaveAfterRelease) {
if (activePopupWidget) {
if (!QWidget::mouseGrabber())
lastMouseReceiver = alienGuard ? alienWidget : (nativeGuard ? nativeWidget : 0);
} else {
lastMouseReceiver = receiverGuard ? receiver : QApplication::widgetAt(event->globalPos());
}
}
return result;
}
bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)
{
Q_TRACE(QCoreApplication_sendSpontaneousEvent, receiver, event, event->type());
if (event)
event->spont = true;
return notifyInternal2(receiver, event);
}
从上面两个函数就可以知道translateMouseEvent–>sendMouseEvent–>sendSpontaneousEvent或者sendEvent。到了sendEvent我们知道后面的调用流程了。其中sendSpontaneousEvent函数里面其实也是和sendEvent一样来进行后面的调用的。
postEvent和spontaneous调用流程较复杂,总结一下:
1、postEvent和spontaneous事件加入到postEventList的链表中。
2、在main函数的exec里面存在一个eventloop系统循环函数。
3、系统循环函数区分windous和linux系统后从processEvents中依次取出各种事件,其中就包括postEvent和spontaneous事件,将它们打包成MSG事件分发给窗口回调函数。
4、窗口回调函数收到MSG事件后调用translateMouseEvent–>sendMouseEvent–>sendSpontaneousEvent–>sendEvent。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。