qt事件原理

Qt事件原理

 

Qt 事件原理

本文爲原創文章,轉載請註明出處,或註明轉載自“黃邦勇帥(原名:黃勇)

若對C++語法不熟悉,建議參閱《C++語法詳解》一書,電子工業出版社出版,該書語法示例短小精悍,對查閱C++知識點相當方便,並對語法原理進行了透徹、深入詳細的講解,可確保讀者徹底弄懂C++的原理,徹底解惑C++,使其知其然更知其所以然。此書是一本全面瞭解C++不可多得的案頭必備圖書。

2.6.1 QApplication、QGuiApplication、QCoreApplication簡介

繼承順序爲QObject、QCoreApplication、QGuiApplication、QApplication(左側爲頂級父類)
一個程序中只能有一個QCoreApplication及其子類的對象,該類主要提供無GUI程序的事件循環。QGuiApplication用於管理GUI程序的控制流和主要設置。
QApplication類專門爲QGuiApplication提供基於QWidget的程序所需的一些功能,主要用於處理部件的初始化、最終化,主要職責如下:
 使用用戶的桌面設置初始化應用程序。
 執行事件處理,也就是說該類能從底層系統接收並分發事件。比如,使用QCoreApplication::sendEvent()或QCoreApplication::postEvent()函數分發自定義事件。
 解析常用命令行參數並設置其內部狀態。
 定義了應用程序的界面外觀,可使用QApplication::setStyle()進行更改。
 指定應用程序如何分配顏色。
 使用QCoreApplication::translate()函數對字符串進行轉換。
 通過QApplication::desktop()函數處理桌面,通過QCoreApplication::clipboard()函數處理剪貼板。
 管理應用程序的鼠標光標。比如使用QGuiApplication::setOverrideCuresor()函數設置光標等。

2.6.2 Qt對事件的描述及分類

事件是由程序內部或外部產生的事情或某種動作的通稱。比如用戶按下鍵盤或鼠標,就會產生一個鍵盤事件或鼠標事件(這是由程序外部產生的事件);再如,當窗口第一次顯示時,會產生一個繪製事件,以通知窗口需要重新繪製其自身,從而使該窗口可見(這是由程序內部產生的事件)。
事件和信號的區別
 他們兩個是不同的概念,不要弄混淆。信號是由對象產生的,而事件則不一定是由對象產生的(比如由鼠標產生的事件),事件通常來自底層的窗口系統,但也可以手動發送自定義的事件,可見信號和事件的來源是不同的。
 事件既可以同步使用,也可以異步使用 (取決於調用sendEvent()還是 postEvents()),而使用信號和槽總是同步的。事件的另一個好處是可以被過濾。
Qt中使用QEvent及其子類來描述事件(其繼承關係見圖2-7),比如QMouseEvent類用於描述與鼠標相關的事件,QKeyEvent描述了與鍵盤相關的事件等
根據事件的來源和傳遞方式,事件可分爲以下三大類
 自發事件:這是由窗口系統生成的,這些事件置於系統隊列中,並由事件循環一個接一個地處理。
 發佈的事件(Posted events):該類事件由Qt或應用程序生成,這些事件由Qt排隊,並由事件循環處理。
 發送的事件(Sent events):該類事件由Qt或應用程序生成,這些事件直接發送到目標對象,不經過事件循環處理。
事件還可被細分爲很多種類型(有一百多種),每一種類型使用QEvent類中的枚舉常量進行表示,比如QMouseEvent管理的鼠標事件有鼠標雙擊、移動、按下等類型,這些類型分別使用QEvent::Type枚舉類型中的枚舉常量MouseButtonDblClick、MouseMove、MouseButtonPress表示。所有的類型分類請查閱幫助文檔。可使用函數 Type QEvent::type() const;獲取事件的類型
使用Qt編程幾乎不需考慮事件,因爲當產生某種事件時,Qt窗口部件都會發射一個相應的信號(即Qt會把事件轉換爲一個對應的信號),比如按鈕被按下時,會產生一個MouseButtonPress事件,Qt會處理這一事件,並且會發射一個clicked()單擊信號,程序員可以直接處理clicked()信號,而不必處理底層的事件。
我們不需要知道Qt是怎樣把事件轉換爲QEvent或其子類類型的對象的,程序員只需要知道怎樣傳遞和處理這些事件即可。比如對於按下鼠標事件,不需要知道Qt是怎樣把該事件轉換爲QMouseEvent類型的對象的,只需要知道怎樣傳遞和處理該事件即可。
2.6.3 事件的傳遞(或分發)及處理

