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