QT的事件分发、事件过滤器详解
一、事件的流向
QT的各種控件(QObject的子類)都有事件處理成員函數,例如:
bool QObject::event(QEvent *e);//所有事件
dragEnterEvent(QDragEnterEvent *);//拖拽進入事件
focusInEvent(QFocusEvent *);//獲得焦點事件
mousePressEvent(QMouseEvent *);//鼠標壓下事件
····//還有幾十個各種類型的事件,不一一列舉了,任何一個控件的幫助文件里都可以查到
這些事件需要繼承父類重寫覆蓋之后才能使用,這里主要想說明一點,事件的分發方向,是從子控件一步步向上傳遞到祖宗控件的,如果子控件攔截了事件,那么父控件就接收不到事件了。子控件怎么攔截事件,怎么不攔截事件,可以先看這個例子:
添加新類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
二、事件過濾器
考慮一下上面的這種事件流向機制,有何不足?先來看這幾種應用場景:
1、某事件發生了,我想在父控件先處理,處理完再讓子控件處理(和前面所述的流向相反)
2、某些控件已經重寫了一些事件處理函數,我想臨時讓這些事件處理函數失效,待會再恢復
3、Qt 創建了QEvent事件對象之后,會調用QObject的event()函數處理事件的分發。顯然,我們可以在event()函數中實現攔截的操作。由于event()函數是 protected 的,因此,需要繼承已有類。如果組件很多,就需要重寫很多個event()函數。這當然相當麻煩,更不用說重寫event()函數還得小心一堆問題。https://blog.csdn.net/gusgao/article/details/48917427
4、繪制了一個按鈕,按鈕上又覆蓋上一層label,那么這個label會遮住button,使得這個按鈕無法被點擊到。
這些問題都可以用事件過濾器來解決,事件過濾器的作用用一句話來說就是:
任意對象都可以提前攔截其他任意對象的事件,攔截到的事件都會在本對象的eventFilter函數中被接收到,程序員可以決定攔截并處理后是否繼續放行。
舉例:一個按鈕被點擊,本應是按鈕先收到點擊事件,通過事件過濾器,可以讓窗體先收到這個事件,窗體再決定是否把這個事件繼續傳播給按鈕。從事件過濾器的功能來看,不妨叫做:事件監視器、事件攔截器。
假設有兩個對象,分別叫做“老師A”和“學生a”,實現事件過濾器需要做兩個工作:
1、學生a給老師A授權,允許老師查看自己的所有事件;
2、老師A重寫eventFilter函數,對攔截到的學生的事件進行處理;
第1個工作就是一句代碼,例如:
ui->pushButton->installEventFilter(this);//pushButton設置mainwindow作為自己的事件監視器(事件過濾器)
第2個工作就是重寫監視對象A的成員函數:
bool MainWindow::eventFilter(QObject *watched, QEvent *event)//pushButton的所有事件都要先流到這里,這里放行后pushButton才能收到事件
{
? ? if(watched == ui->pushButton)//確認被監視的對象
? ? {
? ? ? ? if(event->type() == QEvent::MouseButtonPress)//確認事件的類型
? ? ? ? {
? ? ? ? ? ? QMouseEvent *e = static_cast<QMouseEvent *>(event);//前面已經確認過事件類型為鼠標類型,所以這里可以放心的進行靜態轉換
? ? ? ? ? ? qDebug()<<"Filter mainwindow MouseButtonPress";
? ? ? ? ? ? if(e->button() == Qt::LeftButton)//鼠標左鍵
? ? ? ? ? ? ? ? return true; //true=攔截
? ? ? ? ? ? else
? ? ? ? ? ? ? ? return false; //false=繼續傳播
?
? ? ? ? }
? ? }
? ? return false; //false=繼續傳播
}
分別單擊鼠標左、右鍵,運行的結果分別為:
?------
可以看到:
1、按鈕并沒有收到左鍵被按下事件,因為這一事件被監視對象給攔截后,沒有放行;
2、按鈕收到了右鍵按下事件,這一事件依次被以下函數執行:監視對象的eventFilter函數-->按鈕的mousePressEvent()函數-->窗口的mousePressEvent()函數
/*******************************
Qt的事件模型一個強大的功能是:一個QObject對象能夠監視發送其他QObject對象的事件,在事件到達之前對其進行處理。
假設我們有一個CustomerInfoDialog控件,由一些QLineEdit控件組成。我們希望使用Space鍵得到下一個QLineEdit的輸入焦點。
一個最直接的方法是繼承QLineEdit重寫keyPressEvent()函數,當點擊了Space鍵時,調用focusNextChild():
void MyLineEdit::keyPressEvent(QKeyEvent *event)
{
? ? if (event->key() == Qt::Key_Space)?
? ? {
? ? ? ? focusNextChild();
? ? }?
? ? else?
? ? {
? ? ? ? QLineEdit::keyPressEvent(event);
? ? }
}
這個方法有一個最大的缺點:如果我們在窗體中使用了很多不同類型的控件(QComboBox,QSpinBox等等),
我們也要繼承這些控件,重寫它們的keyPressEvent()。一個更好的解決方法是讓CustomerInfoDialog監視其子控件的鍵盤事件,
在監視代碼處實現以上功能。這就是事件過濾的方法。實現一個事件過濾包括兩個步驟:
1. ? ? ?在目標對象上調用installEventFilter(),注冊監視對象。
2. ? ? ?在監視對象的eventFilter()函數中處理目標對象的事件。
注冊監視對象的位置是在CustomerInfoDialog的構造函數中:
CustomerInfoDialog::CustomerInfoDialog(QWidget *parent) : QDialog(parent)
{
? ? ...
? ? firstNameEdit->installEventFilter(this);
? ? lastNameEdit->installEventFilter(this);
? ? cityEdit->installEventFilter(this);
? ? phoneNumberEdit->installEventFilter(this);
}
事件過濾器注冊后,發送到firstNameEdit,lastNameEdit,cityEdit,phoneNumberEdit控件的事件首先到達CustomerInfoDialog::eventFilter()函數,然后在到達最終的目的地。
下面是eventFilter()函數的代碼:
bool CustomerInfoDialog::eventFilter(QObject *target, QEvent *event)
{
? ? if (target == firstNameEdit || target == lastNameEdit
? ? ? ? ? ? || target == cityEdit || target == phoneNumberEdit)
? ? {
? ? ? ? if (event->type() == QEvent::KeyPress)
? ? ? ? {
? ? ? ? ? ? QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
? ? ? ? ? ? if (keyEvent->key() == Qt::Key_Space)?
? ? ? ? ? ? {
? ? ? ? ? ? ? ? focusNextChild();
? ? ? ? ? ? ? ? return true;
? ? ? ? ? ? }
? ? ? ? }
? ? }
? ? return QDialog::eventFilter(target, event);
}
首先,我們看是目標控件是否為QLineEdit,如果事件為鍵盤事件,把QEvent轉換為QKeyEvent,確定被敲擊的鍵。如果為Space鍵,調用focusNextChild(),把焦點交給下一個控件,返回true通知Qt已經處理了這個事件,如果返回false,Qt將會把事件傳遞給目標控件,把一個空格字符插入到QLineEdit中。
如果目標控件不是QLineEdit,或者事件不是Space敲擊事件,把控制權交給基類QDialog的eventFilter()。目標控件也可以是基類QDialog正在監視的控件。(在Qt4.1中,QDialog沒有監視的控件,但是Qt的其他控件類,如QScrollArea,監視一些它們的子控件)
Qt的事件處理有5中級別:
1. 重寫控件的事件處理函數:如重寫keyPressEvent(),mousePressEvent()和paintEvent(),這是最常用的事件處理方法,我們已經看到過很多這樣的例子了。
2. ?重寫QObject::event(),在事件到達事件處理函數時處理它。在需要改變Tab鍵的慣用法時這樣做。也可以處理那些沒有特定事件處理函數的比較少見的事件類型(例如,QEvent::HoverEnter)。我們重寫event()時,必須要調用基類的event(),由基類處理我們不需要處理的那些情況。
3. ?給QObject對象安裝事件過濾器:對象用installEventFilter()后,所有達到目標控件的事件都首先到達監視對象的eventFilter()函數。如果一個對象有多個事件過濾器,過濾器按順序激活,先到達最近安裝的監視對象,最后到達最先安裝的監視對象。
4. 給QApplication安裝事件過濾器,如果qApp(唯一的QApplication對象)安裝了事件過濾器,程序中所有對象的事件都要送到eventFilter()函數中。這個方法在調試的時候非常有用,在處理非活動狀態控件的鼠標事件時這個方法也很常用。
5. ?繼承QApplication,重寫notify()。Qt調用QApplication::nofity()來發送事件。重寫這個函數是在其他事件過濾器處理事件前得到所有事件的唯一方法。通常事件過濾器是最有用的,因為在同一時間,可以有任意數量的事件過濾器,但是notify()函數只有一個。
許多事件類型,包括鼠標,鍵盤事件,是能夠傳播的。如果事件在到達目標對象的途中或者由目標對象處理掉,事件處理的過程會重新開始,不同的是這時的目標對象是原目標對象的父控件。這樣從父控件再到父控件,知道有控件處理這個事件或者到達了最頂級的那個控件。
下圖顯示了一個鍵盤事件在一個對話框中從子控件到父控件的傳播過程。當用戶敲擊一個鍵盤,時間首先發送到有焦點的控件上(這個例子中是QCheckBox)。如果QCheckBox沒有處理這個事件,Qt把事件發送到QGroupBox中,如果仍然沒有處理,則最后發送到QDialog中。
/***************************************
事件
在Qt中,事件是作為對象處理的,所有事件對象繼承自抽象類QEvent。此類用來表示程序內部發生或者來自于外部但應用程序應該知道的動作。事件能夠能過被 QObject 的子類接受或者處理,但是通常用在與組件有關的應用中。本文主要闡述了在一個典型應用中的事件接收與處理。
事件的傳遞發送
當一個事件產生時,Qt 通過實例化一個 QEvent 的合適的子類來表示它,然后通過調用 event() 函數發送給 QObject 的實例(或者它的子類)。
event() 函數本身并不會處理事件,根據事件類型,它將調用相應的事件處理函數,并且返回事件被接受還是被忽略。
一些事件,比如 QMouseEvent 和 QKeyEvent,來自窗口系統;有的,比如 QTimerEvent,來自于其他事件源;另外一些則來自應用程序本身。
事件的類型
大部分事件類型有專門的類,比如 QResizeEvent, QPaintEvent, QMouseEvent, QKeyEvent 和 QCloseEvent。它們都是 QEvent 的子類,并且添加了自己特定的事件處理函數。比如 QResizeEvent 事件添加了 size()和 oldSize() 函數,使組件獲知自身大小的改變。
有些事件支持不止一個事件類型。比如 QMouseEvent 鼠標事件,可以表示鼠標的按下,雙擊,移動,以及其它的一些操作。
每一個事件都有其相關聯的類型,由 QEvent::Type 定義。我們能夠很方便地在運行時用這些類型來判斷該事件是哪一個子類。
因為程序響應方式的多樣性和復雜性,Qt 的事件傳遞機制是富有彈性很靈活的。QCoreApplication::notify() 的相關文檔闡述大部分內容;Qt Quarterly 中的文章 Another Look at Events 也進行了簡要描述。在這里我們的闡述對于 95% 的程序而言來說已經足夠了。
事件的處理
通常事件的處理需要調用一個虛函數。比如,QPaintEvent 事件的處理需要調用 QWidget::paintEvent() 函數。這個虛函數負責做出適當的響應,通常是用來重繪組件。如果你在自己的函數中并不打算實現所有的處理,你可以調用基類的實現。
例如,下面的代碼用來處理鼠標左鍵點擊一個自定義的選擇框的操作,而其他的點擊事件則被傳遞給基類 QCheckBox 處理。
void MyCheckBox::mousePressEvent(QMouseEvent *event)
{
? ? if (event->button() == Qt::LeftButton) {
? ? ? ? // handle left mouse button here
? ? } else {
? ? ? ? // pass on other buttons to base class
? ? ? ? QCheckBox::mousePressEvent(event);
? ? }
}
如果你想代替基類的處理,你必須自己實現所有的功能。但是,如果你只想擴展子基類的功能,你只需要實現你自己需要的那部分,剩下的讓基類來替你處理。
少數情況下,Qt 可能沒有指定專門的處理函數,或者指定的處理函數不能滿足要求。通常對 Tab 鍵的處理就會發生這種情況。一般地,Tab 鍵用來移動焦點,但是一些控件需要 Tab 鍵作其它的事情。
這些對象可以通過重新實現 QObject::event() 來滿足需要,它們可以在通用處理調用之前或之后來加入自己的處理,或者完全將事件處理替換為自己的事件處理函數。一個非常罕見的控件或許既要處理 Tab 鍵,又要調用程序特定的事件類型。那么,我們就可以使用以下代碼實現。
bool MyWidget::event(QEvent *event)
{
? ? if (event->type() == QEvent::KeyPress) {
? ? ? ? QKeyEvent *ke = static_cast<QKeyEvent *>(event);
? ? ? ? if (ke->key() == Qt::Key_Tab) {
? ? ? ? ? ? // special tab handling here
? ? ? ? ? ? return true;
? ? ? ? }
? ? } else if (event->type() == MyCustomEventType) {
? ? ? ? MyCustomEvent *myEvent = static_cast<MyCustomEvent *>(event);
? ? ? ? // custom event handling here
? ? ? ? return true;
? ? }
? ? return QWidget::event(event);
}
注意,QWidget::event() 在那些沒有被處理的事件仍然要被調用,并且通過返回值表示事件是否被處理,返回 true 表示事件被阻止發送到其他的對象。
事件過濾器
有時,并不存在一個特定事件函數,或者特定事件功能不足。最普通的例如按下tab鍵。正常情況下,被QWidget看成是去移動 鍵盤焦點,但少數窗口部件需要自行解釋。
讓我們試著設想已經有了一個CustomerInfoDialog的小部件。CustomerInfoDialog 包含一系列QLineEdit. 現在,我們想用空格鍵來代替Tab,使焦點在這些QLineEdit間切換。
一個解決的方法是子類化QLineEdit,重新實現keyPressEvent(),并在keyPressEvent()里調用focusNextChild()。像下面這樣:
void MyLineEdit::keyPressEvent(QKeyEvent *event)?
{?
? ? ?if (event->key() == Qt::Key_Space)
? ? ?{?
? ? ? ? ?focusNextChild();?
? ? ?}
? ? ?else
? ? ?{?
? ? ? ? ?QLineEdit::keyPressEvent(event);?
? ? ?}?
}
但這有一個缺點。如果CustomerInfoDialog里有很多不同的控件(比如QComboBox,QEdit,QSpinBox),我們就必須子類化這么多控件。這是一個煩瑣的任務。
一個更好的解決辦法是: 讓CustomerInfoDialog去管理他的子部件的按鍵事件,實現要求的行為。我們可以使用事件過濾器。
一個事件過濾器的安裝需要下面2個步驟:
1, 調用installEventFilter()注冊需要管理的對象。
2,在eventFilter() 里處理需要管理的對象的事件。
一般,推薦在CustomerInfoDialog的構造函數中注冊被管理的對象。像下面這樣:
CustomerInfoDialog::CustomerInfoDialog(QWidget *parent)
? ? : QDialog(parent)
{
? ? ?... ? ?
? ? ?firstNameEdit->installEventFilter(this);
? ? ?lastNameEdit->installEventFilter(this);
? ? ?cityEdit->installEventFilter(this);
? ? ?phoneNumberEdit->installEventFilter(this);
}
一旦,事件管理器被注冊,發送到firstNameEdit,lastNameEdit,cityEdit,phoneNumberEdit的事件將首先發送到eventFilter()。
下面是一個 eventFilter()函數的實現:
bool CustomerInfoDialog::eventFilter(QObject *target, QEvent *event)?
{?
? ? ?if (target == firstNameEdit || target == lastNameEdit?
? ? ? ? ? ? ?|| target == cityEdit || target == phoneNumberEdit)
? ? ? ? ? ? ?{?
? ? ? ? ?if (event->type() == QEvent::KeyPress)
? ? ? ? ?{?
? ? ? ? ? ? ?QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);?
? ? ? ? ? ? ?if (keyEvent->key() == Qt::Key_Space)
? ? ? ? ? ? ?{?
? ? ? ? ? ? ? ? ?focusNextChild();?
? ? ? ? ? ? ? ? ?return true;?
? ? ? ? ? ? ?}?
? ? ? ? ?}?
? ? ?}?
? ? ?return QDialog::eventFilter(target, event);?
}
在上面的函數中,我們首先檢查目標部件是否是 firstNameEdit,lastNameEdit,cityEdit,phoneNumberEdit。接著,我們判斷事件是否是按鍵事件。如果事件是按鍵事件,我們把事件轉換為QKeyEvent。接著,我們判斷是否按下了空格鍵,如果是,我們調用focusNextChild(),把焦點傳遞給下一個控件。然后,返回,true通知Qt,我們已經處理了該事件。
如果返回false的話,Qt繼續將該事件發送給目標控件,結果是一個空格被插入到QLineEdit中。
如果目標控件不是 QLineEdit,或者按鍵不是空格鍵,我們將把事件傳遞給基類的eventFilter()函數。
Qt提供5個級別的事件處理和過濾:
1,重新實現事件函數。 比如: mousePressEvent(), keyPress-Event(), paintEvent() 。
這是最常規的事件處理方法。
2,重新實現QObject::event().
這一般用在Qt沒有提供該事件的處理函數時。也就是,我們增加新的事件時。
3,安裝事件過濾器
4,在 QApplication 上安裝事件過濾器。
這之所以被單獨列出來是因為: QApplication 上的事件過濾器將捕獲應用程序的所有事件,而且第一個獲得該事件。也就是說事件在發送給其它任何一個event filter之前發送給QApplication的event filter。
5,重新實現QApplication 的 notify()方法.
Qt使用 notify()來分發事件。要想在任何事件處理器捕獲事件之前捕獲事件,唯一的方法就是重新實現QApplication 的 notify()方法。
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(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 )
1
這個函數是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()函數。這幾個層次的控制權是逐層增大的。
/***********************父子窗體事件傳遞與事件過濾**************
處理監控系統的時候遇到問題,在MainWidget中創建多個子Widget的時候,原意是想鼠標點擊先讓MainWidget截獲處理后再分派給子Widget去處理,但調試后發現如果子Widget重新實現了事件方法,就直接處理掉事件了,沒有進到MainWidget的處理方法中去,如果子Widget沒有accept或ignore該事件,則該事件就會被傳遞給其父親,在子Widget存在accept或ignore事件的時候,想要經過一下MainWidget的處理方法,就得用到事件處理器,因此網上找了一下,發現QT的事件處理器可以處理。
QT將事件封裝為QEvent實例以后,會呼叫QObject的event()方法,并且將QEvent實例傳送給它,在某些情況下,希望在執行event()之前,先對一些事件進行處理或過濾,然后再決定是否呼叫event()方法,這時候可以使用事件過濾器。
可以重新定義一個繼承自QObject(或其子類)的類的eventFilter()方法,
bool FilterObject::eventFilter(QObject *object, QEvent *event)
{?? ?
if(event->type() == QEvent::KeyPress)
{?? ? ? ?
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);?? ? ? ?
if (keyEvent->key() == Qt::Key_Tab)?
{
// 處理Tab鍵 ? ? ? ? ??
return true;?? ? ??
}
}?? ?
return false;
}
eventFilter()的object參數表示事件發生的來源物件,eventFilter()若返回false,則安裝該事件過濾器的對象的event()會繼續執行,若返回true,則安裝事件過濾器的對象后event()方法就不會被執行,由此進行事件的攔截處理。給本對象安裝事件過濾器:
this->installEventFilter(this);
Qt事件的類型很多,?常見的qt的事件如下:
鍵盤事件:?按鍵按下和松開.
鼠標事件:?鼠標移動,鼠標按鍵的按下和松開.
拖放事件:?用鼠標進行拖放.
滾輪事件:?鼠標滾輪滾動.
繪屏事件:?重繪屏幕的某些部分.
定時事件:?定時器到時.
焦點事件:?鍵盤焦點移動.
進入和離開事件:?鼠標移入widget之內,或是移出.
移動事件: widget的位置改變.
大小改變事件: widget的大小改變.
顯示和隱藏事件: widget顯示和隱藏.
窗口事件:?窗口是否為當前窗口.
還有一些非常見的qt事件,比如socket事件,剪貼板事件,字體改變,布局改變等等.
Qt的事件和Qt中的signal不一樣.?后者通常用來"使用"widget,?而前者用來"實現" widget.?比如一個按鈕,?我們使用這個按鈕的時候,?我們只關心他clicked()的signal,?至于這個按鈕如何接收處理鼠標事件,再發射這個信號,我們是不用關心的.?但是如果我們要重載一個按鈕的時候,我們就要面對event了.?比如我們可以改變它的行為,在鼠標按鍵按下的時候(mouse press event)?就觸發clicked()的signal而不是通常在釋放的( mouse release event)時候.
事件的產生
事件的兩種來源:
???????一種是系統產生的;通常是window system把從系統得到的消息,比如鼠標按鍵,鍵盤按鍵等,?放入系統的消息隊列中. Qt事件循環的時候讀取這些事件,轉化為QEvent,再依次處理.
???????一種是由Qt應用程序程序自身產生的.程序產生事件有兩種方式,?一種是調用QApplication::postEvent().?例如QWidget::update()函數,當需要重新繪制屏幕時,程序調用update()函數,new出來一個paintEvent,調用QApplication::postEvent(),將其放入Qt的消息隊列中,等待依次被處理.?另一種方式是調用sendEvent()函數.?這時候事件不會放入隊列,?而是直接被派發和處理, QWidget::repaint()函數用的就是這種方式.
事件的調度
兩種調度方式,一種是同步的,?一種是異步.
Qt的事件循環是異步的,當調用QApplication::exec()時,就進入了事件循環.?該循環可以簡化的描述為如下的代碼:
while ( !app_exit_loop ) {
???????while( !postedEvents ) {?????????????processPostedEvents()???????}
???????while( !qwsEvnts ){????????????qwsProcessEvents();???}
???????while( !postedEvents ) {?????????????processPostedEvents()???????}
}
先處理Qt事件隊列中的事件,?直至為空.?再處理系統消息隊列中的消息,?直至為空,?在處理系統消息的時候會產生新的Qt事件,?需要對其再次進行處理.
調用QApplication::sendEvent的時候,?消息會立即被處理,是同步的.?實際上QApplication::sendEvent()是通過調用QApplication::notify(),?直接進入了事件的派發和處理環節.
事件的派發和處理
首先說明Qt中事件過濾器的概念.?事件過濾器是Qt中一個獨特的事件處理機制,?功能強大而且使用起來靈活方便.?通過它,?可以讓一個對象偵聽攔截另外一個對象的事件.?事件過濾器是這樣實現的:?在所有Qt對象的基類: QObject中有一個類型為QObjectList的成員變量,名字為eventFilters,當某個QObjec (qobjA)給另一個QObject (qobjB)安裝了事件過濾器之后, qobjB會把qobjA的指針保存在eventFilters中.?在qobjB處理事件之前,會先去檢查eventFilters列表,?如果非空,?就先調用列表中對象的eventFilter()函數.?一個對象可以給多個對象安裝過濾器.?同樣,?一個對象能同時被安裝多個過濾器,?在事件到達之后,?這些過濾器以安裝次序的反序被調用.?事件過濾器函數( eventFilter() )?返回值是bool型,?如果返回true,?則表示該事件已經被處理完畢, Qt將直接返回,?進行下一事件的處理;?如果返回false,?事件將接著被送往剩下的事件過濾器或是目標對象進行處理.
Qt中,事件的派發是從QApplication::notify()?開始的,?因為QAppliction也是繼承自QObject,?所以先檢查QAppliation對象,?如果有事件過濾器安裝在qApp上,?先調用這些事件過濾器.?接下來QApplication::notify()?會過濾或合并一些事件(比如失效widget的鼠標事件會被過濾掉,?而同一區域重復的繪圖事件會被合并).?之后,事件被送到reciver::event()?處理.
同樣,?在reciver::event()中,?先檢查有無事件過濾器安裝在reciever上.?若有,?則調用之.?接下來,根據QEvent的類型,?調用相應的特定事件處理函數.?一些常見的事件都有特定事件處理函數,?比如:mousePressEvent(), focusOutEvent(),??resizeEvent(), paintEvent(), resizeEvent()等等.?在實際應用中,?經常需要重載這些特定事件處理函數在處理事件.?但對于那些不常見的事件,?是沒有相對應的特定事件處理函數的.?如果要處理這些事件,?就需要使用別的辦法,?比如重載event()?函數,?或是安裝事件過濾器.
事件的轉發
?對于某些類別的事件,?如果在整個事件的派發過程結束后還沒有被處理,?那么這個事件將會向上轉發給它的父widget,?直到最頂層窗口.?如圖所示,?事件最先發送給QCheckBox,?如果QCheckBox沒有處理,?那么由QGroupBox接著處理,?如果QGroupBox沒有處理,?再送到QDialog,?因為QDialog已經是最頂層widget,?所以如果QDialog不處理, QEvent將停止轉發.
如何判斷一個事件是否被處理了呢? Qt中和事件相關的函數通過兩種方式相互通信. QApplication::notify(), QObject::eventFilter(), QObject::event()?通過返回bool值來表示是否已處理. “真”表示已經處理, “假”表示事件需要繼續傳遞.?另一種是調用QEvent::ignore()?或?QEvent::accept()?對事件進行標識.?這種方式只用于event()?函數和特定事件處理函數之間的溝通.?而且只有用在某些類別事件上是有意義的,?這些事件就是上面提到的那些會被轉發的事件,?包括:?鼠標,?滾輪,?按鍵等事件.
實際應用
1.重載特定事件處理函數
最常見的事件處理辦法就是重載象mousePressEvent(), keyPressEvent(), paintEvent()?這樣的特定事件處理函數.?以按鍵事件為例,?一個典型的處理函數如下:
void imageView::keyPressEvent(QKeyEvent * event)
{
switch (event->key()) {
case Key_Plus:
zoomIn();
break;
case Key_Minus:
zoomOut();
break;
case Key_Left:
// …
default:
QWidget::keyPressEvent(event);
}
}
2.重載event()函數
通過重載event()函數,我們可以在事件被特定的事件處理函數處理之前(象keyPressEvent())處理它.?比如,?當我們想改變tab鍵的默認動作時,一般要重載這個函數.?在處理一些不常見的事件(比如:LayoutDirectionChange)時,evnet()也很有用,因為這些函數沒有相應的特定事件處理函數.?當我們重載event()函數時,?需要調用父類的event()函數來處理我們不需要處理或是不清楚如何處理的事件.
下面這個例子演示了如何重載event()函數,?改變Tab鍵的默認動作: (默認的是鍵盤焦點移動到下一個控件上. )
bool CodeEditor::event(QEvent * event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = (QKeyEvent *) event;
if (keyEvent->key() == Key_Tab) {
insertAtCurrentPosition('\t');
return true;
}
}
return QWidget::event(event);
}
3.在QT對象上安裝事件過濾器
安裝事件過濾器有兩個步驟: (假設要用A來監視過濾B的事件)
首先調用B的installEventFilter( const QOject *obj ),?以A的指針作為參數.?這樣所有發往B的事件都將先由A的eventFilter()處理.
?然后, A要重載QObject::eventFilter()函數,?在eventFilter()?中書寫對事件進行處理的代碼.
用這種方法改寫上面的例子: (假設我們將CodeEditor?放在MainWidget中)
MainWidget::MainWidget()
{
???????// …
CodeEditor * ce = new CodeEditor( this, “code editor”);
ce->installEventFilter( this );
// …
}
bool MainWidget::eventFilter( QOject * target , QEvent * event )
{
???????if( target == ce ){
??????????????if( event->type() == QEvent::KeyPress ) {
?????????????????????QKeyEvent *ke = (QKeyEvent *) event;
?????????????????????if( ke->key() == Key_Tab ){
ce->insertAtCurrentPosition('\t');
return true;
?????????????????????}
??????????????}
???????}
???????return false;
}
4.給QAppliction對象安裝事件過濾器
一旦我們給qApp(每個程序中唯一的QApplication對象)裝上過濾器,那么所有的事件在發往任何其他的過濾器時,都要先經過當前這個eventFilter().?在debug的時候,這個辦法就非常有用,?也常常被用來處理失效了的widget的鼠標事件,通常這些事件會被QApplication::notify()丟掉. (?在QApplication::notify()?中,?是先調用qApp的過濾器,?再對事件進行分析,?以決定是否合并或丟棄)
5.繼承QApplication類,并重載notify()函數
Qt是用QApplication::notify()函數來分發事件的.想要在任何事件過濾器查看任何事件之前先得到這些事件,重載這個函數是唯一的辦法.?通常來說事件過濾器更好用一些,?因為不需要去繼承QApplication類.?而且可以給QApplication對象安裝任意個數的事件過濾器,?相比之下, notify()函數只有一個.
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
以上是生活随笔為你收集整理的QT的事件分发、事件过滤器详解的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《 双城记 》:无数的平民拥有的只是和她
- 下一篇: 解决:Caused by: redis.