1、事件傳遞步驟(見圖2-8):
基本規則:若事件未被目標對象處理,則把事件傳遞給其父對象處理,若父對象仍未處理,則再傳遞給父對象的父對象處理,重複這個過程,直至這個事件被處理或到達頂級對象爲止。注意:事件是在對象間傳遞的,這裏是指對象的父子關係,而不是指類的父子關係。
在Qt中有一個事件循環,該循環負責從可能產生事件的地方捕獲各種事件,並把這些事件轉換爲帶有事件信息的對象,然後由Qt的事件處理流程分發給需要處理事件的對象來處理事件。
通過調用QCoreApplication::exec()函數啓動事件主循環。主循環從事件隊列中獲取事件,然後創建一個合適的QEvent對象或其子類類型的對象來表示該事件,在此步驟中,事件循環首先處理所有發佈的事件,直到隊列爲空,然後處理自發的事件,最後處理在自發事件期間產生的已發佈事件。注意:發送的事件不由事件循環處理,該類事件會被直接傳遞給對象。
然後,Qt會調用QCoreApplication::notify()函數對事件進行傳遞(或分發)
最後,QObject對象調用QObject::event()函數接收事件,
 event()函數是一個虛函數,是QObject對象處理事件的入口,
 在QObject的子類中通常會重寫event()函數,比如QWidget類就重寫了event()函數。
 event()函數與事件處理函數的關係:event()函數負責把事件傳遞給目標對象並調用對應的事件處理函數處理事件,比如調用QWidget::keyPressEvent()函數處理鍵盤按下事件等,注意,QObject中的event()函數並不能實現該功能,這是通過QObject的子類中重寫的event()函數實現的,比如調用QWidget::keyPressEvent()函數,就是由在QWidget類中重寫的event()函數來完成的。
 event()函數對大多數事件都調用了默認的處理函數,但並不能包括全部的事件,因此,有時我們需要重寫該函數,這也是我們處理Qt事件的方式之一。
注意:event()函數不會處理事件,他只是根據事件的類型進行事件的傳遞(或分發),若返回true則表示這個事件被接受並進行了處理,否則事件未被處理,需進行進一步傳遞或丟棄。
在這裏插入圖片描述
2、事件的處理
任何QObject的子類都可以處理事件,QEvent及其子類雖然可以描述一個事件,但並不對事件進行處理。
事件是由類的成員函數(通常爲虛函數)處理的,通常把處理事件的成員函數稱爲事件處理函數。由此可見,處理事件需要兩個要求:處理該事件的對象和該對象中用於處理該事件的事件處理函數。
Qt爲絕大多數常用類型的事件提供了默認的事件處理函數,並作了默認處理,這些事件處理函數位於QObject的子類中,比如QWidget::mousePressEvent()函數用於處理鼠標按下事件,QWidget::keyPressEvent()用於處理鍵盤按下事件等。對這些默認事件處理函數的調用是通過QObject::event()虛函數進行的,因此若默認的事件處理函數不能滿足用戶要求(比如對Tab鍵的處理),則可以通過重新實現event()函數來達到目的。下面爲QWidget::event()的部分源代碼(qwidget.cpp)

bool QWidget::event(QEvent *event){
……
switch (event->type()) {
……
//調用QWidget類的默認事件處理函數
case QEvent::MouseButtonPress:     mousePressEvent((QMouseEvent*)event);	 break;
case QEvent::MouseButtonRelease:   mouseReleaseEvent((QMouseEvent*)event);	break;
……		} 
…… }

3、由事件傳遞過程可見,Qt可以使用以下5種方式來處理事件
①、重新實現各部件內部默認的事件處理函數,比如重新實現paintEvent()、mousePressEvent()等事件處理函數,這是最常用、最簡單的方式
②、重新實現QObject::event()函數(需繼承QObject類),使用該方式,可以在事件到達默認的事件處理函數之前捕獲到該事件。該方式常被用來處理Tab鍵的默認意義。在重新實現event()函數時,必須對未明確處理的事件調用基類的event()函數,比如,若子類化QWidget,並重寫了event()函數,但未調用父類的event()函數,則程序可能會不能正常顯示界面。
③、在QObject對象上安裝(或稱爲註冊)事件過濾器(見後文)。對象一旦使用installEventFilter()註冊,傳遞給目標對象的所有事件都會先傳遞給這個監視對象的eventFilter()函數。或同一對象上安裝了多個事件處理器,則按照安裝的逆序依次激活這些事件處理器。
④、在QApplication上安裝(或稱爲註冊)事件過濾器。該方式與重新實現notify函數一樣強大。一旦在QApplication對象(程序中只有一個這種類型的對象)上註冊了事件過濾器,則程序中每個對象的每個事件都會在發送到其他事件過濾器之前,發送到這個eventFilter()函數。該方式對於調試非常有用。
⑤、子類化QApplication,並重新實現QCoreApplication::notify()函數,由事件傳遞過程可知,該函數提供了對事件的完全控制,因此功能非常強大,所以重寫該函數會很複雜,也因此此方法很少被使用。這是唯一能在事件過濾器之前捕獲到事件的方式。
注意:exec()、notify()和event()函數都不會處理事件,他們只是根據事件的類型進行事件的傳遞(或分發)。
示例2.17:事件處理的方式

#include <QApplication>
#include<QWidget>
#include<QObject>
#include <iostream>
using namespace std;

class A:public QWidget{public:
      bool event(QEvent* e);  //事件處理方式1:重寫虛函數QObject::event()
      void mousePressEvent(QMouseEvent* e); };//事件處理方式2:重寫QWidget類中的事件處理函數
                      
bool A::event(QEvent* e){ static int i=0;   //i用於計數
    cout<<"E"<<i++<<endl;  //驗證該函數能在事件到達目標對象之前被捕獲
    if(e->type()==QEvent::KeyPress) //判斷是否是鍵盤按下事件。
        cout<<"keyDwon"<<endl;
    return QWidget::event(e);  /*此處應調用父類的event函數以對未處理的事件進行處理,若此處不調用父類的event,則本例mousePressEvent處理函數中的內容將不會被執行。讀者可使此處返回1或者0進行驗證。*/
}
void A::mousePressEvent(QMouseEvent* e){//處理鼠標按下事件,這是Qt中最簡單的事件處理方式。
cout<<"mouseDwon"<<endl;		}
int main(int argc, char *argv[]){
    QApplication a(argc,argv);  //在Qt中QApplication類型的對象只能有一個
    A ma;   //創建一個部件
    ma.resize(333,222);  //設置部件的大小
    ma.show();   //顯示創建的部件

    a.exec();   //在此處進入事件主循環。
    return 0;    }

程序運行結果(見圖2-9)及說明
在這裏插入圖片描述
只要窗口ma接收到事件就會輸出Exx,可見event函數捕獲事件的範圍及時機,當按下鍵盤上的任一鍵時會輸出"keyDown",當按下任一鼠標鍵時,會輸出mouseDown

示例2.18:重寫notify函數

#include <QApplication>
#include<QWidget>
#include<QObject>
#include <iostream>
using namespace std;

class A:public QWidget{public:  bool event(QEvent* e); }; //重寫虛函數QObject::event()
class AA:public QApplication{public:
 	 	AA(int i,char *p[]):QApplication(i,p){}
  		bool notify(QObject *o,QEvent *e);};  //重寫虛函數notify

bool A::event(QEvent* e){    cout<<"E"<<endl;  //用於驗證該函數是否被執行
   	 	return QWidget::event(e);		}   //應調用父類的event函數處理未處理的事件
bool AA::notify(QObject *o,QEvent *e){
    static int i=0;
    cout<<"N"<<i++<<endl;
    if(o->objectName()=="ma"&&e->type()==QEvent::KeyPress)  //若對象爲ma且事件爲鍵盤按下事件
        cout<<"keyDwon"<<endl;
    return QApplication::notify(o,e); } /*應調用父類的notify函數,否則本示例的event函數不會被執行,同時無法關閉窗口*/
int main(int argc, char *argv[]){
    AA aa(argc,argv);//在Qt中QApplication或其子類型的對象只能有一個
    A ma;   //創建一個部件
    ma.setObjectName("ma");  //設置對象名,方便在notify函數中調用
    ma.resize(333,222);  //設置部件的大小
    ma.show();   //顯示創建的部件

    aa.exec();//在此處進入事件主循環。
    return 0;            }

