Qt之事件處理機制


思維導讀

  

一、事件簡介

  QT程序是事件驅動的, 程序的每個動作都是由內部某個事件所觸發。QT事件的發生和處理成為程序運行的主線,存在於程序整個生命周期。

  常見的QT事件類型如下:

     鍵盤事件: 按鍵按下和松開

     鼠標事件: 鼠標移動,鼠標按鍵的按下和松開

     拖放事件: 用鼠標進行拖放

     滾輪事件: 鼠標滾輪滾動

     繪屏事件: 重繪屏幕的某些部分

     定時事件: 定時器到時

     焦點事件: 鍵盤焦點移動

     進入和離開事件: 鼠標移入widget之內,或是移出

     移動事件: widget的位置改變

     大小改變事件: widget的大小改變

     顯示和隱藏事件: widget顯示和隱藏

     窗口事件: 窗口是否為當前窗口

   QT將系統產生的消息轉化為QT事件,QT事件被封裝為對象,所有的QT事件均繼承抽象類QEvent,用於描述程序內部或外部發生的動作,任意的QObject對象都具備處理QT事件的能力。

wKioL1gZ-degD-AyAAB-9l0vK0g875.png

二、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_OBJECT
    QLineEdit 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
補充:自定義事件類型可以使用registerEventType
QEvent::Type ImageLoadedEvent::evType(){
    if(s_evType == QEvent::None)
    {
        s_evType = (QEvent::Type)registerEventType();
    }
 
         
    return s_evType;
}
 
        


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM