事件是由系統或者QT平臺自己在不一樣的時刻發出的。當用戶按下鼠標、敲下鍵盤,或者是窗口須要從新繪製的時候,都會發出一個相應的事件。一些事件在對用戶操做作出響應時發出,如鍵盤事件等;另外一些事件則是由系統自動發出,如計時器事件。安全
事件的出現,使得程序代碼不會按照原始的線性順序執行。線性順序的程序設計風格不適合處理複雜的用戶交互,如用戶交互過程當中,用戶點擊「打開文件」將開始執行打開文件的操做,用戶點擊「保存文件」將開始執行保存文件的操做。用戶交互過程當中進行什麼樣的操做是由用戶決定的,程序設計時沒法事先預測,同時用戶的操做都會發出相應的事件,所以用戶交互的程序設計中程序的執行順序再也不是線性的,而是由一個個事件驅動着程序繼續執行,若是沒有事件,程序將阻塞,不執行任何代碼。app
QT中,使用QT組件時,一般不會把主要精力放在事件上。更可能是關心事件關聯的信號。好比,對於QPushButton的鼠標點擊,不須要關心這個鼠標點擊事件,而是關心clicked()信號的發出。但QT中的事件和信號槽卻並非能夠相互替代的。信號由具體的對象發出,信號一旦發出,而後會立刻交給由connect()函數鏈接的槽進行處理;而對於事件,QT使用一個事件隊列對全部發出的事件進行維護,當新的事件產生時,會被追加到事件隊列的尾部,前一個事件完成後,取出後面的事件進行處理,而且QT的事件也能夠不進入事件隊列,而是直接處理。事件則可使用「事件過濾器」進行過濾,對於有些事件進行額外的處理,其它的事件則不關心。異步
一般,若是使用組件,關心的是信號槽;若是自定義組件,關心的是事件。由於能夠經過事件來改變組件的默認操做。好比,若是要自定義一個可以響應鼠標事件的EventLabel,就須要重寫QLabel的鼠標事件,作出指望的操做,有可能還得在恰當的時候發出一個相似按鈕的clicked()信號(若是指望讓EventLabel可以被其它組件使用)或者其它的信號。ide
QT程序須要在main()函數建立一個QCoreApplication對象,而後調用exec()函數。exec()函數就是開始QT的事件循環。執行exec()函數後,程序將進入事件循環來監聽應用程序的事件。當事件發生時,QT將建立一個事件對象。QT中全部事件類都繼承於QEvent。在事件對象建立完畢後,QT將建立的事件對象傳遞給QObject的event()函數。event()函數並不直接處理事件,而是按照事件對象的類型分派給特定的事件處理函數(event handler)。函數
在全部組件的父類QWidget中,定義了不少事件處理的回調函數,如keyPressEvent()、keyReleaseEvent()、mouseDoubleClickEvent()、mouseMoveEvent()、mousePressEvent()、mouseReleaseEvent()等。若是要在自定義組件中對事件進行處理,須要在子類中從新實現事件處理函數。post
QWidget組件有一個mouseTracking屬性,用於設置是否追蹤鼠標。只有鼠標被追蹤時,mouseMoveEvent()纔會發出。若是mouseTracking是 false(默認),QWidget組件在至少一次鼠標點擊後纔可以被追蹤,即在鼠標點擊後纔可以發出mouseMoveEvent()事件。若是設置mouseTracking爲true,則mouseMoveEvent()直接能夠被髮出。ui
事件處理包括事件接受和忽略。this
實例代碼:spa
CustomButton.h文件:線程
#ifndef CUSTOMBUTTON_H #define CUSTOMBUTTON_H #include <QPushButton> #include <QDebug> class CustomButton : public QPushButton { public: CustomButton(QWidget* parent = 0); protected: void mousePressEvent(QMouseEvent *event); private slots: void onButton(); }; #endif // CUSTOMBUTTON_H
CustomButton.cpp文件:
#include "CustomButton.h" #include <QMouseEvent> CustomButton::CustomButton(QWidget* parent):QPushButton(parent) { connect(this, &CustomButton::clicked, this, &CustomButton::onButton); } void CustomButton::onButton() { qDebug() << "child clicked"; } void CustomButton::mousePressEvent(QMouseEvent *event) { if(event->button() == Qt::LeftButton) { qDebug() << "Left press"; } else { QPushButton::mousePressEvent(event); } }
Mainc.cpp文件:
#include "CustomButton.h" #include <QApplication> int main(int argc, char *argv[]) { QApplication a(argc, argv); CustomButton button; button.setText("button"); button.show(); return a.exec(); }
自定義類CustomButton繼承自QPushButton,重寫了mousePressEvent()函數,即鼠標按下事件處理函數。mousePressEvent()函數中,若是鼠標按下的是左鍵,則打印「Left press」字符串,不然,調用父類的同名函數。槽函數onButton()打印「child clicked」。
編譯運行程序,鼠標點擊按鈕後打印出「Left press」字符串,沒有打印出「child clicked」字符串。
緣由在於自定義類CustomButton的事件處理函數mousePressEvent()覆蓋了父類QPushButton相應的事件處理函數。父類QPushButton的mousePressEvent()事件處理函數會發出clicked()信號,自定義類CustomButton實現則不發出clicked()信號。所以,自定義類CustomButton對象在鼠標按下時沒有發出clicked()信號,鏈接的槽函數onButton()不會執行。
自定義類中重寫父類的事件處理函數時會覆蓋父類相應的事件處理函數,父類的事件處理函數中包含的操做(如發出某些信號)也將會被覆蓋。所以當重寫事件回調函數時,必須注意是否須要經過調用父類的同名函數來確保原有實現仍能進行。
經過調用父類的同名函數,能夠把QT的事件傳遞當作鏈狀:若是子類沒有處理這個事件,就會繼續向其父類傳遞。Qt 的事件對象有兩個函數:accept()和ignore()。accept()通知QT平臺,事件處理函數要處理這個事件;ignore()則通知QT平臺,事件處理函數不處理這個事件。在事件處理函數中,可使用isAccepted()來查詢某個事件是否是已經被接受。
若是一個事件處理函數調用了一個事件對象的accept()函數,這個事件就不會被繼續傳播給其父組件;若是事件對象調用了事件的ignore()函數,QT平臺會從其父組件中尋找另外的接受者。
一般不多會使用accept()和ignore()函數,若是但願忽略事件,只要調用父類的相應事件處理函數便可。因爲沒法確認父類中相應的事件處理函數有沒有額外的操做,若是在子類中直接使用ignore()函數忽略事件,QT會去尋找其餘的接受者,父類的操做會被忽略(由於沒有調用父類的同名函數),這可能會有潛在的危險。爲了不子類去調用accept()和ignore()函數,而是儘可能調用父類實現,QT作了特殊的設計:事件對象默認是accept的,而做爲全部組件的父類QWidget的默認實現則是調用ignore()。所以,若是子類從新實現事件處理函數,不調用QWidget的默認實現,就等於接受事件;若是要忽略事件,只需調用QWidget的默認事件處理函數實現。
QT5中QWidget的mousePressEvent()函數的源碼實現以下:
void QWidget::mousePressEvent(QMouseEvent *event) { event->ignore(); if ((windowType() == Qt::Popup)) { event->accept(); QWidget* w; while ((w = QApplication::activePopupWidget()) && w != this) { w->close(); if (QApplication::activePopupWidget() == w) w->hide(); // hide at least } if (!rect().contains(event->pos())) { close(); } } }
若是子類沒有重寫mousePressEvent()函數,QT會默認忽略這個事件,繼續尋找下一個事件接收者。若是在子類的mousePressEvent()函數中直接調用了accept()或者ignore(),而沒有調用父類相應的事件處理函數,QWidget::mousePressEvent()函數中關於Popup判斷的代碼就不會被執行,所以可能會出現莫名其妙的怪異現象。
CustomButton.h文件:
#ifndef CUSTOMBUTTON_H #define CUSTOMBUTTON_H #include <QPushButton> class CustomButton : public QPushButton { Q_OBJECT public: CustomButton(QWidget* parent = 0); protected: void mousePressEvent(QMouseEvent *event); }; #endif // CUSTOMBUTTON_H
CustomButton.cpp文件:
#include "CustomButton.h" #include <QMouseEvent> #include <QDebug> CustomButton::CustomButton(QWidget* parent):QPushButton(parent) { } void CustomButton::mousePressEvent(QMouseEvent *event) { event->ignore(); qDebug() << "CustomButton"; }
CustomButtonEx.h文件:
#ifndef CUSTOMBUTTONEX_H #define CUSTOMBUTTONEX_H #include "CustomButton.h" class CustomButtonEx : public CustomButton { Q_OBJECT public: CustomButtonEx(QWidget* parent = 0); protected: void mousePressEvent(QMouseEvent *event); }; #endif // CUSTOMBUTTONEX_H
CustomButtonEx.cpp文件:
#include "CustomButtonEx.h" #include "CustomWidget.h" #include <QMouseEvent> #include <QDebug> CustomButtonEx::CustomButtonEx(QWidget* parent):CustomButton(parent) { } void CustomButtonEx::mousePressEvent(QMouseEvent *event) { event->ignore(); qDebug() << "CustomButtonEx"; }
CustomWidget.h文件:
#ifndef CUSTOMWIDGET_H #define CUSTOMWIDGET_H #include <QWidget> class CustomWidget : public QWidget { Q_OBJECT public: explicit CustomWidget(QWidget *parent = 0); protected: void mousePressEvent(QMouseEvent *event); }; #endif // CUSTOMWIDGET_H
CustomWidget.cpp文件:
#include "CustomWidget.h" #include <QMouseEvent> #include <QDebug> CustomWidget::CustomWidget(QWidget *parent) : QWidget(parent) { } void CustomWidget::mousePressEvent(QMouseEvent *event) { qDebug() << "CustomWidget"; }
Main.cpp文件:
#include "CustomButton.h" #include "CustomButtonEx.h" #include "CustomWidget.h" #include <QApplication> #include <QHBoxLayout> int main(int argc, char *argv[]) { QApplication a(argc, argv); CustomWidget* customWidget = new CustomWidget(); CustomButton* custombutton = new CustomButton(customWidget); custombutton->setText("CustomBuuton"); CustomButtonEx* custombuttonex = new CustomButtonEx(customWidget); custombuttonex->setText("CustomButtonEx"); QHBoxLayout* layout = new QHBoxLayout(customWidget); layout->addWidget(custombutton); layout->addWidget(custombuttonex); customWidget->setLayout(layout); customWidget->show(); return a.exec(); }
在自定義組件CustomWidget內放置兩個按鈕對象:CustomButton和CustomButtonEx,每個類都重寫了mousePressEvent()函數。
若是在CustomButtonEx的mousePressEvent()中調用event->accept(),鼠標按下事件將不會傳播到父組件CustomWidget,只會打印出「CustomButtonEx」;若是CustomButtonEx的mousePressEvent()中調用event->ignore(),鼠標按下事件會繼續傳播到父組件CustomWidget,會打印出「CustomButtonEx」、「CustomWidget」。因爲CustomWidget的mousePressEvent()函數中並未調用event->accept(),所以鼠標按下事件傳播到CustomWidget組件爲止。
CustomButtonEx的事件傳播給了父組件CustomWidget,而不是父類CustomButton。事件的傳播是在組件層次上面的,而不是依靠類繼承機制。
在窗口關閉事件處理函數closeEvent必須使用accept()和ignore()函數。對於窗口關閉QCloseEvent事件,調用accept()意味着QT會中止事件的傳播,窗口關閉;調用ignore()則意味着事件繼續傳播,即阻止窗口關閉。
void CustomWidget::closeEvent(QCloseEvent *event) { bool exit = QMessageBox::question(this, tr("Quit"), tr("Are you sure to quit this application?"), QMessageBox::Yes | QMessageBox::No, QMessageBox::No) == QMessageBox::Yes; if (exit) { event->accept(); } else { event->ignore(); } }
事件對象建立完畢後,QT將這個事件對象傳遞給QObject的event()函數。event()函數並不直接處理事件,而是將這些事件對象按照它們不一樣的類型分發給不一樣的事件處理器(event handler)。
event()函數主要用於事件的分發。若是指望在事件分發前作一些操做,能夠重寫event()函數。
在QWidget組件中監聽某個按鍵按下事件實現以下:
bool CustomWidget::event(QEvent *event) { if (event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); if (keyEvent->key() == Qt::Key_K) { qDebug() << "You press K."; return true; } } return QWidget::event(event); }
CustomWidget繼承自QWidget,重寫了event()函數,event()函數有一個QEvent對象做爲參數,是須要轉發的事件對象。函數返回值是 bool類型。若是傳入的事件已被識別而且處理,則須要返回 true,不然返回 false。若是返回值是 true,而且事件對象設置了accept(),那麼QT會認爲這個事件已經處理完畢,不會再將這個事件發送給其它組件,而是會繼續處理事件隊列中的下一事件。注意,在event()函數中,調用事件對象的accept()和ignore()函數是沒有做用的,不會影響到事件的傳播。
經過使用QEvent::type()函數能夠檢查事件的實際類型,其返回值是QEvent::Type類型的枚舉。處理完本身感興趣的事件後,能夠直接返回 true,表示已經對此事件進行了處理;對於其它不關心的事件,則須要調用父類的event()函數繼續轉發,不然自定義組件就只能處理咱們感興趣的事件,其它事件將被丟棄。
QT5 中QObject::event()函數的源代碼以下:
bool QObject::event(QEvent *e) { switch (e->type()) { case QEvent::Timer: timerEvent((QTimerEvent*)e); break; case QEvent::ChildAdded: case QEvent::ChildPolished: case QEvent::ChildRemoved: childEvent((QChildEvent*)e); break; // ... default: if (e->type() >= QEvent::User) { customEvent(e); break; } return false; } return true; }
QT使用QEvent::type()判斷事件類型,調用特定的事件處理器實現事件的分發。好比,若是event->type()返回值是QEvent::Timer,則調用timerEvent()函數。
QT建立了QEvent事件對象後,會調用QObject的event()函數處理事件的分發。雖然能夠在event()函數中實現攔截的操做,但event()函數有兩大缺點。
A、event()函數是 protected的,須要繼承已有類。若是組件不少,就須要重寫不少個event()函數。
B、event()函數雖然能夠攔截事件,但其實組件是接收到了事件的。
爲了解決event()函數攔截事件的缺陷,QT提供了另一種方式來實現對事件的攔截:事件過濾器。
QObject有一個eventFilter()函數,用於創建事件過濾器。
virtual bool QObject::eventFilter(QObject * watched, QEvent * event);
事件過濾器會檢查接收到的事件。若是這個事件是感興趣的類型,就進行處理;若是不是,就繼續轉發。eventFilter函數返回一個bool類型,若是想將參數event過濾出來,好比不想讓它繼續轉發,就返回true,不然返回false。事件過濾器的調用時間是目標對象(watched對象)接收到事件對象前。若是在事件過濾器中中止了某個事件,那麼watched對象以及之後全部的事件過濾器都不會知道這個事件。
bool CustomWidget::eventFilter(QObject *watched, QEvent *event) { if (watched == this && event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event); if (keyEvent->key() == Qt::Key_K) { qDebug() << "eventFilter: You press K."; return true; } else { return false; } } return false; }
自定義類CustomWidget重寫了eventFilter()函數。爲了過濾特定組件上的事件,首先須要判斷這個組件對象是否是感興趣的組件,而後判斷要過濾事件的類型。若是是要過濾的事件則直接返回true,即過濾掉這個事件,其餘事件仍是要繼續處理,因此返回false。對於其它的組件,因爲不保證是否是還有過濾器,最保險的辦法是調用父類的eventFilter()函數,保證父對象上面設置的事件過濾器能夠被調用。
eventFilter()函數至關於建立了過濾器,要使過濾器生效須要安裝過濾器。安裝過濾器須要調用QObject::installEventFilter()函數。
void QObject::installEventFilter (QObject * filterObj);
filterObj是過濾器對象,即事件過濾器所屬的類對象。
CustomWidget::CustomWidget(QWidget *parent) : QWidget(parent) { this->installEventFilter(this); }
建立事件過濾器並安裝事件過濾器後,在CustomWidget按下鼠標時,鼠標事件將會被過濾,所以會打印出「eventFilter: You press K.」字符串,mousePressEvent()函數將不會被調用,不會打印出「CustomWidget」字符串。
eventFilter()函數是QObject的一個成員函數,所以,任意QObject均可以做爲事件過濾器(若是沒有重寫eventFilter()函數,事件過濾器是沒有任何做用的,由於默認什麼都不會過濾)。已經存在的過濾器則能夠經過QObject::removeEventFilter()函數移除。
能夠向一個對象上面安裝多個事件處理器,只要調用屢次installEventFilter()函數。若是一個對象存在多個事件過濾器,那麼,最後一個安裝的會第一個執行,也就是後進先執行的順序。
事件過濾器的強大之處在於能夠爲整個應用程序添加一個事件過濾器。installEventFilter()函數是QObject的函數,QApplication或者QCoreApplication對象都是QObject的子類,所以,能夠向QApplication或者QCoreApplication添加事件過濾器。
若是使用installEventFilter()函數給一個對象安裝事件過濾器,那麼該事件過濾器只對該對象有效,只有這個對象的事件須要先傳遞給事件過濾器的eventFilter()函數進行過濾,其它對象不受影響。若是給QApplication對象安裝事件過濾器,那麼該過濾器對程序中的每個對象都有效,任何對象的事件都是先傳給eventFilter()函數。這種全局的事件過濾器將會在全部其它特性對象的事件過濾器以前調用。儘管很強大,但這種行爲會嚴重下降整個應用程序的事件分發效率。所以,除非是不得不使用的狀況,不然的話不該該這麼作。
注意,若是在事件過濾器中delete了某個接收組件,務必將函數返回值設爲 true。不然,QT仍是會將事件分發給這個接收組件,從而致使程序崩潰。
事件過濾器和被安裝過濾器的組件必須在同一線程,不然,過濾器將不起做用。若是在安裝過濾器以後,兩個組件到了不一樣的線程,那麼,只有等到兩者從新回到同一線程的時候過濾器纔會有效。QT中,對象建立以後可使用moveToThread()函數將一個對象移動到另外的線程。
QT事件處理的層次:
A、重寫paintEvent()、mousePressEvent()等事件處理函數,是最普通、最簡單的形式,同時功能也最簡單。
B、重寫event()函數。event()函數是全部對象的事件入口,QObject和QWidget中的實現,默認是把事件傳遞給特定的事件處理函數。
C、在特定對象上面安裝事件過濾器,事件過濾器僅過濾該對象接收到的事件。
D、在QCoreApplication::instance()上安裝事件過濾器。事件過濾器將過濾全部對象的全部事件。全局的事件過濾器能夠看到disabled組件上面發出的鼠標事件。全局過濾器只能用在主線程。
E、重寫QCoreApplication::notify()函數。與全局事件過濾器同樣,提供徹底控制,而且不受線程的限制。但全局範圍內只能有一個被使用(由於QCoreApplication是單例的)。
CustomWidget.h文件:
#ifndef CUSTOMWIDGET_H #define CUSTOMWIDGET_H #include <QWidget> class CustomWidget : public QWidget { Q_OBJECT public: explicit CustomWidget(QWidget *parent = 0); protected: void mousePressEvent(QMouseEvent *event); void closeEvent(QCloseEvent *event); bool event(QEvent *event); bool eventFilter(QObject *watched, QEvent *event); }; #endif // CUSTOMWIDGET_H
CustomWidget.cpp文件:
#include "CustomWidget.h" #include <QMouseEvent> #include <QDebug> CustomWidget::CustomWidget(QWidget *parent) : QWidget(parent) { this->installEventFilter(this); } void CustomWidget::mousePressEvent(QMouseEvent *event) { qDebug() << "CustomWidget::mousePressEvent"; } bool CustomWidget::event(QEvent *event) { if (event->type() == QEvent::MouseButtonPress) { qDebug() << "CustomWidget::event"; } return QWidget::event(event); } bool CustomWidget::eventFilter(QObject *watched, QEvent *event) { if (event->type() == QEvent::MouseButtonPress) { qDebug() << "CustomWidget::eventFilter"; } return false; }
Main.cpp文件:
#include "CustomWidget.h" #include <QApplication> #include <QDebug> class EventFilter : public QObject { public: EventFilter(QObject *watched, QObject *parent = 0) : QObject(parent), m_watched(watched) { } bool eventFilter(QObject *watched, QEvent *event) { if (watched == m_watched) { if (event->type() == QEvent::MouseButtonPress) { qDebug() << "QApplication::eventFilter"; } } return false; } private: QObject *m_watched; }; int main(int argc, char *argv[]) { QApplication a(argc, argv); CustomWidget customWidget; a.installEventFilter(new EventFilter(&customWidget, &customWidget)); customWidget.show(); return a.exec(); }
按下鼠標鍵後,打印出字符串:
QApplication::eventFilter
CustomWidget::eventFilter
CustomWidget::event
CustomWidget::mousePressEvent
全局事件過濾器被第一個調用,組件對象上的事件過濾器第二個被調用,event()函數第三個調用,特定的事件處理函數第四個調用。
事件的分發既能夠是同步的,又能夠是異步的,而信號槽的回調老是同步的。而且事件可使用過濾器。
QT自定義事件須要繼承QEvent。QEvent提供一個QEvent::Type類型的參數,做爲自定義事件的類型值。
QEvent::Type是QEvent定義的一個枚舉。須要注意的是自定義事件類型不能和已經存在的type值重複,不然會有不可預料的錯誤發生,由於系統會將新增長的自定義事件當作系統事件進行派發和調用。QT中,系統保留0 – 999的值,自定義事件的type要大於 999。QT定義了兩個邊界值:QEvent::User和QEvent::MaxUser,自定義事件的type應該在兩個值的範圍之間。其中,QEvent::User的值是1000,QEvent::MaxUser的值是65535。經過這兩個枚舉值,能夠保證自定義的事件類型不會覆蓋系統定義的事件類型。但並不能保證自定義事件相互之間不會被覆蓋。爲了不自定義事件間的相互覆蓋,QT提供了一個函數:registerEventType(),用於自定義事件的註冊。
static int QEvent::registerEventType ( int hint = -1 );
registerEventType函數是static的,可使用QEvent類直接調用。函數返回值是向系統註冊的新的Type類型的值。若是hint是合法的,即hint不會發生任何覆蓋(系統的以及其它自定義事件的),則會直接返回這個值;不然,系統會自動分配一個合法值並返回。使用registerEventType函數便可完成type 值的指定。registerEventType函數是線程安全的,沒必要另外添加同步。
能夠在自定義事件中添加所須要的數據,而後進行事件的發送。
QT提供了兩種事件發送方式:
A、非阻塞式發送
static bool QCoreApplication::sendEvent(QObject *receiver,QEvent *event);
直接將event事件發送給receiver接收者,使用的是QCoreApplication::notify()函數。函數返回值就是事件處理函數的返回值。在事件被髮送的時候,event對象並不會被銷燬。一般會在棧上建立event對象,例如:
QMouseEvent event(QEvent::MouseButtonPress, pos, 0, 0, 0);
QApplication::sendEvent(receiver, &event);
B、阻塞式發送
static void QCoreApplication::postEvent(QObject *receiver,QEvent *event);
將event事件及其接收者receiver一同追加到事件隊列中,函數當即返回。
由於post事件隊列會持有事件對象,而且在其post的時候將其delete掉,所以,必須在堆上建立event對象。當對象被髮送以後,再試圖訪問event對象就會出現問題(由於post後,event對象就會被delete)。
當控制權返回到主線程循環時,保存在事件隊列中的全部事件都經過notify()函數發送出去。
事件會根據post的順序進行處理。若是想要改變事件的處理順序,能夠考慮爲其指定一個優先級。默認的優先級是Qt::NormalEventPriority。
postEvent函數是線程安全的。
static void QCoreApplication::sendPostedEvents(QObject *receiver,int event_type);
sendPostedEvents函數的做用是將事件隊列中的接收者爲receiver,事件相似爲event_type的全部事件當即發送給receiver進行處理。須要注意的是,來自窗口系統的事件並不禁sendPostedEvents函數進行處理,而是processEvent()。
自定義事件的處理既能夠定義一個自定義事件處理函數,也能夠在event()函數中直接處理。
void CustomWidget::customEvent(QEvent *event) { CustomEvent *customEvent = static_cast<CustomEvent *>(event); // ... } bool CustomWidget::event(QEvent *event) { if (event->type() == CustomEventType) { CustomEvent *myEvent = static_cast<CustomEvent *>(event); // processing... return true; } return QWidget::event(event); }