程序運行結果(見圖2-10)及說明
在這裏插入圖片描述
本示例可以看到notify()函數和event()函數的執行順序,及notify函數和event函數捕獲的事件的範圍和時機,當用戶按下鍵盤上的鍵時會輸出keyDown

示例2.19:事件傳遞

#include <QApplication>
#include<QWidget>
#include<QPushButton>
#include<QObject>
#include <iostream>
using namespace std;

class A:public QWidget{public:   bool event(QEvent* e);  };//子類化QWidget
class C:public QPushButton{public:    bool event(QEvent* e);}; //子類化標準按鈕QPushButton

bool A::event(QEvent* e){
    if(e->type()==QEvent::KeyPress) //判斷是否是鍵盤按下事件。
       {cout<<objectName().toStdString()<<"=keyDwon"<<endl; }
    if(e->type()==QEvent::MouseButtonPress) //判斷是否是鼠標按下事件。
       {cout<<objectName().toStdString()<<"=mouseDwon"<<endl; }
    if(e->type()==QEvent::MouseButtonRelease) //判斷是否是鼠標釋放事件。
       {cout<<objectName().toStdString()<<"=mouseRelease"<<endl;  }
return QWidget::event(e);	}

bool C::event(QEvent* e){
    if(e->type()==QEvent::KeyPress) //判斷是否是鍵盤按下事件。
       {cout<<objectName().toStdString()<<"=keyDwon"<<endl;
        	return 0;  }//將事件傳遞給父對象處理
    if(e->type()==QEvent::MouseButtonPress) //判斷是否是鼠標按下事件。
       {cout<<objectName().toStdString()<<"=mouseDwon"<<endl;
        return 1; }  //事件不傳遞
    return QWidget::event(e);}

int main(int argc, char *argv[]){
    QApplication a(argc,argv);  //在Qt中QApplication類型的對象只能有一個
    A ma;   C *mc=new C();
    mc->setParent(&ma);  /*設置父對象爲ma,若mc未處理的對象會傳遞給此處設置的父對象ma處理,注意事件傳遞是在對象之間的父子關係,而不是類之間的父子關係。*/
    mc->setText("AAA");
    mc->move(22,22);  //設置mc相對於ma的位置
    ma.setObjectName("ma");   //設置對象名
    mc->setObjectName("mc");
    ma.resize(333,222);  //設置部件的大小
    ma.show();
    a.exec();   //在此處進入事件主循環。
    return 0;     }

程序運行結果(見圖2.11)及說明
在這裏插入圖片描述
當在按鈕上按下鼠標鍵時,調用C::event()函數輸
出mc=mouseDown,此時該函數返回1,表示事件不
再傳遞。但是該函數並未處理鼠標釋放事件,因此調用QWidget::event()對該事件作默認處理,因默認未處理該事件,因此調用mc的父對象ma處理該事件,此時調用A::event()函數輸出ma=mouseRelease,至此鼠標事件處理結束。
當按鈕獲得焦點,按下鍵盤上的按鍵時的處理方式與鼠標事件類似,只是C::event()函數直接把鍵按下事件傳遞給了父對象ma處理。

2.6.4 事件的接受和忽略

事件可以被接受或忽略,被接受的事件不會再傳遞給其他對象,被忽略的事件會被傳遞給其他對象處理,或者該事件被丟棄(即沒有對象處理該事件)。
使用QEvent::accept()函數表示接受一個事件,使用QEvent::ignore()函數表示忽略一個事件。也就是說若調用accept(),則事件不會傳遞給父對象,若調用ignore()則事件會向父對象傳遞。
Qt默認值是accept (接受事件),但在QWidget的默認事件處理函數(比如keyPressEvent())中,默認值是ignore(),因爲這樣可實現事件的傳遞(即子對象未處理就傳遞給父對象處理)。因此對事件的接受和忽略,最好是明確的調用accept()和ignore函數。
在event()函數中調用accept()或ignore()是沒有意義的,event()函數通過返回一個bool值來告訴調用者是否接受了事件(true表示接受事件)。是否調用accept()是用於事件處理函數與event()函數之間通信的,而event()函數返回的bool值是用於與QApplication::notify()函數之間通信的。
注意:QCloseEvent(關閉事件)有一些不同,QCloseEvent::ignore()表示取消關閉操作,而QCloseEvent::accept()則表示讓Qt繼續關閉操作。爲避免產生混淆,最好在closeEvent()處理函數的重新實現中顯示地調用accept()和ignore()。
示例2.20:事件的接受和忽略

