1. 程式人生 > >Qt學習筆記:Qt 事件機制

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)
{

}