QT類 Qevent事件處理過程 事件過濾器
QT類 Qevent事件處理過程 事件過濾器 是本文要介紹的內容,我們直接進入內容。
事件處理流程:
某個事件發生------>exec()循環會接收到這個事件------>
創建一個事件對象,并將對象傳遞給QObject::event()------>
在QWidget::event()函數中,分配給特定的事件處理函數------>
在QButton的事件處理函數中emit(clicked消息)
前面說到了事件的作用,下面來看看我們如何來接收事件?;貞浺幌虑懊娴拇a,我們在子類中重寫了事件函數,以便讓這些子類按照我們的需要完成某些功能,就像下面的代碼:
- void MyLabel::mousePressEvent(QMouseEvent * event)
- {
- if(event->button() == Qt::LeftButton) {
- // do something
- } else {
- QLabel::mousePressEvent(event);
- }
- }
上面的代碼和前面類似,在鼠標按下的事件中檢測,如果按下的是左鍵,做我們的處理工作,如果不是左鍵,則調用父類的函數。這在某種程度上說,是把事件向上傳遞給父類去響應,也就是說,我們在子類中“忽略”了這個事件。
我們可以把Qt的事件傳遞看成鏈狀:如果子類沒有處理這個事件,就會繼續向其他類傳遞。其實,Qt的事件對象都有一個accept()函數和ignore()函數。正如它們的名字,前者用來告訴Qt,事件處理函數“接收”了這個事件,不要再傳遞;后者則告訴Qt,事件處理函數“忽略”了這個事件,需要繼續傳遞,尋找另外的接受者。在事件處理函數中,可以使用isAccepted()來查詢這個事件是不是已經被接收了。
事實上,我們很少使用accept()和ignore()函數,而是想上面的示例一樣,如果希望忽略事件,只要調用父類的響應函數即可。記得我們曾經說過,Qt中的事件大部分是protected的,因此,重寫的函數必定存在著其父類中的響應函數,這個方法是可行的。為什么要這么做呢?因為我們無法確認父類中的這個處理函數沒有操作,如果我們在子類中直接忽略事件,Qt不會再去尋找其他的接受者,那么父類的操作也就不能進行,這可能會有潛在的危險。另外我們查看一下QWidget的mousePressEvent()函數的實現:
- void QWidget::mousePressEvent(QMouseEvent *event)
- {
- event->ignore();
- if ((windowType() == Qt::Popup)) {
- event->accept();
- QWidget* w;
- while ((w = qApp->activePopupWidget()) && w != this){
- w->close();
- if (qApp->activePopupWidget() == w) // widget does not want to dissappear
- w->hide(); // hide at least
- }
- if (!rect().contains(event->pos())){
- close();
- }
- }
- }
請注意第一條語句,如果所有子類都沒有覆蓋mousePressEvent函數,這個事件會在這里被忽略掉,這暗示著這個組件不關心這個事件,這個事件就可能被傳遞給其父組件。
不過,事情也不是絕對的。在一個情形下,我們必須使用accept()和ignore()函數,那就是在窗口關閉的時候。如果你在窗口關閉時需要有個詢問對話框,那么就需要這么去寫:
- void MainWindow::closeEvent(QCloseEvent * event)
- {
- if(continueToClose()) {
- event->accept();
- } else {
- event->ignore();
- }
- }
- bool MainWindow::continueToClose()
- {
- if(QMessageBox::question(this,
- tr("Quit"),
- tr("Are you sure to quit this application?"),
- QMessageBox::Yes | QMessageBox::No,
- QMessageBox::No)
- == QMessageBox::Yes) {
- return true;
- } else {
- return false;
- }
- }
這樣,我們經過詢問之后才能正常退出程序
今天要說的是event()函數。記得之前曾經提到過這個函數,說在事件對象創建完畢后,Qt將這個事件對象傳遞給QObject的event()函數。event()函數并不直接處理事件,而是將這些事件對象按照它們不同的類型,分發給不同的事件處理器(event handler)。
event()函數主要用于事件的分發,所以,如果你希望在事件分發之前做一些操作,那么,就需要注意這個event()函數了。為了達到這種目的,我們可以重寫event()函數。例如,如果你希望在窗口中的tab鍵按下時將焦點移動到下一組件,而不是讓具有焦點的組件處理,那么你就可以繼承QWidget,并重寫它的event()函數,已達到這個目的:
- bool MyWidget::event(QEvent *event) {
- if (event->type() == QEvent::KeyPress) {
- QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
- if (keyEvent->key() == Qt::Key_Tab) {
- // 處理Tab鍵
- return true;
- }
- }
- return QWidget::event(event);
- }
event()函數接受一個QEvent對象,也就是需要這個函數進行轉發的對象。為了進行轉發,必定需要有一系列的類型判斷,這就可以調用QEvent的type()函數,其返回值是QEvent::Type類型的枚舉。我們處理過自己需要的事件后,可以直接return回去,對于其他我們不關心的事件,需要調用父類的event()函數繼續轉發,否則這個組件就只能處理我們定義的事件了。
event()函數返回值是bool類型,如果傳入的事件已被識別并且處理,返回true,否則返回false。如果返回值是true,QApplication會認為這個事件已經處理完畢,會繼續處理事件隊列中的下一事件;如果返回值是false,QApplication會嘗試尋找這個事件的下一個處理函數。
event()函數的返回值和事件的accept()和ignore()函數不同。accept()和ignore()函數用于不同的事件處理器之間的溝通,例如判斷這一事件是否處理;event()函數的返回值主要是通知QApplication的notify()函數是否處理下一事件。為了更加明晰這一點,我們來看看QWidget的event()函數是如何定義的:
- bool QWidget::event(QEvent *event) {
- switch (e->type()) {
- case QEvent::KeyPress:
- keyPressEvent((QKeyEvent *)event);
- if (!((QKeyEvent *)event)->isAccepted())
- return false;
- break;
- case QEvent::KeyRelease:
- keyReleaseEvent((QKeyEvent *)event);
- if (!((QKeyEvent *)event)->isAccepted())
- return false;
- break;
- // more...
- }
- return true;
- }
QWidget的event()函數使用一個巨大的switch來判斷QEvent的type,并且分發給不同的事件處理函數。在事件處理函數之后,使用這個事件的isAccepted()方法,獲知這個事件是不是被接受,如果沒有被接受則event()函數立即返回false,否則返回true。
另外一個必須重寫event()函數的情形是有自定義事件的時候。如果你的程序中有自定義事件,則必須重寫event()函數以便將自定義事件進行分發,否則你的自定義事件永遠也不會被調用。
創建事件過濾器和安裝事件過濾器
Qt創建了QEvent事件對象之后,會調用QObject的event()函數做事件的分發。有時候,你可能需要在調用event()函數之前做一些另外的操作,比如,對話框上某些組件可能并不需要響應回車按下的事件,此時,你就需要重新定義組件的event()函數。如果組件很多,就需要重寫很多次event()函數,這顯然沒有效率。為此,你可以使用一個事件過濾器,來判斷是否需要調用event()函數。
QOjbect有一個eventFilter()函數,用于建立事件過濾器。這個函數的簽名如下:
- virtual bool QObject::eventFilter ( QObject * watched, QEvent * event )
如果watched對象安裝了事件過濾器,這個函數會被調用并進行事件過濾,然后才輪到組件進行事件處理。在重寫這個函數時,如果你需要過濾掉某個事件,例如停止對這個事件的響應,需要返回true。
- 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建立了一個事件過濾器。為了過濾某個組件上的事件,首先需要判斷這個對象是哪個組件,然后判斷這個事件的類型。例如,我不想讓textEdit組件處理鍵盤事件,于是就首先找到這個組件,如果這個事件是鍵盤事件,則直接返回true,也就是過濾掉了這個事件,其他事件還是要繼續處理,所以返回false。對于其他組件,我們并不保證是不是還有過濾器,于是最保險的辦法是調用父類的函數。
在創建了過濾器之后,下面要做的是安裝這個過濾器。安裝過濾器需要調用installEventFilter()函數。這個函數的簽名如下:
- void QObject::installEventFilter ( QObject * filterObj )
這個函數是QObject的一個函數,因此可以安裝到任何QObject的子類,并不僅僅是UI組件。這個函數接收一個QObject對象,調用了這個函數安裝事件過濾器的組件會調用filterObj定義的eventFilter()函數。例如,textField.installEventFilter(obj),則如果有事件發送到textField組件是,會先調用obj->eventFilter()函數,然后才會調用textField.event()。
當然,你也可以把事件過濾器安裝到QApplication上面,這樣就可以過濾所有的事件,已獲得更大的控制權。不過,這樣做的后果就是會降低事件分發的效率。
如果一個組件安裝了多個過濾器,則最后一個安裝的會最先調用,類似于堆棧的行為。
注意,如果你在事件過濾器中delete了某個接收組件,務必將返回值設為true。否則,Qt還是會將事件分發給這個接收組件,從而導致程序崩潰。
事件過濾器和被安裝的組件必須在同一線程,否則,過濾器不起作用。另外,如果在install之后,這兩個組件到了不同的線程,那么,只有等到二者重新回到同一線程的時候過濾器才會有效。
事件的調用最終都會調用QCoreApplication的notify()函數,因此,最大的控制權實際上是重寫QCoreApplication的notify()函數。由此可以看出,Qt的事件處理實際上是分層五個層次:重定義事件處理函數,重定義event()函數,為單個組件安裝事件過濾器,為QApplication安裝事件過濾器,重定義QCoreApplication的notify()函數。這幾個層次的控制權是逐層增大的。
小結:關于QT類 Qevent事件處理過程 事件過濾器 的內容介紹按了,希望本文對你有所幫助。