#include <QApplication>
#include<QWidget>
#include<QKeyEvent>
#include<QMouseEvent>
#include<QPushButton>
#include<QObject>
#include <iostream>
using namespace std;

class A:public QWidget{public:  
		//重寫以下事件處理函數
void mousePressEvent(QMouseEvent *e){  cout<<"AmouseDwon"<<endl;}  //鼠標按下
        void mouseReleaseEvent(QMouseEvent *e){  cout<<"AmouseRelease"<<endl;} //鼠標釋放
        void keyPressEvent(QKeyEvent* e){   cout<<"AkeyDwon"<<endl;}        //鍵盤按下
        void keyReleaseEvent(QKeyEvent* e){    cout<<"AkeyRelease"<<endl;}  };   //鍵盤釋放
class C:public QPushButton{public:    
void mousePressEvent(QMouseEvent *e){    cout<<"CmouseDwon"<<endl;
    					e->accept();	}   //驗證事件被接受後不會再被傳遞給父對象
         void mouseReleaseEvent(QMouseEvent *e){		cout<<"CmouseRelease"<<endl;
    					QWidget::mouseReleaseEvent(e);  }//驗證QWidget默認爲忽略事件
         void keyPressEvent(QKeyEvent* e){	cout<<"CkeyDwon"<<endl;
    					e->ignore();   } //驗證事件事件被忽略後會被傳遞給父對象
         void keyReleaseEvent(QKeyEvent* e){      cout<<"CkeyRelease"<<endl;
   			  //驗證Qt的默認處理方式爲接受事件(此處未明確調用accept()或ignore()函數
}         }; 
int main(int argc, char *argv[]){
    QApplication a(argc,argv);  //在Qt中QApplication類型的對象只能有一個
    A ma;   C *mc=new C();
    mc->setParent(&ma);  //設置父對象爲ma
    mc->setText("AAA");
    mc->move(22,22);  //設置mc相對於ma的位置
    ma.resize(333,222);  //設置部件的大小
    ma.show();
    a.exec();   //在此處進入事件主循環。
    return 0;     }

程序運行結果(見圖2-12)及說明
在這裏插入圖片描述
當在按鈕上按下鼠標時,調用C::mousePressEvent()函數,該函數接受該事件,因此事件不再傳遞。因此僅輸出CmouseDown。
當釋放鼠標時調用C::mouseReleaseEvent()函數輸出CmouseRelease,該函數調用QWidget的默認事件處理函數忽略該事件,因此事件傳遞給mc的父對象ma處理,此時調用A::mouseReleaseEvent()函數輸出AmouseRelease。
④~⑥的鍵盤事件與鼠標事件類似,只是⑥表示的是Qt的默認處理方式爲接受該事件
示例2.21:event()函數與事件處理函數的關係。

#include <QApplication>
#include<QWidget>
#include<QMouseEvent>
#include<QObject>
#include <iostream>
using namespace std;

class A:public QWidget{public:
    bool event(QEvent* e){
        if(e->type()==QEvent::MouseButtonPress)   //處理鼠標事件
             {cout<<"AE"<<endl;
            mousePressEvent((QMouseEvent*)e);  /*明確調用鼠標事件處理函數處理該事件,需要注意的是Qt對事件處理函數的默認處理方式是接受事件。*/
            //return QWidget::event(e); /*也可通過父類的event函數間接的調用鼠標事件處理函數。
            return 0; }      //event()函數的返回值與事件處理函數沒有關係,返回0只表示該事件可以傳遞給父對象處理,返回1則事件不再傳遞。*/
        return QWidget::event(e);  //其他事件使用父類的event函數處事
        }
    void mousePressEvent(QMouseEvent *e){cout<<"AK"<<endl;} };
int main(int argc, char *argv[]){
QApplication aa(argc,argv);   
 A ma;     ma.resize(333,222);     ma.show();      aa.exec();    return 0;     }

運行結果說明:在窗口中點擊鼠標會先後輸出AE和AK
本文作者:黃邦勇帥(原名:黃勇)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章