Qt學習筆記:Qt 事件機制
一、Qt中的事件處理
1. 在Qt中,事件被封裝成一個個物件,所有的事件均繼承自抽象類QEvent. 事件處理的核心包括事件①產生、②分發、③接受和處理。
- 事件的產生:
誰來產生事件? 最容易想到的是我們的輸入裝置,比如鍵盤、滑鼠產生的keyPressEvent,keyReleaseEvent,mousePressEvent,mouseReleaseEvent事件(他們被封裝成QMouseEvent和QKeyEvent)。 - Qt中事件的分發:
對於non-GUI的Qt程式,是由QCoreApplication負責將QEvent分發給QObject的子類-Receiver.
對於Qt GUI程式,由QApplication來負責 - 事件的接受和處理:
QObject類 是整個Qt物件模型的心臟,事件處理機制是QObject三大職責(記憶體管理、內省(intropection)與事件處理制)之一。任何一個想要接受並處理事件的物件均須繼承自QObject,可以選擇過載QObject::event()函式或事件的處理權轉給父類。
2. Qt平臺將系統產生的訊息轉變成Qt事件:
Qt 程式在main()函式建立一個QApplication物件,然後呼叫它的exec()函式。這個函式就是開始 Qt 的事件迴圈。在執行exec()函式之後,程式將進入事件迴圈來監聽應用程式的事件。當事件發生時,Qt 將建立一個事件物件。Qt 中所有事件類都繼承於QEvent。在事件物件建立完畢後,Qt 將這個事件物件傳遞給QObject的event()函式。event()函式並不直接處理事件,而是將這些事件物件按照它們不同的型別,分發給不同的事件處理器(event handler)。如上所述,event()函式主要用於事件的分發。
3. GUI應用程式的事件處理方式:
- Qt事件產生後會立即被分發到QWidget物件(QObject的子類,如按鍵QPushButton物件等)
- QWidget物件其內部會有一個event(QEVent*)函式被呼叫,進行事件處理
- event()根據事件型別呼叫不同的事件處理函式(預設的子函式)
- 在事件處理函式中傳送Qt中預定義的訊號
- 呼叫訊號關聯的槽函式
二、事件分發、事件過濾器:
1. 事件的傳遞:
如果事件在目標物件上得不到處理,事件向上一層進行傳播,直到最頂層的widget為止。
如果得到事件的物件,呼叫了accept(),則事件停止繼續傳播;如果呼叫了ignore(),事件向上一級繼續傳播。
Qt對自定義事件處理函式的預設返回值是accept(),但預設的事件處理函式是ingore()。
因此,如果要繼續向上傳播,呼叫QWidget的預設處理函式即可。到此為止的話,不必顯式呼叫accept()。
新增新類QPushButtonEx,它的父類為QPushButton,QPushButtonEx 覆蓋 QPushButton 的滑鼠按下事件:
void QButtonEx::mousePressEvent(QMouseEvent *e)
{
qDebug()<<"button pressed";
e->ignore();//ignore的作用是使事件繼續向父控制元件傳遞
}
在一個MainWindow上新增一個QPushButton(提升為QPushButtonEx),也重寫MainWindow的滑鼠按下事件:
void MainWindow::mousePressEvent(QMouseEvent *e)
{
qDebug()<<"mainwindow pressed";
e->ignore();
}
點選按鈕,執行結果如下:
列印輸出的結果驗證了傳遞方向,子控制元件先接收到事件,父控制元件後接收到事件。尤其注意程式碼中的e->ignore();這句的作用是使得事件能夠繼續流向父控制元件;與之相反的是e->accept();它將事件攔截,父控制元件將無法收到已經被accept過的事件;
重寫的事件處理函式中,如果不寫accept或ignore,那麼預設為:事件被accept!
上面的程式碼為例,如果QButtonEx::mousePressEvent()函式中不寫e->ignore(),或者寫e->accept(),那麼MainWindow::mousePressEvent()函式將不會被觸發,執行結果中只能看到列印:button pressed
因此,自定義Button控制元件繼承自QPushbutton,然後重寫滑鼠事件mousePressEvent。會發現在父類中呼叫該自定義的button,沒有發出clicked()訊號。
原因是:當重寫完成以後,需要在每個事件中新增:
QPushButton::mousePressEvent(e);
//重寫滑鼠釋放事件
void myButton::mouseReleaseEvent(QMouseEvent*e)
{
if(e->button()==Qt::RightButton)
{
m_menu.exec(QCursor::pos());
}
//保留原有QPushButton點選事件
QPushButton::mouseReleaseEvent(e);//不新增該句會造成不釋放clicked()訊號。
}
2. 事件過濾器:
Qt建立QEvent事件物件後,會呼叫QObject的event()函式來分發事件。
但有時,你可能需要在呼叫event()函式之前做一些自己的操作,比如,對話方塊上某些元件可能並不需要響應回車鍵按下的事件,此時,你就需要重新定義元件的event()函式。
如果元件很多,就需要重寫很多次event()函式,這顯然沒有效率。為此,你可以使用一個事件過濾器,來判斷是否需要呼叫元件的event()函式。
QOjbect有一個eventFilter()函式,用於建立事件過濾器。這個函式的宣告如下:
virtual bool QObject::eventFilter (QObject * watched, QEvent * event)
如果watched物件安裝了事件過濾器,這個函式會被呼叫並進行事件過濾,
然後才輪到元件進行事件處理。在重寫這個函式時,如果你需要過濾掉某個事件,例如停止對這個事件的響應,需要返回true。
在建立了過濾器之後,下面要做的是安裝這個過濾器。安裝過濾器需要呼叫installEventFilter()函式。這個函式的宣告如下:
void QObject::installEventFilter ( QObject * filterObj)
這個函式是QObject的一個函式,因此可以安裝到任何QObject的子類,並不僅僅是UI元件。
這個函式接收一個QObject物件,呼叫了這個函式安裝事件過濾器的元件會呼叫filterObj定義的eventFilter()函式。
例如,textField->installEventFilter(obj),則如果有事件傳送到textField元件時,會先呼叫obj->eventFilter()函式,然後才會呼叫textField->event()。
當然,你也可以把事件過濾器安裝到QApplication上面,這樣就可以過濾所有的事件,已獲得更大的控制權。不過,這樣做的後果就是會降低事件分發的效率。
我們可以把Qt的事件傳遞看成鏈狀:如果子類沒有處理這個事件,就會繼續向其他類傳遞。其實,Qt的事件物件都有一個accept()函式和ignore()函式,用於“接受”和“忽略”。
具體可參考該圖:
例項:
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; //返回true --> 將鍵盤事件過濾
} else {
return false;//繼續處理其他事件
}
} else {
// pass the event on to the parent class
return QMainWindow::eventFilter(obj, event);////呼叫父類函式
}
}
上面的例子中為MainWindow建立了一個事件過濾器。為了過濾某個元件上的事件,首先需要判斷這個物件是哪個元件,然後判斷這個事件的型別。例如,我不想讓textEdit元件處理鍵盤事件,於是就首先找到這個元件,如果這個事件是鍵盤事件,則直接返回true,也就是過濾掉了這個事件,其他事件還是要繼續處理,所以返回false。對於其他元件,我們並不保證是不是還有過濾器,於是最保險的辦法是呼叫父類的函式。
總結起來使用事件過濾器就兩個步驟:
第一:對目標物件呼叫installEventFilter()來註冊監視物件(事件過濾器);
第二:重寫監視物件的eventFilter()函式處理目標物件的事件。
即:
(1). 目標物件 -> installEventFilter( 監控物件);
目標物件將事件傳遞給監控物件處理
(2). bool 監控物件::eventFilter(QObject *watched,QEvent *event)
{
}