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
#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); }
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)時候.。
信號經過事件實現,事件能夠過濾,事件更底層,事件是基礎,信號是擴展。