QT學習之事件處理

  • Qt事件機制app

    Qt程序是事件驅動的, 程序的每一個動做都是由幕後某個事件所觸發.。異步

    Qt事件的發生和處理成爲程序運行的主線,存在於程序整個生命週期。socket

    Qt事件的類型不少, 常見的qt的事件以下:ide

    鍵盤事件: 按鍵按下和鬆開.函數

    鼠標事件: 鼠標移動,鼠標按鍵的按下和鬆開.oop

    拖放事件: 用鼠標進行拖放.佈局

    滾輪事件: 鼠標滾輪滾動.post

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

    定時事件: 定時器到時.this

    焦點事件: 鍵盤焦點移動.

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

    移動事件: widget的位置改變.

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

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

    窗口事件: 窗口是否爲當前窗口.

     

    還有一些很是見的qt事件,好比socket事件,剪貼板事件,字體改變,佈局改變等等.

     

    交流會圍繞如下三個問題展開:

    1、什麼是事件?

    2、事件是怎樣被處理的?

    3、事件與信號的區別?

     

    1、什麼是事件?

    事件:某個「動做」的完成後,需讓某個對象知道而發送的消息。(我的觀點)

    解釋:此時的「動做」並不是一般意義所指的動做,而是廣義的「動做」,是主動和被動的總和。

    例:兩個窗體A和B,當A爲最小化狀態時,咱們使它最大化,這就會讓A主動產生一個重繪事件;當A和B非最小化狀態,且B位於A窗體之上時,咱們讓B最小化,那麼剛纔被B遮擋的A窗體就會被動地產生一個重繪事件。

    Qt 的事件和Qt中的signal不同. 後者一般用來"使用"widget, 而前者用來"實現" widget. 好比一個按鈕, 咱們使用這個按鈕的時候, 咱們只關心他clicked()的signal, 至於這個按鈕如何接收處理鼠標事件,再發射這個信號,咱們是不用關心的. 可是若是咱們要重載一個按鈕的時候,咱們就要面對event了. 好比咱們能夠改變它的行爲,在鼠標按鍵按下的時候(mouse press event) 就觸發clicked()的signal而不是一般在釋放的( mouse release event)時候.

    咱們按產生來源把事件分爲兩類:

    (一) 系統產生的;一般是window system把從系統獲得的消息,好比鼠標按鍵,鍵盤按鍵等, 放入系統的消息隊列中,Qt事件循環的時候讀取這些事件,轉化爲QEvent,再依次處理.

    (二)是由Qt應用程序程序自身產生的.程序產生事件有兩種方式, 一種是調用QApplication::postEvent(). 例如QWidget::update()函數,當須要從新繪製屏幕時,程序調用update()函數,new出來一個paintEvent,調用 QApplication::postEvent(),將其放入Qt的消息隊列中,等待依次被處理. 另外一種方式是調用sendEvent()函數. 這時候事件不會放入隊列, 而是直接被派發和處理, QWidget::repaint()函數用的就是這種方式。

    2、事件是怎樣被處理的?

    (一)兩種調度方式,一種是同步的, 一種是異步.

     

    Qt的事件循環是異步的,當調用QApplication::exec()時,就進入了事件循環. 該循環能夠簡化的描述爲以下的代碼:

     

    while ( !app_exit_loop )

    {

       while( !postedEvents ) { processPostedEvents() }

       while( !qwsEvnts ){ qwsProcessEvents();   }

       while( !postedEvents ) { processPostedEvents() }

     

    }

     

    先處理Qt事件隊列中的事件, 直至爲空. 再處理系統消息隊列中的消息, 直至爲空, 在處理系統消息的時候會產生新的Qt事件, 須要對其再次進行處理.

     

    調用QApplication::sendEvent的時候, 消息會當即被處理,是同步的. 實際上QApplication::sendEvent()是經過調用QApplication::notify(), 直接進入了事件的派發和處理環節.

     

    (二) 事件的派發和處理

    首先說明Qt中事件過濾器的概念. 事件過濾器是Qt中一個獨特的事件處理機制, 功能強大並且使用起來靈活方便. 經過它, 可讓一個對象偵聽攔截另一個對象的事件. 事件過濾器是這樣實現的: 在全部Qt對象的基類: QObject中有一個類型爲QObjectList的成員變量,名字爲eventFilters,當某個QObjec (qobjA)給另外一個QObject (qobjB)安裝了事件過濾器以後, qobjB會把qobjA的指針保存在eventFilters中. 在qobjB處理事件以前,會先去檢查eventFilters列表, 若是非空, 就先調用列表中對象的eventFilter()函數. 一個對象能夠給多個對象安裝過濾器. 一樣, 一個對象能同時被安裝多個過濾器, 在事件到達以後, 這些過濾器以安裝次序的反序被調用. 事件過濾器函數( eventFilter() ) 返回值是bool型, 若是返回true, 則表示該事件已經被處理完畢, Qt將直接返回, 進行下一事件的處理; 若是返回false, 事件將接着被送往剩下的事件過濾器或是目標對象進行處理.

     事件過濾器的代碼實現:
    這個代碼實現了點擊圖片進行放大的功能(屬於事件過濾器的操做實例)

    #ifndef EVENTFILTER_H
    #define EVENTFILTER_H
    
    #include <QDialog>
    #include <QLabel>
    #include <QImage>
    #include <QEvent>
    
    class EventFilter : public QDialog
    {
        Q_OBJECT
        
    public:
        EventFilter(QWidget *parent = 0,Qt::WindowFlags f=0);
        ~EventFilter();
    public slots:
        bool eventFilter(QObject *, QEvent *);
    private:
        QLabel *label1;
        QLabel *label2;
        QLabel *label3;
    
        QLabel *stateLabel;
    
        QImage Image1;
        QImage Image2;
        QImage Image3;
    };
    
    #endif // EVENTFILTER_H
    View Code
    #include "eventfilter.h"
    #include <QHBoxLayout>
    #include <QVBoxLayout>
    #include <QMouseEvent>
    #include <QMatrix>
    #pragma execution_character_set("utf-8")
    
    EventFilter::EventFilter(QWidget *parent,Qt::WindowFlags f)
        : QDialog(parent,f)
    {
        setWindowTitle(tr("事件過濾"));
    
        label1 = new QLabel;
        Image1.load("../image/1.png");
        label1->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
        label1->setPixmap(QPixmap::fromImage(Image1));
    
        label2 = new QLabel;
        Image2.load("../image/2.png");
        label2->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
        label2->setPixmap(QPixmap::fromImage(Image2));
    
        label3 = new QLabel;
        Image3.load("../image/3.png");
        label3->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
        label3->setPixmap(QPixmap::fromImage(Image3));
    
        stateLabel = new QLabel(tr("鼠標按下標誌"));
        stateLabel->setAlignment(Qt::AlignHCenter);
        QHBoxLayout *layout=new QHBoxLayout;
        layout->addWidget(label1);
        layout->addWidget(label2);
        layout->addWidget(label3);
    
        QVBoxLayout *mainLayout = new QVBoxLayout(this);
        mainLayout->addLayout(layout);
        mainLayout->addWidget(stateLabel);
    
        //三個標籤安裝事件監聽器, 指定整個窗體爲監視的對象
        label1->installEventFilter(this);
        label2->installEventFilter(this);
        label3->installEventFilter(this);
    }
    
    EventFilter::~EventFilter()
    {
        
    }
    
    bool EventFilter::eventFilter(QObject *watched, QEvent *event)
    {
        if(watched==label1)
        {
            //判斷時間類型:鼠標按壓事件(左中右)
            if(event->type()==QEvent::MouseButtonPress)
            {
                QMouseEvent *mouseEvent=(QMouseEvent *)event;
                if(mouseEvent->buttons()&Qt::LeftButton)
                {
                    stateLabel->setText(tr("左鍵按下左邊圖片"));
                }
                else if(mouseEvent->buttons()&Qt::MidButton)
                {
                    stateLabel->setText(tr("中鍵按下左邊圖片"));
                }
                else if(mouseEvent->buttons()&Qt::RightButton)
                {
                    stateLabel->setText(tr("右鍵按下左邊圖片"));
                }
    
                //放大選中圖片
                QMatrix matrix;
                matrix.scale(1.8,1.8);
                //從新載入縮放圖片
                QImage tmpImg=Image1.transformed(matrix);
                label1->setPixmap(QPixmap::fromImage(tmpImg));
            }
            //釋放就還原
            if(event->type()==QEvent::MouseButtonRelease)
            {
                stateLabel->setText(tr("鼠標釋放左邊圖片"));
                label1->setPixmap(QPixmap::fromImage(Image1));
            }
        }
        else if(watched==label2)
        {
            if(event->type()==QEvent::MouseButtonPress)
            {
                QMouseEvent *mouseEvent=(QMouseEvent *)event;
                if(mouseEvent->buttons()&Qt::LeftButton)
                {
                    stateLabel->setText(tr("左鍵按下中間圖片"));
                }
                else if(mouseEvent->buttons()&Qt::MidButton)
                {
                    stateLabel->setText(tr("中鍵按下中間圖片"));
                }
                else if(mouseEvent->buttons()&Qt::RightButton)
                {
                    stateLabel->setText(tr("右鍵按下中間圖片"));
                }
    
                QMatrix matrix;
                matrix.scale(1.8,1.8);
                QImage tmpImg=Image2.transformed(matrix);
                label2->setPixmap(QPixmap::fromImage(tmpImg));
            }
            if(event->type()==QEvent::MouseButtonRelease)
            {
                stateLabel->setText(tr("鼠標釋放中間圖片"));
                label2->setPixmap(QPixmap::fromImage(Image2));
            }
        }
        else if(watched==label3)
        {
            if(event->type()==QEvent::MouseButtonPress)
            {
                QMouseEvent *mouseEvent=(QMouseEvent *)event;
                if(mouseEvent->buttons()&Qt::LeftButton)
                {
                    stateLabel->setText(tr("左鍵按下右邊圖片"));
                }
                else if(mouseEvent->buttons()&Qt::MidButton)
                {
                    stateLabel->setText(tr("中鍵按下右邊圖片"));
                }
                else if(mouseEvent->buttons()&Qt::RightButton)
                {
                    stateLabel->setText(tr("右鍵按下右邊圖片"));
                }
    
                QMatrix matrix;
                matrix.scale(1.8,1.8);
                QImage tmpImg=Image3.transformed(matrix);
                label3->setPixmap(QPixmap::fromImage(tmpImg));
            }
            if(event->type()==QEvent::MouseButtonRelease)
            {
                stateLabel->setText(tr("鼠標釋放右邊圖片"));
                label3->setPixmap(QPixmap::fromImage(Image3));
            }
        }
        return QDialog::eventFilter(watched,event);
    }
    View Code

     


    Qt中,事件的派發是從 QApplication::notify() 開始的, 由於QAppliction也是繼承自QObject, 因此先檢查QAppliation對象, 若是有事件過濾器安裝在qApp上, 先調用這些事件過濾器. 接下來QApplication::notify() 會過濾或合併一些事件(好比失效widget的鼠標事件會被過濾掉, 而同一區域重複的繪圖事件會被合併). 以後,事件被送到reciver::event() 處理.

     

    一樣, 在reciver::event()中, 先檢查有無事件過濾器安裝在reciever上. 如有, 則調用之. 接下來,根據QEvent的類型, 調用相應的特定事件處理函數. 一些常見的事件都有特定事件處理函數, 好比:mousePressEvent(), focusOutEvent(),  resizeEvent(), paintEvent(), resizeEvent()等等. 在實際應用中, 常常須要重載這些特定事件處理函數在處理事件. 但對於那些不常見的事件, 是沒有相對應的特定事件處理函數的. 若是要處理這些事件, 就須要使用別的辦法, 好比重載event() 函數, 或是安裝事件過濾器.

     

    事件派發和處理的流程圖以下:

     

    (三) 事件的轉發

     

    對於某些類別的事件, 若是在整個事件的派發過程結束後尚未被處理, 那麼這個事件將會向上轉發給它的父widget, 直到最頂層窗口. 如圖所示, 事件最早發送給QCheckBox, 若是QCheckBox沒有處理, 那麼由QGroupBox接着處理, 若是QGroupBox沒有處理, 再送到QDialog, 由於QDialog已是最頂層widget, 因此若是QDialog不處理, QEvent將中止轉發.       如何判斷一個事件是否被處理了呢? Qt中和事件相關的函數經過兩種方式相互通訊. QApplication::notify(), QObject::eventFilter(), QObject::event() 經過返回bool值來表示是否已處理. 「真」表示已經處理, 「假」表示事件須要繼續傳遞. 另外一種是調用QEvent::ignore() 或 QEvent::accept() 對事件進行標識. 這種方式只用於event() 函數和特定事件處理函數之間的溝通. 並且只有用在某些類別事件上是有意義的, 這些事件就是上面提到的那些會被轉發的事件, 包括: 鼠標, 滾輪, 按鍵等事件.

     

     

    (四)實際運用

     

    根據對Qt事件機制的分析, 咱們能夠獲得5種級別的事件過濾,處理辦法. 以功能從弱到強, 排列以下:

     

    (1)重載特定事件處理函數.

    最多見的事件處理辦法就是重載象mousePressEvent(), keyPressEvent(), paintEvent() 這樣的特定事件處理函數. 以按鍵事件爲例, 一個典型的處理函數以下:

     

    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()函數.

    經過重載event()函數,咱們能夠在事件被特定的事件處理函數處理以前(象keyPressEvent())處理它. 好比, 當咱們想改變tab鍵的默認動做時,通常要重載這個函數. 在處理一些不常見的事件(好比:LayoutDirectionChange)時,evnet()也頗有用,由於這些函數沒有相應的特定事件處理函數. 當咱們重載event()函數時, 須要調用父類的event()函數來處理咱們不須要處理或是不清楚如何處理的事件.

     

    下面這個例子演示瞭如何重載event()函數, 改變Tab鍵的默認動做: (默認的是鍵盤焦點移動到下一個控件上. )

     

    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對象上安裝事件過濾器.

     

    安裝事件過濾器有兩個步驟: (假設要用A來監視過濾B的事件)

    首先調用B的installEventFilter( const QOject *obj ), 以A的指針做爲參數. 這樣全部發往B的事件都將先由A的eventFilter()處理.

    而後, A要重載QObject::eventFilter()函數, 在eventFilter() 中書寫對事件進行處理的代碼.

    用這種方法改寫上面的例子: (假設咱們將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對象)裝上過濾器,那麼全部的事件在發往任何其餘的過濾器時,都要先通過當前這個 eventFilter(). 在debug的時候,這個辦法就很是有用, 也經常被用來處理失效了的widget的鼠標事件,一般這些事件會被QApplication::notify()丟掉. ( 在QApplication::notify() 中, 是先調用qApp的過濾器, 再對事件進行分析, 以決定是否合併或丟棄)

     

    (5) 繼承QApplication類,並重載notify()函數.

    Qt 是用QApplication::notify()函數來分發事件的.想要在任何事件過濾器查看任何事件以前先獲得這些事件,重載這個函數是惟一的辦法. 一般來講事件過濾器更好用一些, 由於不須要去繼承QApplication類. 並且能夠給QApplication對象安裝任意個數的事

     

    3、事件與信號的區別?

     

    Qt 的事件和Qt中的signal不同. 後者一般用來"使用"widget, 而前者用來"實現" widget. 好比一個按鈕, 咱們使用這個按鈕的時候, 咱們只關心他clicked()的signal, 至於這個按鈕如何接收處理鼠標事件,再發射這個信號,咱們是不用關心的。可是若是咱們要重載一個按鈕的時候,咱們就要面對event了。 好比咱們能夠改變它的行爲,在鼠標按鍵按下的時候(mouse press event) 就觸發clicked()的signal而不是一般在釋放的( mouse release event)時候.。

    信號經過事件實現,事件能夠過濾,事件更底層,事件是基礎,信號是擴展。

相關文章
相關標籤/搜索