QT程序是事件驅動的, 程序的每一個動做都是由內部某個事件所觸發。QT事件的發生和處理成爲程序運行的主線,存在於程序整個生命週期。windows
常見的QT事件類型以下:app
鍵盤事件: 按鍵按下和鬆開異步
鼠標事件: 鼠標移動,鼠標按鍵的按下和鬆開socket
拖放事件: 用鼠標進行拖放ide
滾輪事件: 鼠標滾輪滾動函數
繪屏事件: 重繪屏幕的某些部分oop
定時事件: 定時器到時源碼分析
焦點事件: 鍵盤焦點移動post
進入和離開事件: 鼠標移入widget以內,或是移出優化
移動事件: widget的位置改變
大小改變事件: widget的大小改變
顯示和隱藏事件: widget顯示和隱藏
窗口事件: 窗口是否爲當前窗口
QT將系統產生的消息轉化爲QT事件,QT事件被封裝爲對象,全部的QT事件均繼承抽象類QEvent,用於描述程序內部或外部發生的動做,任意的QObject對象都具有處理QT事件的能力。
操做系統將獲取的事件,好比鼠標按鍵,鍵盤按鍵等keyPressEvent,keyReleaseEvent,mousePressEvent,mouseReleaseEvent事件, 放入系統的消息隊列中,Qt事件循環的時候讀取消息隊列中的消息,轉化爲QEvent並被分發到相應的QWidget對象,相應的QWidget中的event(QEvent *)進行事件處理會對事件進行處理,event(QEvent *)會根據事件類型調用不一樣的事件處理函數,在事件處理函數中發送QT預約義的信號,最終調用信號關聯的槽函數。
GUI應用程序的事件處理:
A、QT事件產生後會被當即發送到相應的QWidget對象
B、相應的QWidget中的event(QEvent *)進行事件處理
C、event(QEvent *)根據事件類型調用不一樣的事件處理函數
D、在事件處理函數中發送QT預約義的信號
E、調用信號關聯的槽函數
程序產生事件有兩種方式, 一種是調用QApplication::postEvent(), 例如QWidget::update()函數,當須要從新繪製屏幕時,程序調用update()函數,new出來一個paintEvent,調用 QApplication::postEvent(),將其放入Qt的消息隊列中,等待依次被處理;另外一種方式是調用sendEvent()函數,事件不會放入隊列, 而是直接被派發和處理, QWidget::repaint()函數用的就是阻塞型的。
sendEvent()中事件對象的生命期由Qt程序管理,支持分配在棧上和堆上的事件對象;postEvent()中事件對象的生命期由Qt平臺管理,只支持分配在堆上的事件對象,事件被處理後由Qt平臺銷燬。
A、事件由具體的QWidget(子)對象進行處理,信號則由具體的QWidget(子)對象生成。
B、改寫事件處理函數可能會致使程序行爲發生改變,信號是否存在對應的槽函數不會改變程序的行爲。
C、一般,信號在具體的事件處理函數中產生。
事件有兩種調度方式,同步和異步。
Qt的事件循環是異步的,當調用QApplication::exec()時,就進入了事件循環,先處理Qt事件隊列中的事件, 直至爲空,再處理系統消息隊列中的消息, 直至爲空, 處理系統消息的時候會產生新的Qt事件, 須要對其再次進行處理。
調用QApplication::sendEvent的時候, 消息會當即被處理,是同步的。實際上QApplication::sendEvent()是經過調用QApplication::notify(), 直接進入了事件的派發和處理環節。
事件過濾器是Qt中一個獨特的事件處理機制, 功能強大並且使用起來靈活方便。經過事件過濾器, 可讓一個對象偵聽攔截另一個對象的事件。事件過濾器實現以下: 在全部Qt對象的基類QObject中有一個類型爲QObjectList的成員變量,名字爲eventFilters,當某個QObject(A)給另外一個QObject(B)安裝了事件過濾器後, B會把A的指針保存在eventFilters中。在B處理事件前,會先去檢查eventFilters列表, 若是非空, 就先調用列表中對象的eventFilter()函數。一個對象能夠給多個對象安裝過濾器,一個對象能同時被安裝多個過濾器, 在事件到達以後, 事件過濾器以安裝次序的反序被調用。事件過濾器函數( eventFilter() ) 返回值是bool型, 若是返回true, 則表示事件已經被處理完畢, Qt將直接返回, 進行下一事件的處理。若是返回false, 事件將接着被送往剩下的事件過濾器或是目標對象進行處理。
QT中,事件的派發是從 QApplication::notify()開始的, 由於QAppliction也是繼承自QObject, 因此先檢查QAppliation對象, 若是有事件過濾器安裝在qApp上, 先調用事件過濾器,接下來QApplication::notify() 會過濾或合併一些事件(好比失效widget的鼠標事件會被過濾掉, 而同一區域重複的繪圖事件會被合併),事件被送到reciver::event()處理。
在reciver::event()中, 先檢查有無事件過濾器安裝在reciever上。如有, 則調用之。而後根據QEvent的類型, 調用相應的特定事件處理函數。常見的事件都有特定事件處理函數, 好比:mousePressEvent(), focusOutEvent(), resizeEvent(), paintEvent(), resizeEvent()等等。在實際應用中, 常常須要重載特定事件處理函數處理事件。對於不常見的事件, 沒有相對應的特定事件處理函數,若是要處理這些事件, 就須要使用別的辦法, 好比重載event() 函數, 或是安裝事件過濾器。
對於某些類別的事件,若是在整個事件的派發過程結束後尚未被處理, 那麼這個事件將會向上轉發給它的父widget, 直到最頂層窗口。Qt中和事件相關的函數經過兩種方式相互通訊,一種是QApplication::notify(), QObject::eventFilter(), QObject::event()經過返回bool值來表示是否已處理;另外一種是調用QEvent::ignore() 或 QEvent::accept() 對事件進行標識,只用於event()函數和特定事件處理函數之間的溝通,並且只有用在某些類別事件上是有意義的, 這些事件就是上面提到的那些會被轉發的事件, 包括: 鼠標, 滾輪, 按鍵等事件。
QT提供了五種不一樣級別的事件處理和過濾:
A、重寫特定事件處理函數.
最多見的事件處理辦法就是重寫mousePressEvent(), keyPressEvent(), paintEvent() 等特定事件處理函數。
B、重寫event()函數.
重寫event()函數時, 須要調用父類的event()函數來處理不須要處理或是不清楚如何處理的事件。
return QWidget::event(event);
C、在Qt對象上安裝事件過濾器
安裝事件過濾器有兩個步驟: (假設要用A來監視過濾B的事件)
首先調用B的installEventFilter( const QOject *obj ), 以A的指針做爲參數,全部發往B的事件都將先由A的eventFilter()處理。而後, A要重寫QObject::eventFilter()函數, 在eventFilter() 中對事件進行處理。
D、給QAppliction對象安裝事件過濾器
若是給QApplication對象裝上過濾器,那麼全部的事件在發往任何其餘的過濾器時,都要先通過當前eventFilter()。在QApplication::notify() 中, 是先調用qApp的過濾器, 再對事件進行分析, 以決定是否合併或丟棄。
E、繼承QApplication類,並重載notify()函數
Qt是用QApplication::notify()函數來分發事件的,要在任何事件過濾器查看任何事件以前先獲得這些事件,重寫notify()函數是惟一的辦法。一般來講事件過濾器更好用一些, 由於不須要去繼承QApplication類,並且能夠給QApplication對象安裝任意個數的事件過濾器。
GUI應用程序的事件處理:
A、QT事件產生後會被當即發送到QWidget對象
B、QWwidget中的event(QEvent *)進行事件處理
C、event(QEvent *)根據事件類型調用不一樣的事件處理函數
D、在事件處理函數中發送QT預約義的信號
E、調用信號關聯的槽函數
QT源碼中事件梳理過程當中調用函數以下:
QApplication::exec() QCoreApplication::exec() QEventLoop::exec(ProcessEventsFlags ) QEventLoop::processEvents(ProcessEventsFlags ) QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags) QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) bool QETWidget::translateMouseEvent(const MSG &msg) bool QApplicationPrivate::sendMouseEvent(...) inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event) bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event) bool QApplication::notify(QObject *receiver, QEvent *e) bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e) bool QWidget::event(QEvent *event)
int main(int argc, char *argv[]) { QApplication app(argc, argv); Widget window; // Widget 繼承自QWidget window.show(); return app.exec(); // 進入Qpplication事件循環 }
int QApplication::exec() { #ifndef QT_NO_ACCESSIBILITY QAccessible::setRootObject(qApp); #endif //簡單的交給QCoreApplication來處理事件循環 return QCoreApplication::exec(); }
int QCoreApplication::exec() { if (!QCoreApplicationPrivate::checkInstance("exec")) return -1; //獲得當前Thread數據 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; } //檢查event loop是否已經建立 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; //委任QEventLoop 處理事件隊列循環 int returnCode = eventLoop.exec(); threadData->quitNow = false; if (self) { self->d_func()->in_exec = false; if (!self->d_func()->aboutToQuitEmitted) emit self->aboutToQuit(); self->d_func()->aboutToQuitEmitted = true; sendPostedEvents(0, QEvent::DeferredDelete); } return returnCode; }
int QEventLoop::exec(ProcessEventsFlags flags) { Q_D(QEventLoop); //訪問QEventloop私有類實例d //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; } d->inExec = true; d->exit = false; ++d->threadData->loopLevel; d->threadData->eventLoops.push(this); locker.unlock(); // remove posted quit events when entering a new event loop QCoreApplication *app = QCoreApplication::instance(); if (app && app->thread() == thread()) QCoreApplication::removePostedEvents(app, QEvent::Quit); //這裏的實現代碼很多,最爲重要的是如下幾行 #if defined(QT_NO_EXCEPTIONS) while (!d->exit) processEvents(flags | WaitForMoreEvents | EventLoopExec);#else try { while (!d->exit) //只要沒有碰見exit,循環派發事件 processEvents(flags | WaitForMoreEvents | EventLoopExec); } catch (...) { 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"); // copied from below locker.relock(); QEventLoop *eventLoop = d->threadData->eventLoops.pop(); Q_ASSERT_X(eventLoop == this, "QEventLoop::exec()", "internal error"); Q_UNUSED(eventLoop); // --release warning d->inExec = false; --d->threadData->loopLevel; throw; }#endif // copied above locker.relock(); QEventLoop *eventLoop = d->threadData->eventLoops.pop(); Q_ASSERT_X(eventLoop == this, "QEventLoop::exec()", "internal error"); Q_UNUSED(eventLoop); // --release warning d->inExec = false; --d->threadData->loopLevel; return d->returnCode; }
bool QEventLoop::processEvents(ProcessEventsFlags flags) { Q_D(QEventLoop); if (!d->threadData->eventDispatcher) return false; if (flags & DeferredDeletion) QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); //將事件派發給與平臺相關的QAbstractEventDispatcher子類 return d->threadData->eventDispatcher->processEvents(flags); }
AbstractEventDispatcher的子類QEventDispatcherWin32得到用戶的輸入事件,並將其打包成message後,經過標準的Windows API傳遞給Windows OS
bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags) { Q_D(QEventDispatcherWin32); if (!d->internalHwnd) createInternalHwnd(); d->interrupt = false; emit awake(); bool canWait; bool retVal = false; bool seenWM_QT_SENDPOSTEDEVENTS = false; bool needWM_QT_SENDPOSTEDEVENTS = false; do { DWORD waitRet = 0; HANDLE pHandles[MAXIMUM_WAIT_OBJECTS - 1]; QVarLengthArray<MSG> processedTimers; while (!d->interrupt) { DWORD nCount = d->winEventNotifierList.count(); Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1); 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隊列中取出一條事件,處理隊列裏面的socket事件 } else { haveMessage = PeekMessage(&msg, 0, 0, 0, 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=0; i<(int)nCount; i++) pHandles[i] = d->winEventNotifierList.at(i)->handle(); 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) { #ifdef Q_OS_WINCE // WinCE doesn't support hooks at all, so we have to call this by hand :( (void) qt_GetMessageHook(0, PM_REMOVE, (LPARAM) &msg);#endif 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 (!filterEvent(&msg)) { TranslateMessage(&msg); //將事件打包成message調用Windows API派發出去 DispatchMessage(&msg); //分發一個消息給窗口程序。消息被分發到回調函數,將消息傳遞給windows系統,windows處理完畢,會調用回調函數 => section 7 } } 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 - 1); for (int i=0; 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) == 0) { // when called "manually", always send posted events QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData); } if (needWM_QT_SENDPOSTEDEVENTS) PostMessage(d->internalHwnd, WM_QT_SENDPOSTEDEVENTS, 0, 0); return retVal; }
extern "C" LRESULT QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { ... //將消息從新封裝成QEvent的子類QMouseEvent ==> Section 8 result = widget->translateMouseEvent(msg); ... }
bool QETWidget::translateMouseEvent(const MSG &msg) { ...... 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) { ... if (spontaneous) result = QApplication::sendSpontaneousEvent(receiver, event); else result = QApplication::sendEvent(receiver, event); ... return result; }
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; }
bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event) { ... // 如下代碼主要意圖爲Qt強制事件只可以發送給當前線程裏的對象,也就是說receiver->d_func()->threadData應該等於QThreadData::current()。 //注意,跨線程的事件須要藉助Event Loop來派發 QObjectPrivate *d = receiver->d_func(); QThreadData *threadData = d->threadData; ++threadData->loopLevel; QT_TRY { returnValue = notify(receiver, event); } QT_CATCH (...) { --threadData->loopLevel; QT_RETHROW; } ... return returnValue; }
任何線程的任何對象的全部事件在發送時都會調用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 == 0) { // 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); }
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; return receiver->event(event); }
bool QWidget::event(QEvent *event) { ... switch (event->type()) { case QEvent::MouseMove: mouseMoveEvent((QMouseEvent*)event); break; case QEvent::MouseButtonPress: #if 0 resetInputContext(); #endif mousePressEvent((QMouseEvent*)event); break; ... }
A、構造事件對象
QEvent *event = new QEvent(QEvent::Close);
B、發送事件給指定對象
QApplication::sendEvent(this, event);
A、肯定要對組件的哪些事件進行處理, 如close、key、keyboard 事件
B、重寫對象的 event() 函數
QEvent中的主要成員函數
void ignore();接收者忽略當前事件,事件可能傳遞給父組件
void accept();接收者指望處理當前事件
bool isAccepted();判斷當前事件是否被處理
A、肯定須要過濾處理哪些對象的哪些事件
B、構造本身的事件過濾類: 重寫類的eventFilter函數
C、在主程序中實例化一個過濾類對象
D、調用過濾類對象的installEventFilter(receiver, QEvent *event)函數,
在目標對象上安裝過濾器。
事件過濾器能夠對其餘組件接收到的事件進行監控,任意的QObject對象均可以做爲事件過濾器使用,事件過濾器對象須要重寫eventFilter函數。
組件經過installEventFilter函數安裝事件過濾器,事件過濾器在組件以前接收到事件,可以決定是否將事件轉發到組件對象。
事件過濾器的通常實現:
//返回true,表示事件已經被處理,無須傳遞給obj對象
//返回false,事件正常傳遞都給obj對象
bool Widget::eventFilter(QObject* obj, QEvent* event)
{
if( (根據obj判斷)
{
if(根據event判斷)
{
//事件處理
}
}
//調用父類的eventFilter函數
return QWidget::eventFilter(obj, event);
}
若是一個組件安裝了多個過濾器,則最後一個安裝的會最早調用。
若是在事件過濾器中delete了某個接收組件,務必將返回值設爲true。不然,Qt仍是會將事件分發給已經刪除的接收組件,從而致使程序崩潰。
事件過濾器和被安裝的組件必須在同一線程,不然,過濾器不起做用。另外,若是在install以後,兩個組件到了不一樣的線程,那麼,只有等到兩者從新回到同一線程的時候過濾器纔會有效。
事件的調用最終都會調用QCoreApplication的notify()函數,所以,最大的控制權其實是重寫QCoreApplication的notify()函數。由此能夠看出,Qt的事件處理其實是分層五個層次:重定義事件處理函數,重定義event()函數,爲單個組件安裝事件過濾器,爲QApplication安裝事件過濾器,重定義QCoreApplication的notify()函數。這幾個層次的控制權是逐層增大的。
Qt能夠在程序中自主發送事件,發送事件的方式分爲兩種:
A、阻塞型事件發送
事件發送後須要等待事件處理完成。
bool QCoreApplication::sendEvent ( QObject * receiver, QEvent * event ) [static]
B、非阻塞型事件發送
事件發送後當即完成。
事件被髮送到事件隊列中等待處理。
void QCoreApplication::postEvent ( QObject * receiver, QEvent * event ) [static]
void QCoreApplication::postEvent ( QObject * receiver, QEvent * event, int priority ) [static]
sendEvent()中事件對象的生命期由Qt程序管理,支持分配在棧上和堆上的事件對象;postEvent()中事件對象的生命期由Qt平臺管理,只支持分配在堆上的事件對象,事件被處理後由Qt平臺銷燬。
sendEvent()當即同步處理要發送的event。當sendEvent()返回的時候, 表示相關的事件過濾器或目標對象處理完了event。對於多數的event類, 有一個成員函數 isAccepted() 能夠用來判別event事件是已被接受處理或被拒絕處理。
postEvent()將event提交到一個事件隊列中等待調度。在下一次 Qt 的主 event loop 運行的時候,主 event loop 就會調度全部提交到隊列中的 event, 以某種優化的方式. 例如, 若是有幾個 resize event, 他們就會被壓縮成一個事件. 一樣適用於 paint events: QWidget::update() 調用postEvent(), 以免屢次重畫來避免閃爍以及提升速度.
postEvent()也被用於對象的初始化過程, 由於提交過的 event 一般在相應對象初始化完畢後極短的 時間內就會被調度. 在實現一個控件的時候, 在自定義控件的 constructor 中儘早支持事件機制是很是重要的, 在可能接受到任何事件以前,確保儘早初始化成員變量。
事件和信號不一樣,事件由具體對象進行處理,信號由具體對象產生,改寫事件處理函數可能致使程序行爲發生改變。
void Widget::testSendEvent() { QMouseEvent dbcEvt(QEvent::MouseButtonDblClick, QPoint(0, 0), Qt::LeftButton, Qt::NoButton, Qt::NoModifier); qDebug() << "Before sendEvent()"; //同步執行event事件處理函數 QApplication::sendEvent(this, &dbcEvt); qDebug() << "After sendEvent()"; } void Widget::testPostEvent() { //必須分配在堆空間 QMouseEvent* dbcEvt = new QMouseEvent(QEvent::MouseButtonDblClick, QPoint(0, 0), Qt::LeftButton, Qt::NoButton, Qt::NoModifier); qDebug() << "Before postEvent()"; //放入事件隊列,一部執行event事件處理函數 QApplication::postEvent(this, dbcEvt); qDebug() << "After postEvent()"; } bool Widget::event(QEvent* evt) { if( evt->type() == QEvent::MouseButtonDblClick ) { qDebug() << "event(): " << evt; } return QWidget::event(evt); }
發送事件後,事件會被在事件處理函數被處理。
Qt自定義事件的要點:
A、自定義事件必須繼承自QEvent。
B、自定義事件類必須擁有全局惟一的Type值(自定義事件可使用QEvent::User以後的值,並保證全局惟一)。
C、程序必須提供處理自定義事件對象的方法
#ifndef STRINGEVENT_H #define STRINGEVENT_H #include <QEvent> #include <QString> class StringEvent : public QEvent { QString m_data; public: const static Type TYPE = static_cast<Type>(QEvent::User + 0xFF); explicit StringEvent(QString data = ""): QEvent(TYPE) { m_data = data; } QString data() { return m_data; } }; #endif // STRINGEVENT_H
自定義事件對象的處理方法:
A、將事件過濾器安裝到目標對象
在eventFilter函數中實現自定義事件的處理邏輯
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QLineEdit> #include "StringEvent.h" #include <QMouseEvent> #include <QDebug> #include <QApplication> class Widget : public QWidget { Q_OBJECT QLineEdit m_edit; public: Widget(QWidget *parent = 0): QWidget(parent), m_edit(this) { m_edit.installEventFilter(this); } bool event(QEvent* evt) { if( evt->type() == QMouseEvent::MouseButtonDblClick ) { qDebug() << "event: Before sentEvent"; StringEvent e("D.T.Software"); QApplication::sendEvent(&m_edit, &e); qDebug() << "event: After sentEvent"; } return QWidget::event(evt); } bool eventFilter(QObject* obj, QEvent* evt) { if( (obj == &m_edit) && (evt->type() == StringEvent::TYPE) ) { StringEvent* se = dynamic_cast<StringEvent*>(evt); qDebug() << "Receive: " << se->data(); m_edit.insert(se->data()); return true; } return QWidget::eventFilter(obj, evt); } ~Widget() { } }; #endif // WIDGET_H
B、在目標對象的類中重寫事件處理函數
在event函數中編寫自定義事件的處理邏輯
#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QPushButton> #include "StringEvent.h" #include <QMouseEvent> #include <QDebug> #include <QApplication> class Widget : public QWidget { Q_OBJECT QPushButton m_button; private slots: void onButton() { //槽函數內發送自定義事件 StringEvent e("D.T.Software"); QApplication::sendEvent(this, &e); qDebug() << "clicked button "; } public: Widget(QWidget *parent = 0): QWidget(parent), m_button(this) { m_button.setText("Button"); connect(&m_button, SIGNAL(clicked()), this, SLOT(onButton())); } bool event(QEvent* evt) { //若是事件類型爲自定義事件 if( evt->type() == StringEvent::TYPE ) { qDebug() << "event"; //執行自定義事件的處理函數 return stringEvent(evt); } return QWidget::event(evt); } bool stringEvent(QEvent* evt) { StringEvent* se = dynamic_cast<StringEvent*>(evt); qDebug() << "stringEvent: " << se->data(); return true; } ~Widget() { } }; #endif // WIDGET_H
自定義事件類的適用場合:
A、擴展一個已有組件類的功能
B、開發一個全新功能的組件
C、向第三方的組件類發送消息
拖放一個文件進入窗口時將觸發拖放事件,每個QWidget對象均可以處理拖放事件。
拖放事件的處理函數
void dragEnterEvent(QDragEnterEvent *e)
void dragEvent(QDropEvent *)
拖放事件中的QMimeData,QMimeData是QT中的多媒體數據類,拖放事件經過QMimeData對象傳遞數據,QMimeData支持多種不一樣類型的多媒體數據。
經常使用QMimeData數據處理函數
自定義拖放事件的流程:
A、對接收拖放事件的對象調用setAcceptDrops成員函數
B、重寫dragEnterEvent函數並判斷MIME類型
指望類型數據:e->acceptProposedAction();
其餘數據類型:e->ignore();
C、重寫dragEvent函數並判斷MIME類型
指望數據類型:從事件對象中獲取MIME數據並處理
其餘數據類型:e->ignore();
//在窗口類構造函數中設置拖放事件生效 setAcceptDrops(true); //重寫窗口類的dropEvent函數 void MainWindow::dropEvent(QDropEvent* e) { if( e->mimeData()->hasUrls() ) { QList<QUrl> list = e->mimeData()->urls(); QString path = list[0].toLocalFile(); QFileInfo fi(path); if( fi.isFile() ) { saveOldFile(); if( !m_isTextChanged ) { showFile(path); } } else { showErrorMessage("Cannot open a folder!"); } } else { e->ignore(); } } //重寫窗口類的dragEnterEvent函數 void MainWindow::dragEnterEvent(QDragEnterEvent* e) { if( e->mimeData()->hasUrls() ) { e->acceptProposedAction(); } else { e->ignore(); } }