一、Qt中事件處理的方式
1、事件處理模式一
首先是事件源產生事件,最后是事件處理器對這些事件進行處理。然而也許大家會問,
Qt中有這么多類的事件,我們怎么樣比較簡便的處理每個事件呢?設想,如果是每個事件都對應同一個事件處理器,在該事件處理器中對不同的事件進行分類處理,這樣的弊端有兩點:第一,導致該事件處理器過於臃腫復雜;第二,這樣不便於擴展,當系統新增加事件類型或者是我們需要使用到自定義事件時,就不得不修改Qt的源碼來達到目的。所以Qt設計者的做法是針對不同類型的事件提供不同的事件處理器與之對應。這里又有一個問題了,Qt中是怎么讓不同類型事件與之對應的事件處理器相關聯的呢?我們不難猜想在事件和事件處理器中間必定有一個橋梁。這個橋梁就是QObject::event()函數,該函數是虛函數,QObject的子類例如QWidget都實現了該函數。該函數的主要功能是進行事件的分發,也就是將不同類型的事件與之相對應的事件處理器相關聯,該函數並不對事件進行處理,真正的事件處理是在事件處理器中進行的。
例如:當QWidget產生QPaintEvent事件后,QWidget的event函數會將該事件分發給QWidget::paintEvent()事件處理器,這樣該事件就得到處理了。
以上內容用一個圖形表示就是:
2、事件處理模式二
很多時候,我們只對某些特定的事件比較關心,例如:鼠標單擊或者鍵盤按下等事件。其它的事件我們並不關心它是否發生,也無需對它們進行處理,這個時候最直接的想法就是將這些事件過濾掉,這樣做既可以免去對它們進行處理,也可以避免它們對程序其它部分產生影響。因此,處理模式二中我們引入了事件過濾器這個概念。
如果對象安裝了事件過濾器,則事件在到達目標對象之前先被事件過濾器截獲,進行一些處理之后再交給目標對象,該模式總結為一個圖如下:
注意:這里需要區別對待,如果你是使用installEventFilter()函數給目標對象注冊事件過濾器,那么該事件過濾器只對該目標對象有效,只有該對象的事件需要先傳遞給eventFilter()函數進行過濾,然后調用相應的事件處理器進行處理,非目標對象則不受影響。如果你是給程序中唯一的QApplication對象注冊事件過濾器,那么該過濾器對程序中的每一個對象都有效,任何對象的事件都是先傳給eventFilter()函數,然后再使用事件處理器進行處理。
3、事件處理模式三
Qt調用QApplicaton來發送一個事件。所以我們可以重新實現QApplication中的notify()函數來獲取事件並進行處理,而且使用該函數獲取事件的時間要早於事件過濾器獲取事件的時間。但是使用事件過濾器較為簡便,因為我們可以有多個事件過濾器,但是只能有一個notify()函數。
用一個圖來總結該模式就是:
二、Qt中事件處理的方法
從以上三個處理模式我們可以看出,這是一個不斷完善的過程,從3個模式的討論中我們不難找到可以進行事件處理的地方,而這幾個地方就是我們在編寫程序的時候可以控制事件處理的地方。
1、event()函數
首先是控制事件分發的event()函數,我們可以改寫該函數,改變事件的分發方式,這樣就可以改變事件處理的結果。
2、notify()函數
實現該函數可以截獲事件,並對事件加以處理,但是該方法很少用,這里不做介紹。
3、事件過濾器
實現自己的事件過濾器就可以改變事件處理的方法和結果,這個方法比較常用。
4、事件處理器
事件處理的最后一步,也是最重要的一步就是事件處理器,因為它才是真正進行事件處理的地方,我們可以改寫以有的事件處理器,以此改變已有事件的處理方法和處理結果,我們也可以定義自己的事件類型和相應的事件處理器。
注意:以上四種方法中最常用的是后兩者:事件過濾器和事件處理器。
補充內容:
1、事件的傳遞
包括鼠標和鍵盤事件在內的很多事件都可以被傳遞。如果事件在到達目標對象之前沒有被截獲處理,或者已經傳遞給了它的目標對象但目標對象並沒有進行處理,那么此時,目標對象的父對象將變成新的目標對象,整個事件處理的過程將重復進行,直到該事件被處理或者到達最頂層對象為止。
2、event實例解析
下面的代碼是QWidget::event()函數的代碼,從代碼中可以看出event()函數確實只進行事件的分發而不負責事件的處理。由於函數代碼過多,且都是一類型的用switch語句進行處理的,這里只貼出一部分代碼:
- bool QWidget::event(QEvent *event)
- {
- Q_D(QWidget);
- // ignore mouse events when disabled
- if (!isEnabled()) {
- switch(event->type()) {
- case QEvent::TabletPress:
- case QEvent::TabletRelease:
- case QEvent::TabletMove:
- case QEvent::MouseButtonPress:
- case QEvent::MouseButtonRelease:
- case QEvent::MouseButtonDblClick:
- case QEvent::MouseMove:
- case QEvent::TouchBegin:
- case QEvent::TouchUpdate:
- case QEvent::TouchEnd:
- case QEvent::ContextMenu:
- #ifndef QT_NO_WHEELEVENT
- case QEvent::Wheel:
- #endif
- return false;
- default:
- break;
- }
- }
- 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;
- case QEvent::MouseButtonRelease:
- mouseReleaseEvent((QMouseEvent*)event);
- break;
- case QEvent::MouseButtonDblClick:
- mouseDoubleClickEvent((QMouseEvent*)event);
- break;
- #ifndef QT_NO_WHEELEVENT
- case QEvent::Wheel:
- wheelEvent((QWheelEvent*)event);
- break;
- #endif
- #ifndef QT_NO_TABLETEVENT
- case QEvent::TabletMove:
- case QEvent::TabletPress:
- case QEvent::TabletRelease:
- tabletEvent((QTabletEvent*)event);
- break;
一、事件處理器使用實例
Qt中針對每一種常見的事件類型都提供了相應的事件處理器,我們如果想捕獲某種類型的事件並進行自定義處理,那么只需要實現重寫這些事件處理器就行,至於常見的時間類型和對應的事件處理器如下圖:
在我的程序中,我使用到了鼠標滾輪事件,主要實現的就效果就是大家比較熟悉的:用一個控件顯示圖片,當滾動鼠標滾輪的時候可以調整圖片顯示的大小。
這里我要做的就是捕獲該圖片顯示控件的鼠標滾輪事件,然后改寫該控件的鼠標滾輪滾動事件處理器。
首先我們來看看鼠標滾輪事件以及相應的事件處理器是啥,查看上面的圖即可知道:

在Qt幫助文檔里面我們可以查看到如下語句“The event handler QWidget::wheelEvent() receives wheel events.”。也就是說最后事件處理器會調用wheelEvent()函數來處理該事件,因此我們需要的就是改寫wheelEvent()這個函數。
第一步:在頭文件中申明該函數:
第二步實現wheelEvent()函數(這里主要關注的是事件處理的框架,而不是具體的示例,因此不去過多講解代碼細節):
- <span style="font-size:16px;">void ImageWidget::wheelEvent(QWheelEvent *event)
- {
- int numDegrees = event->delta();
- int num = numDegrees / 120;
- if(num < 0)
- {
- num = 0 - num;
- }
- if (event->orientation() == Qt::Horizontal) {
- //scrollHorizontally(numSteps);
- scale *= 1.25;
- resize(this->scale * this->size());
- } else {
- //scrollVertically(numSteps);
- if(numDegrees > 0)
- {
- //scale *= 0.75;
- resize(num * 0.75 * this->size());
- }
- else if(numDegrees < 0)
- {
- //scale *= 1.25;
- resize(num * 1.25 * this->size());
- }
- }
- event->accept();
- }</span>
到這里這個功能就實現了,大家看看上面的頭文件就知道這里還實現了其它類型事件的處理,其實都是一樣的思路,找到欲處理的事件類型,找到對應的事件處理器,重寫事件處理器中處理事件的方法即可。
二、事件過濾器使用實例
Qt事件模型中一項非常強大的功能就是一個QObject實例可以監視另一個QObject實例中的事件,實現方法是在目標對象中安裝事件過濾器。這里我們接着上面的實例進行,為上面的圖片瀏覽器增加一個功能:在我的程序中設置了一個自動播放的按鍵,點擊按鍵后,在圖片顯示部件中就會顯示一系列圖片,就像動畫播放許多幅圖片一樣,當在上面點擊鼠標左鍵時,就停止動畫播放而是停留在當前播放的圖片;當我再點擊鼠標右鍵的時候,又恢復為動畫播放模式。首先說一下主要的頁面布局:我在一個QMainWindow中放置一個自定義的圖片顯示部件。對照上面的說明,我們很容易知道,實現該功能的方法就是在目標部件(自定義的圖片顯示部件)上注冊事件過濾器,此時的事件過濾器就是我們所說的監視對象,完成這些步驟之后,當目標部件有事件產生后,首先會傳遞給監視對象(事件過濾器)進行處理而不是該事件對應的事件處理器。所以說我們可以截獲事件進行處理。監視對象截獲目標對象的事件后就會調用自己的eventFilter()函數處理這些事件。
總結起來就兩個步驟:
第一:對目標對象調用installEventFilter()來注冊監視對象(事件過濾器);
第二:重寫監視對象的eventFilter()函數處理目標對象的事件。
我們就嚴格按照這兩步走:
首先使用installEventFilter()函數給目標對象注冊事件監聽器:
- <span style="font-size:16px;">//給圖片顯示部件注冊事件過濾器
- imageWidget->installEventFilter(this);</span>
然后就是重寫監視對象eventFilter()函數:
- <span style="font-size:16px;">//圖片顯示部件時間過濾器處理
- bool PMainWindow::eventFilter(QObject *target, QEvent *event)
- {
- if(target == imageWidget)
- {
- qDebug("The imageWidget generate the event!");
- if(event->type() == QEvent::MouseButtonPress)
- {
- QMouseEvent *mouseEvent = static_cast<QMouseEvent *>(event);
- if(mouseEvent->buttons() & Qt::LeftButton)
- {
- qDebug("The Left Button Event!");
- killTimer(timeId);
- }
- else if(mouseEvent->buttons() & Qt::RightButton)
- {
- qDebug("The Right Button Event!");
- //clickNum++;
- //again();
- timeId = startTimer(3000);
- }
- return true;
- }
- }
- //其它部件產生的事件則交給基類處理
- return QMainWindow::eventFilter(target, event);
- }</span>