有時候,對象須要查看、甚至要攔截髮送到另外對象的事件。例如,對話框可能想要攔截按鍵事件,不讓別的組件接收到;或者要修改回車鍵的默認處理。框架
經過前面的章節,咱們已經知道,Qt 建立了QEvent事件對象以後,會調用QObject的event()函數處理事件的分發。顯然,咱們能夠在event()函數中實現攔截的操做。因爲event()函數是 protected 的,所以,須要繼承已有類。若是組件不少,就須要重寫不少個event()函數。這固然至關麻煩,更不用說重寫event()函數還得當心一堆問題。好在 Qt 提供了另一種機制來達到這一目的:事件過濾器。函數
QObject有一個eventFilter()函數,用於創建事件過濾器。函數原型以下:this
virtual bool QObject::eventFilter ( QObject * watched, QEvent * event );spa
這個函數正如其名字顯示的那樣,是一個「事件過濾器」。所謂事件過濾器,能夠理解成一種過濾代碼。事件過濾器會檢查接收到的事件。若是這個事件是咱們感興趣的類型,就進行咱們本身的處理;若是不是,就繼續轉發。這個函數返回一個 bool 類型,若是你想將參數 event 過濾出來,好比,不想讓它繼續轉發,就返回 true,不然返回 false。事件過濾器的調用時間是目標對象(也就是參數裏面的watched對象)接收到事件對象以前。也就是說,若是你在事件過濾器中中止了某個事件,那麼,watched對象以及之後全部的事件過濾器根本不會知道這麼一個事件。線程
咱們來看一段簡單的代碼:對象
class MainWindow : public QMainWindow繼承
{事件
public:開發
MainWindow();rem
protected:
bool eventFilter(QObject *obj, QEvent *event);
private:
QTextEdit *textEdit;
};
MainWindow::MainWindow()
{
textEdit = new QTextEdit;
setCentralWidget(textEdit);
textEdit->installEventFilter(this);
}
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == textEdit) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
qDebug() << "Ate key press" << keyEvent->key();
return true;
} else {
return false;
}
} else {
// pass the event on to the parent class
return QMainWindow::eventFilter(obj, event);
}
}
MainWindow是咱們定義的一個類。咱們重寫了它的eventFilter()函數。爲了過濾特定組件上的事件,首先須要判斷這個對象是否是咱們感興趣的組件,而後判斷這個事件的類型。在上面的代碼中,咱們不想讓textEdit組件處理鍵盤按下的事件。因此,首先咱們找到這個組件,若是這個事件是鍵盤事件,則直接返回 true,也就是過濾掉了這個事件,其餘事件仍是要繼續處理,因此返回 false。對於其它的組件,咱們並不保證是否是還有過濾器,因而最保險的辦法是調用父類的函數。
eventFilter()函數至關於建立了過濾器,而後咱們須要安裝這個過濾器。安裝過濾器須要調用QObject::installEventFilter()函數。函數的原型以下:
void QObject::installEventFilter ( QObject * filterObj )
這個函數接受一個QObject *類型的參數。記得剛剛咱們說的,eventFilter()函數是QObject的一個成員函數,所以,任意QObject均可以做爲事件過濾器(問題在於,若是你沒有重寫eventFilter()函數,這個事件過濾器是沒有任何做用的,由於默認什麼都不會過濾)。已經存在的過濾器則能夠經過QObject::removeEventFilter()函數移除。
咱們能夠向一個對象上面安裝多個事件處理器,只要調用屢次installEventFilter()函數。若是一個對象存在多個事件過濾器,那麼,最後一個安裝的會第一個執行,也就是後進先執行的順序。
還記得咱們前面的那個例子嗎?咱們使用event()函數處理了 Tab 鍵:
bool CustomWidget::event(QEvent *e)
{
if (e->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
if (keyEvent->key() == Qt::Key_Tab) {
qDebug() << "You press tab.";
return true;
}
}
return QWidget::event(e);
}
如今,咱們能夠給出一個使用事件過濾器的版本:
bool FilterObject::eventFilter(QObject *object, QEvent *event)
{
if (object == target && event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Tab) {
qDebug() << "You press tab.";
return true;
} else {
return false;
}
}
return false;
}
事件過濾器的強大之處在於,咱們能夠爲整個應用程序添加一個事件過濾器。記得,installEventFilter()函數是QObject的函數,QApplication或者QCoreApplication對象都是QObject的子類,所以,咱們能夠向QApplication或者QCoreApplication添加事件過濾器。這種全局的事件過濾器將會在全部其它特性對象的事件過濾器以前調用。儘管很強大,但這種行爲會嚴重下降整個應用程序的事件分發效率。所以,除非是不得不使用的狀況,不然的話咱們不該該這麼作。
注意,事件過濾器和被安裝過濾器的組件必須在同一線程,不然,過濾器將不起做用。另外,若是在安裝過濾器以後,這兩個組件到了不一樣的線程,那麼,只有等到兩者從新回到同一線程的時候過濾器纔會有效。
Qt 的事件是整個 Qt 框架的核心機制之一,也比較複雜。說它複雜,更可能是由於它涉及到的函數衆多,而處理方法也不少,有時候讓人難以選擇。如今咱們簡單總結一下 Qt 中的事件機制。
Qt 中有不少種事件:鼠標事件、鍵盤事件、大小改變的事件、位置移動的事件等等。如何處理這些事件,實際有兩種選擇:
l 全部事件對應一個事件處理函數,在這個事件處理函數中用一個很大的分支語句進行選擇,其表明做就是 win32 API 的WndProc()函數:
LRESULT CALLBACK WndProc(HWND hWnd,
UINT message,
WPARAM wParam,
LPARAM lParam)
在這個函數中,咱們須要使用switch語句,選擇message參數的類型進行處理,典型代碼是:
switch(message)
{
case WM_PAINT:
// ...
break;
case WM_DESTROY:
// ...
break;
...
}
l 每一種事件對應一個事件處理函數。Qt 就是使用的這麼一種機制:
n mouseEvent()
n keyPressEvent()
n …
Qt 具備這麼多種事件處理函數,確定有一個地方對其進行分發,不然,Qt 怎麼知道哪種事件調用哪個事件處理函數呢?這個分發的函數,就是event()。顯然,當QMouseEvent產生以後,event()函數將其分發給mouseEvent()事件處理器進行處理。
event()函數會有兩個問題:
l event()函數是一個 protected 的函數,這意味着咱們要想重寫event(),必須繼承一個已有的類。試想,個人程序根本不想要鼠標事件,程序中全部組件都不容許處理鼠標事件,是否是我得繼承全部組件,一一重寫其event()函數?protected 函數帶來的另一個問題是,若是我基於第三方庫進行開發,而對方沒有提供源代碼,只有一個連接庫,其它都是封裝好的。我怎麼去繼承這種庫中的組件呢?
l event()函數的確有必定的控制,不過有時候個人需求更嚴格一些:我但願那些組件根本看不到這種事件。event()函數雖然能夠攔截,但其實也是接收到了QMouseEvent對象。我連讓它收都收不到。這樣作的好處是,模擬一種系統根本沒有那個事件的效果,因此其它組件根本不會收到這個事件,也就無需修改本身的事件處理函數。這種需求怎麼辦呢?
這兩個問題是event()函數沒法處理的。因而,Qt 提供了另一種解決方案:事件過濾器。事件過濾器給咱們一種能力,讓咱們可以徹底移除某種事件。事件過濾器能夠安裝到任意QObject類型上面,而且能夠安裝多個。若是要實現全局的事件過濾器,則能夠安裝到QApplication或者QCoreApplication上面。這裏須要注意的是,若是使用installEventFilter()函數給一個對象安裝事件過濾器,那麼該事件過濾器只對該對象有效,只有這個對象的事件須要先傳遞給事件過濾器的eventFilter()函數進行過濾,其它對象不受影響。若是給QApplication對象安裝事件過濾器,那麼該過濾器對程序中的每個對象都有效,任何對象的事件都是先傳給eventFilter()函數。
事件過濾器能夠解決剛剛咱們提出的event()函數的兩點不足:
l 首先,事件過濾器不是 protected 的,所以咱們能夠向任何QObject子類安裝事件過濾器;
l 其次,事件過濾器在目標對象接收到事件以前進行處理,若是咱們將事件過濾掉,目標對象根本不會見到這個事件。
事實上,還有一種方法,咱們沒有介紹。Qt 事件的調用最終都會追溯到QCoreApplication::notify()函數,所以,最大的控制權其實是重寫QCoreApplication::notify()。這個函數的聲明是:
virtual bool QCoreApplication::notify ( QObject * receiver,
QEvent * event );
該函數會將event發送給receiver,也就是調用receiver->event(event),其返回值就是來自receiver的事件處理器。注意,這個函數爲任意線程的任意對象的任意事件調用,所以,它不存在事件過濾器的線程的問題。不過咱們並不推薦這麼作,由於notify()函數只有一個,而事件過濾器要靈活得多。
如今咱們能夠總結一下 Qt 的事件處理,其實是有五個層次:
l 重寫paintEvent()、mousePressEvent()等事件處理函數。這是最普通、最簡單的形式,同時功能也最簡單。
l 重寫event()函數。event()函數是全部對象的事件入口,QObject和QWidget中的實現,默認是把事件傳遞給特定的事件處理函數。
l 在特定對象上面安裝事件過濾器。該過濾器僅過濾該對象接收到的事件。
l 在QCoreApplication::instance()上面安裝事件過濾器。該過濾器將過濾全部對象的全部事件,所以和notify()函數同樣強大,可是它更靈活,由於能夠安裝多個過濾器。全局的事件過濾器能夠看到 disabled 組件上面發出的鼠標事件。全局過濾器有一個問題:只能用在主線程。
l 重寫QCoreApplication::notify()函數。這是最強大的,和全局事件過濾器同樣提供徹底控制,而且不受線程的限制。可是全局範圍內只能有一個被使用(由於QCoreApplication是單例的)。