QT事件處理機制
QT開發(十二)——QT事件處理機制
一、QT事件簡介
QT程式是事件驅動的, 程式的每個動作都是由內部某個事件所觸發。QT事件的發生和處理成為程式執行的主線,存在於程式整個生命週期。
常見的QT事件型別如下:
鍵盤事件: 按鍵按下和鬆開
滑鼠事件: 滑鼠移動,滑鼠按鍵的按下和鬆開
拖放事件: 用滑鼠進行拖放
滾輪事件: 滑鼠滾輪滾動
繪屏事件: 重繪螢幕的某些部分
定時事件: 定時器到時
焦點事件: 鍵盤焦點移動
進入和離開事件: 滑鼠移入widget之內,或是移出
移動事件: widget的位置改變
大小改變事件: widget的大小改變
顯示和隱藏事件: widget顯示和隱藏
視窗事件: 視窗是否為當前視窗
QT將系統產生的訊息轉化為QT事件,QT事件被封裝為物件,所有的QT事件均繼承抽象類QEvent,用於描述程式內部或外部發生的動作,任意的QObject物件都具備處理QT事件的能力。
二、QT事件的產生
1、操作系統產生
作業系統將獲取的事件,比如滑鼠按鍵,鍵盤按鍵等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、呼叫訊號關聯的槽函式
2、QT應用程式程式自己產生
程式產生事件有兩種方式, 一種是呼叫QApplication::postEvent(),例如QWidget::update()函式,當需要重新繪製螢幕時,程式呼叫update()函式,new出來一個paintEvent,呼叫 QApplication::postEvent(),將其放入Qt的訊息佇列中,等待依次被處理;另一種方式是呼叫sendEvent()函式,事件不會放入佇列, 而是直接被派發和處理, QWidget::repaint()函式用的就是阻塞型的。
sendEvent()中事件物件的生命期由Qt程式管理,支援分配在棧上和堆上的事件物件;postEvent()中事件物件的生命期由Qt平臺管理,只支援分配在堆上的事件物件,事件被處理後由Qt平臺銷燬。
3、訊號與事件的區別
A、事件由具體的QWidget(子)物件進行處理,訊號則由具體的QWidget(子)物件生成。
B、改寫事件處理函式可能會導致程式行為發生改變,訊號是否存在對應的槽函式不會改變程式的行為。
C、通常,訊號在具體的事件處理函式中產生。
三、QT事件的處理
1、排程方式
事件有兩種排程方式,同步和非同步。
Qt的事件迴圈是非同步的,當呼叫QApplication::exec()時,就進入了事件迴圈,先處理Qt事件佇列中的事件, 直至為空,再處理系統訊息佇列中的訊息, 直至為空, 處理系統訊息的時候會產生新的Qt事件, 需要對其再次進行處理。
呼叫QApplication::sendEvent的時候, 訊息會立即被處理,是同步的。實際上QApplication::sendEvent()是通過呼叫QApplication::notify(), 直接進入了事件的派發和處理環節。
2、事件的派發和處理
事件過濾器是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() 函式, 或是安裝事件過濾器。
3、事件的轉發
對於某些類別的事件,如果在整個事件的派發過程結束後還沒有被處理, 那麼這個事件將會向上轉發給它的父widget, 直到最頂層視窗。Qt中和事件相關的函式通過兩種方式相互通訊,一種是QApplication::notify(), QObject::eventFilter(), QObject::event()通過返回bool值來表示是否已處理;另一種是呼叫QEvent::ignore() 或 QEvent::accept() 對事件進行標識,只用於event()函式和特定事件處理函式之間的溝通,而且只有用在某些類別事件上是有意義的, 這些事件就是上面提到的那些會被轉發的事件, 包括: 滑鼠, 滾輪, 按鍵等事件。
4、事件的處理、過濾
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事件處理機制
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)
1、進入Qpplication事件迴圈
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
Widget window; // Widget 繼承自QWidget
window.show();
return app.exec(); // 進入Qpplication事件迴圈
}
2、進入QCoreApplication事件迴圈
int QApplication::exec()
{
#ifndef QT_NO_ACCESSIBILITY
QAccessible::setRootObject(qApp);
#endif
//簡單的交給QCoreApplication來處理事件迴圈
return QCoreApplication::exec();
}
3、進入QEventLoop事件佇列迴圈
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;
}
4、進入QEventLoop::processEvents
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;
}
5、事件處理QEventDispatcherWin32::processEvents
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);
}
6、將獲取的事件打包為訊息,傳遞給作業系統
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;
}
7、作業系統將事件發回給QT平臺
extern "C" LRESULT QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
...
//將訊息重新封裝成QEvent的子類QMouseEvent ==> Section 8
result = widget->translateMouseEvent(msg);
...
}
8、將作業系統打包的事件解包、翻譯為QApplication可識別的事件
bool QETWidget::translateMouseEvent(const MSG &msg)
{
......
res = QApplicationPrivate::sendMouseEvent(target, &e, alienWidget, this, &qt_button_down, qt_last_mouse_receiver);
}
9、根據事件型別傳送事件
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;
}
10、傳送事件
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;
}
11、執行緒內事件的處理
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;
}
12、事件的派發
任何執行緒的任何物件的所有事件在傳送時都會呼叫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);
}
13、使用事件過濾器對傳送的事件進行處理
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);
}
14、事件派發至QObject的子類-QWidget
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;
...
}
五、自定義事件與事件處理
1、手動傳送事件的流程
A、構造事件物件
QEvent*event=newQEvent(QEvent::Close);
B、傳送事件給指定物件
QApplication::sendEvent(this,event);
2、定製某個元件的事件處理
A、確定要對元件的哪些事件進行處理,如close、key、keyboard 事件
B、重寫物件的event() 函式
QEvent中的主要成員函式
void ignore();接收者忽略當前事件,事件可能傳遞給父元件
void accept();接收者期望處理當前事件
bool isAccepted();判斷當前事件是否被處理
3、事件過濾流程
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()函式。這幾個層次的控制權是逐層增大的。
4、事件的傳送
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);
}
傳送事件後,事件會被在事件處理函式被處理。
5、傳送自定義事件
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、向第三方的元件類傳送訊息
六、自定義QDropEvent拖放事件例項
1、QDropEvent拖放事件
拖放一個檔案進入視窗時將觸發拖放事件,每一個QWidget物件都可以處理拖放事件。
拖放事件的處理函式
void dragEnterEvent(QDragEnterEvent *e)
void dragEvent(QDropEvent *)
拖放事件中的QMimeData,QMimeData是QT中的多媒體資料類,拖放事件通過QMimeData物件傳遞資料,QMimeData支援多種不同型別的多媒體資料。
常用QMimeData資料處理函式
2、自定義拖放事件
自定義拖放事件的流程:
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();
}
}