Qt之事件处理机制
思維導讀
一、事件簡介
QT程序是事件驅動的, 程序的每個動作都是由內部某個事件所觸發。QT事件的發生和處理成為程序運行的主線,存在于程序整個生命周期。
常見的QT事件類型如下:
???? 鍵盤事件: 按鍵按下和松開
???? 鼠標事件: 鼠標移動,鼠標按鍵的按下和松開
???? 拖放事件: 用鼠標進行拖放
???? 滾輪事件: 鼠標滾輪滾動
???? 繪屏事件: 重繪屏幕的某些部分
???? 定時事件: 定時器到時
???? 焦點事件: 鍵盤焦點移動
???? 進入和離開事件: 鼠標移入widget之內,或是移出
???? 移動事件: widget的位置改變
???? 大小改變事件: widget的大小改變
???? 顯示和隱藏事件: widget顯示和隱藏
???? 窗口事件: 窗口是否為當前窗口
? QT將系統產生的消息轉化為QT事件,QT事件被封裝為對象,所有的QT事件均繼承抽象類QEvent,用于描述程序內部或外部發生的動作,任意的QObject對象都具備處理QT事件的能力。
二、QT事件產生
(1)操作系統事件
操作系統將獲取的事件,比如鼠標按鍵,鍵盤按鍵等keyPressEvent,keyReleaseEvent,mousePressEvent,mouseReleaseEvent事件, 放入系統的消息隊列中,Qt事件循環的時候讀取消息隊列中的消息,轉化為QEvent并被分發到相應的QWidget對象,相應的QWidget中的event(QEvent *)進行事件處理會對事件進行處理,event(QEvent *)會根據事件類型調用不同的事件處理函數,在事件處理函數中發送QT預定義的信號,最終調用信號關聯的槽函數。
GUI應用程序的事件處理:
A、QT事件產生后會被立即發送到相應的QWidget對象
B、相應的QWidget中的event(QEvent *)進行事件處理
C、event(QEvent *)根據事件類型調用不同的事件處理函數
D、在事件處理函數中發送QT預定義的信號
E、調用信號關聯的槽函數
(2)Qt應用程序自己產生
程序產生事件有兩種方式, 一種是調用QApplication::postEvent(),?例如QWidget::update()函數,當需要重新繪制屏幕時,程序調用update()函數,new出來一個paintEvent,調用 QApplication::postEvent(),將其放入Qt的消息隊列中,等待依次被處理;
另一種方式是調用sendEvent()函數,事件不會放入隊列, 而是直接被派發和處理, QWidget::repaint()函數用的就是阻塞型的。
???? sendEvent()中事件對象的生命期由Qt程序管理,支持分配在棧上和堆上的事件對象;postEvent()中事件對象的生命期由Qt平臺管理,只支持分配在堆上的事件對象,事件被處理后由Qt平臺銷毀。
三、Qt事件處理
(1)事件調度
事件有兩種調度方式,同步和異步。
Qt的事件循環是異步的,當調用QApplication::exec()時,就進入了事件循環,先處理Qt事件隊列中的事件, 直至為空,再處理系統消息隊列中的消息, 直至為空, 處理系統消息的時候會產生新的Qt事件, 需要對其再次進行處理。
???? 調用QApplication::sendEvent的時候, 消息會立即被處理,是同步的。實際上QApplication::sendEvent()是通過調用QApplication::notify(), 直接進入了事件的派發和處理環節。
(2)事件通知、派發
事件過濾器是Qt中一個獨特的事件處理機制, 功能強大而且使用起來靈活方便。通過事件過濾器, 可以讓一個對象偵聽攔截另外一個對象的事件。事件過濾器實現如下: 在所有Qt對象的基類QObject中有一個類型為QObjectList的成員變量,名字為eventFilters,當某個QObject(A)給另一個QObject(B)安裝了事件過濾器后, B會把A的指針保存在eventFilters中。在B處理事件前,會先去檢查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() 函數, 或是安裝事件過濾器。
(3)事件的轉發
對于某些類別的事件,如果在整個事件的派發過程結束后還沒有被處理, 那么這個事件將會向上轉發給它的父widget, 直到最頂層窗口。Qt中和事件相關的函數通過兩種方式相互通信,一種是QApplication::notify(), QObject::eventFilter(), QObject::event()通過返回bool值來表示是否已處理;另一種是調用QEvent::ignore() 或 QEvent::accept() 對事件進行標識,只用于event()函數和特定事件處理函數之間的溝通,而且只有用在某些類別事件上是有意義的, 這些事件就是上面提到的那些會被轉發的事件, 包括: 鼠標, 滾輪, 按鍵等事件。
(4)事件的處理和過濾
QT提供了五種不同級別的事件處理和過濾:
???? A、重寫特定事件處理函數.
???? 最常見的事件處理辦法就是重寫mousePressEvent(), keyPressEvent(), paintEvent()?等特定事件處理函數。
? B、重寫event()函數.
???? 重寫event()函數時, 需要調用父類的event()函數來處理不需要處理或是不清楚如何處理的事件。
???? return QWidget::event(event);
???? C、在Qt對象上安裝事件過濾器
???? 安裝事件過濾器有兩個步驟: (假設要用A來監視過濾B的事件)
???? 首先調用B的installEventFilter( const QOject *obj ), 以A的指針作為參數,所有發往B的事件都將先由A的eventFilter()處理。然后, A要重寫QObject::eventFilter()函數, 在eventFilter() 中對事件進行處理。
???? D、給QAppliction對象安裝事件過濾器
如果給QApplication對象裝上過濾器,那么所有的事件在發往任何其他的過濾器時,都要先經過當前eventFilter()。在QApplication::notify() 中, 是先調用qApp的過濾器, 再對事件進行分析, 以決定是否合并或丟棄。
???? E、繼承QApplication類,并重載notify()函數
???? Qt是用QApplication::notify()函數來分發事件的,要在任何事件過濾器查看任何事件之前先得到這些事件,重寫notify()函數是唯一的辦法。通常來說事件過濾器更好用一些, 因為不需要去繼承QApplication類,而且可以給QApplication對象安裝任意個數的事件過濾器。
四、自定義事件和eventFilter示例
class DefineEvent : public QEvent { public:const static QEvent::Type DefineType = static_cast<QEvent::Type>(QEvent::User + 0xFF);explicit DefineEvent(QString data) : QEvent(DefineType){m_data = data;}QString data() {return m_data;}private:QString m_data; };?
#ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QLineEdit> #include "StringEvent.h" #include <QMouseEvent> #include <QDebug> #include <QApplication>class Widget : public QWidget {Q_OBJECTQLineEdit m_edit; public:Widget(QWidget *parent = 0): QWidget(parent), m_edit(this){m_edit.installEventFilter(this);}bool event(QEvent* evt){if( evt->type() == QMouseEvent::MouseButtonDblClick ){qDebug() << "event: Before sentEvent";StringEvent e("D.T.Software");QApplication::sendEvent(&m_edit, &e);qDebug() << "event: After sentEvent";}return QWidget::event(evt);}bool eventFilter(QObject* obj, QEvent* evt){if( (obj == &m_edit) && (evt->type() == StringEvent::TYPE) ){StringEvent* se = dynamic_cast<StringEvent*>(evt);qDebug() << "Receive: " << se->data();m_edit.insert(se->data());return true;}return QWidget::eventFilter(obj, evt);}~Widget(){} };#endif // WIDGET_H 補充:自定義事件類型可以使用registerEventTypeQEvent::Type ImageLoadedEvent::evType(){ if(s_evType == QEvent::None) { s_evType = (QEvent::Type)registerEventType(); } return s_evType; }
轉載于:https://www.cnblogs.com/xiaobingqianrui/p/9547924.html
總結
- 上一篇: Array and ArrayList
- 下一篇: 重定向与页面转发