QT-qevent 事件的accept()和ignore()
QEvent的accept()和ignore()一般不會用到,因為不如直接調用QWidget類的事件處理函數直接,而且作用是一樣的,見下面的例子。
推薦直接調用QWidget的事件處理函數。而不是調用accept()和ignore()。
只有一種情況下,必須使用調用accept()和ignore(),那就是closeEvent(),在closeEvent()的事件處理函數中,必須調用accept()和ignore()。即如果想窗口被關閉,那么必須顯示調用event->accept();如果不想關閉窗口,必須顯示調用ignore(),否則窗口默認會關閉。
本章內容也是關于Qt事件。或許這一章不能有一個完整的例子,因為對于事件總是感覺很抽象,還是從底層上理解一下比較好的吧!
 前面說到了事件的作用,下面來看看我們如何來接收事件。回憶一下前面的代碼,我們在子類中重寫了事件函數,以便讓這些子類按照我們的需要完成某些功能,就像下面的代碼:
?void?MyLabel::mousePressEvent(QMouseEvent *?event)
{
?????????if(?event->button() == Qt::LeftButton) {
?????????????????// do something
????????}?else?{
????????????????QLabel::mousePressEvent(?event);
????????}
}
 上面的代碼和前面類似,在鼠標按下的事件中檢測,如果按下的是左鍵,做我們的處理工作,如果不是左鍵,則調用父類的函數。這在某種程度上說,是把事件向上傳遞給父類去響應,也就是說,我們在子類中“忽略”了這個事件。
比如上面的例子,eventLabel忽略了這個事件,那么這個事件就會被繼續傳遞下去,實際上是傳遞給了eventLabel的父組件,QLabel,
accept()接收,表面eventLabel會處理這個事件,那么這個事件就不會再繼續傳遞下去,那么QLabel就不會再收到這個事件,
 我們可以把Qt的事件傳遞看成鏈狀:如果子類沒有處理這個事件,就會繼續向其他類傳遞。
其實,Qt的事件對象都有一個accept()函數和ignore()函數。正如它們的名字,前者用來告訴Qt,事件處理函數“接收”了這個事件,不要再傳遞;后者則告訴Qt,事件處理函數“忽略”了這個事件,需要繼續傳遞,尋找另外的接受者。在事件處理函數中,可以使用isAccepted()來查詢這個事件是不是已經被接收了。
 事實上,我們很少使用accept()和ignore()函數,而是想上面的示例一樣,如果希望忽略事件,只要調用父類的響應函數即可。(其實作用是一樣的)
為什么要這么做呢?因為我們無法確認父類中的這個處理函數沒有操作,如果我們在子類中直接忽略事件,Qt不會再去尋找其他的接受者,那么父類的操作也就不能進行,這可能會有潛在的危險。
另外我們查看一下QWidget的mousePressEvent()函數的實現:
?void?QWidget::mousePressEvent(QMouseEvent *?event)
{
?????????event->ignore();//QWidget 會忽略這個事件,
?????????if?((windowType() == Qt::Popup)) {
?????????????????event->accept();
????????????????QWidget* w;
?????????????????while?((w = qApp->activePopupWidget()) && w !=?this){
????????????????????????w->close();
?????????????????????????if?(qApp->activePopupWidget() == w)?// widget does not want to dissappear
????????????????????????????????w->hide();?// hide at least
????????????????}
?????????????????if?(!rect().contains(?event->pos())){
????????????????????????close();
????????????????}
????????}
}
 請注意第一條語句,如果所有子類(比如EventLabel類,)都沒有重寫mousePressEvent函數,這個事件會在這里被忽略掉,這暗示著這個組件(eventLabel)不關心這個事件,這個事件就可能被傳遞給其父組件。
 不過,事情也不是絕對的。在一個情形下,我們必須使用accept()和ignore()函數,那就是在窗口關閉的時候。這個必須明確顯示的調用accept()和ignore(),
在closeEvent()事件處理函數中,accept()是關閉窗口,ignore()是不關閉窗口,只有在closeEvent()中才是這樣,
如果你在窗口關閉時需要有個詢問對話框,那么就需要這么去寫:
closeEvent事件的默認槽函數是QWidget類的CloseEvent()函數,該函數中,會關閉掉當前的widget,
?void?MainWindow::closeEvent(QCloseEvent *?event)
{
?????????if(continueToClose()) {
?????????????????event?->accept();
????????}?else?{
?????????????????event->ignore();
????????}
}
 bool?MainWindow::continueToClose()
{
?????????if(QMessageBox::question(?this,
??????????????????????????????????????????? tr(?"Quit"),
??????????????????????????????????????????? tr(?"Are you sure to quit this application?"),
??????????????????????????????????????????? QMessageBox::Yes | QMessageBox::No,
??????????????????????????????????????????? QMessageBox::No)
????????????????== QMessageBox::Yes) {
?????????????????return?true;
????????}?else?{
?????????????????return?false;
????????}
}
 這樣,我們經過詢問之后才能正常退出程序。
/*****************Qt中QEvent的accept和ignore函數 事件詳解(以QLabel事件重載為例)
Qt中QEvent的accept和ignore函數
 QApplication::notify() (用來發送一個事件)
QObject::eventFilter() (用來過濾事件,即接收攔截別的對象的事件,并處理)
QObject::event() (接收發送給自己這個對象的事件)
Qt事件相關函數的兩種通信方式:1、通過返回值;2、通過accept和ignore
 在Qt事件傳遞和接收相關的函數中,QApplication::notify() , QObject::eventFilter() , QObject::event() 返回值都是bool類型的,返回值為bool類型的函數,通過返回值來說明事件是否被識別(用Qt Assistant中的話叫recognized,深層含義是event()函數對事件進行和處理,或調用了特定事件處理函數進行了處理);而特定的事件處理函數例如mousePressEvent(), keyPressEvent(), paintEvent()等函數它們都是在event()函數中通過switch...case調用的,返回值都是void,處在事件循環隊列的最末端,一般由這些函數調用accept()函數和ignore()函數來表明事件是否被接受。下面講解這兩種通信方式的區別和用途。
? ? accept()函數和ignore()函數用來設置事件的accept屬性,決定此事件會不會轉發給父對象;返回值為true和false決定的是同一目標對象的下一個處理函數是否會被調用,由于事件循環及傳遞過程是個非常復雜的過程,至少有20層函數調用,我們可以將它簡化為維護事件循環隊列的exec()函數,針對某一目標對象的processXX()函數,各個的事件處理函數假設只有2個eventFilter()函數,event()函數。其中processXX()函數,負責傳遞事件到某一特定的對象,我們可以簡單的理解為在它的函數體中首先調用了eventFilter()事件過濾器函數,如果eventFilter返回值為true,則不再調用下一個處理函數event(),否則接著調用event()函數,event()函數返回true和false已經沒有意義了,因為我們假設它是最后一個事件處理函數了,這就是返回值的用處(這也很容易理解,假如在eventFilter()函數中調用了ignore(),并且返回false,那么event()函數將被緊接著調用,且它收到的事件accept屬性為false);到這里我們捏造的processXX函數返回,根據其傳出的事件的accpet屬性來決定是否傳遞給父對象,如果為accpet為false,則傳遞給父對象嗎,在傳遞給父對象前,將accept屬性重置為true。
總結:accpet屬性對應的accpet()函數和ignore()函數決定的事件傳遞是在父子對象之間的,而返回值決定的事件傳遞是在同一目標對象的各個事件處理函數之間的。在各個事件處理函數中都可以調用ignore()和accept(),以最后一個調用這兩個函數的事件處理函數設置的accept屬性值為最終值。
驗證:
設計如下界面,其中press為類型為自定義的MyLabel,繼承于QLabel
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
?
mylabel.cpp文件
#include "mylabel.h"
 #include <QDebug>
 #include <QMouseEvent>
 ?
 MyLabel::MyLabel(QWidget *parent) : QLabel(parent)
 {
 ?
 }
 ?
 void MyLabel::mousePressEvent(QMouseEvent *ev)
 {
 ? ? if(ev->type() == QEvent::MouseButtonPress)
 ? ? ? ? qDebug() << "in Mylabel class press";
 }
 ?
 void MyLabel::mouseDoubleClickEvent(QMouseEvent *event)
 {
 ? ? qDebug() << "in MyLabel class mouse DoubleClickEvent" << "event ignore " << !event->isAccepted();
 ?
 ? ? QLabel::mouseDoubleClickEvent(event);
 }
 其中重載了mousePressEvent()和mouseDoubleEvent();
mainwindow.cpp文件:
#include "mainwindow.h"
 #include "ui_mainwindow.h"
 #include <QMouseEvent>
 #include <QEvent>
 #include <QDebug>
 ?
 MainWindow::MainWindow(QWidget *parent) :
 ? ? QMainWindow(parent),
 ? ? ui(new Ui::MainWindow)
 {
 ? ? ui->setupUi(this);
 ? ? ui->label->installEventFilter(this);
 }
 ?
 MainWindow::~MainWindow()
 {
 ? ? delete ui;
 }
 ?
 void MainWindow::mousePressEvent(QMouseEvent */*event*/)
 {
 ? ? qDebug() << "in MainWindow class press";
 }
 void MainWindow::mouseDoubleClickEvent(QMouseEvent *event)
 {
 ? ? qDebug() << "in MainWindow class DoubleClick";
 }
 ?
 bool MainWindow::eventFilter(QObject *watched, QEvent *event)
 {
 ? ? if(watched == ui->label)
 ? ? {
 ? ? ? ? if(event->type() == QEvent::MouseButtonPress)
 ? ? ? ? {
 ? ? ? ? ? ? qDebug() << "in MainWindow eventFilter press";
 ? ? ? ? ? ? event->ignore();
 ? ? ? ? ? ? return true;
 ? ? ? ? }
 ? ? ? ? event->ignore();
 ? ? ? ? return false;
 ? ? }
 ? ? else
 ? ? {
 ? ? ? ? return QMainWindow::eventFilter(watched, event);
 ? ? }
 }
 其中MyLabel類的label對象,安裝了主窗口的事件過濾器,關鍵的驗證代碼,在于事件過濾器,事件過濾器過濾了MouseButtonPress事件,按照上述我們所說的邏輯,當我們單機界面的press圖標,過濾器MainWindow::eventFilter()先被調用,然后事件會傳遞給父對象,調用MainWindow::mousePressEvent(),因為設置過濾器中調用了了event->ignore(),但子對象label接收不到事件,因為返回了true,所以控制臺只輸出了兩句話:
in MainWindow eventFilter press
 in MainWindow class press
 當雙擊press圖標,會觸發一次MousePressEvent,然后觸發DoubleClickEvent,MousePressEvent和上述單機過程相同,DoubleClickEvent,事件在過濾器中執行了event->ingore();return false;所以執行完過濾器,傳遞給目標對象再執行Mylabel::mouseDoubleClick(),且此函數接收到的事件accpet屬性為false,然后傳遞為父對象,執行MainWindow::mouseDoubleClick(),所以要有4句話輸出:
in MainWindow eventFilter press
 in MainWindow class press
 in MyLabel class mouse DoubleClickEvent event ignore ?true
 in MainWindow class DoubleClick
 下面我們參考Qt中QWidget和QAbstractButton的源碼來理解accept()和ignore()函數:
參考Qt中QWidget類的源碼,許多事件處理函數都直接調用ignore函數來促使事件傳遞給父對象,因為QWidget作為窗口部件的基類,無需對這些事件做出響應,只能拋給父類處理:
QWidget類部分源碼:
void QWidget::mouseMoveEvent(QMouseEvent *event)
 {
 ? ? event->ignore();
 }
 void QWidget::mouseReleaseEvent(QMouseEvent *event)
 {
 ? ? event->ignore();
 }
 void QWidget::tabletEvent(QTabletEvent *event)
 {
 ? ? event->ignore();
 }
但對于QPushButton類的基類QAbstractButton,它就必須對鼠標事件進行處理。查看其鼠標松開事件處理函數,它的處理步驟是如果按鍵是按下的狀態,并且松開事件的坐標在按鍵區域內,則調用d->click()函數,發出clicked()信號,并accept該事件,對于其他情況仍然需要ignore事件,促使事件繼續傳遞:
QAbstractButton部分源碼:
void QAbstractButton::mouseReleaseEvent(QMouseEvent *e)
 {
 ? ? Q_D(QAbstractButton);
 ? ? d->pressed = false;
 ?
 ? ? if (e->button() != Qt::LeftButton) {
 ? ? ? ? e->ignore();
 ? ? ? ? return;
 ? ? }
 ? ? //檢測mouseReleaseEvent發生之前,按鍵是否是按下狀態
 ? ? if (!d->down) {
 ? ? ? ? // refresh is required by QMacStyle to resume the default button animation
 ? ? ? ? d->refresh();
 ? ? ? ? e->ignore();
 ? ? ? ? return;
 ? ? }
 ? ? //檢測mouseReleaseEvent事件發生時,坐標是否在按鍵區域內
 ? ? if (hitButton(e->pos())) {
 ? ? ? ? d->repeatTimer.stop();
 ? ? ? ? d->click();
 ? ? ? ? e->accept();//accept該事件,停止對事件的轉發
 ? ? } else {
 ? ? ? ? setDown(false);
 ? ? ? ? e->ignore();
 ? ? }
 }
 如果事件在某個對象的事件處理函數中被ignore,即標記為未處理,則該事件將會被傳遞給該對象的父對象,直到事件被處理或者到達最頂層對象,每次事件被event()函數、xxxEvent()(特定事件處理函數)接收到時,事件的accept屬性都已經在傳入之前被重置為true,即在event()、特定事件處理函數中可以省略對accept()函數的調用,下面這個例子自定義一個MyLabel的類,并在自定義的Widget中插入MyLabel標簽,我們可以通過mousePressEvent事件來查看此事件是如何在子對象和父對象之間傳遞的。
mylabel.cpp文件
#include "mylabel.h"
 #include <QLabel>
 #include <QMessageBox>
 #include <QMouseEvent>
 #include <QDebug>
 ?
 MyLabel::MyLabel(QWidget *parent) : QLabel(parent)
 {
 ? ? //設置QLabel類默認追蹤鼠標軌跡,默認不追蹤,重載mouseMoveEvent()時可以設置此屬性,否則只有先點擊一下MyLabel標簽,才能開始接收鼠標相關事件
 ? ? //this->setMouseTracking(true);
 }
 void MyLabel::mousePressEvent(QMouseEvent *event)
 {
 ? ? qDebug() << "in MyLabel::mousePressEvent" << event->isAccepted();
 ? ? QMessageBox::information(this, "MyLabel",
 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?QString("coordinate is %1 %2").arg(event->globalPos().x()).arg(event->globalPos().y()));
 ? ? event->ignore();
 }
 MyLabel繼承自QLabel,只是對父類中mousePressEvent函數進行了簡單的重載,并且在處理完事件后,調用了ignore()函數,這回導致mousePressEvent事件繼續傳遞下去。
widget.cpp文件
#include "widget.h"
 #include "ui_widget.h"
 #include <QMessageBox>
 #include <QCloseEvent>
 #include <QDebug>
 #include <QPushButton>
 ?
 Widget::Widget(QWidget *parent) :
 ? ? QWidget(parent),
 ? ? ui(new Ui::Widget)
 {
 ? ? ui->setupUi(this);
 }
 ?
 Widget::~Widget()
 {
 ? ? delete ui;
 }
 ?
 void Widget::mousePressEvent(QMouseEvent *event)
 {
 ? ? qDebug() << "in Widget::mousePressEvent " <<event->isAccepted();
 ? ? QMessageBox::information(this, "Widget",
 ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?QString("coordinate is %1 %2").arg(event->globalPos().x()).arg(event->globalPos().y()));
 }
 ?
 bool Widget::event(QEvent *event)
 {
 ? ? if(event->type() == QEvent::MouseButtonPress)
 ? ? {
 ? ? ? ? qDebug() << "in Widget::event" << event->isAccepted() << "event type is " << event->type();
 ? ? }
 ? ? return QWidget::event(event);
 }
 Widget類中重載了mousePressEvent函數和event函數,用來驗證mousePressEvent事件是否會傳遞到父對象中,運行效果是點擊MyLabel圖標,會彈出一個MessageBox,點擊確定后,彈出第二個MessageBox;
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ??
?
另外Widget::event()函數中的提示會在Widget::mousePressEvent()函數中提示顯示之前顯示,但在MyLabel::mousePressEvent()函數中提示顯示后顯示,如下:
in MyLabel::mousePressEvent true
 in Widget::event true event type is ?QEvent::Type(MouseButtonPress)
 in Widget::mousePressEvent ?true
 這說明,事件的傳遞順序是先傳遞到子對象的event(),子對象的event()調用子對象的mousePressEvent()函數,然后事件傳遞到父對象的event()函數,然后調用父對象的mousePressEvent()函數。
向widget.cpp文件中添加如下代碼:
void Widget::closeEvent(QCloseEvent *event)
 {
 ? ? qDebug() << "in Widget::closeEvent" << event->isAccepted();
 ? ? int result = QMessageBox::warning(this, "warning","are you sure to close?", QMessageBox::Ok | QMessageBox::Cancel);
 ? ? if(result == QMessageBox::Ok)
 ? ? ? ? //注意此處的accept()函數可以省略,因為event在進入此函數前,accept屬性已經置為true
 ? ? ? ? ;//event->accept();
 ? ? else
 ? ? ? ? event->ignore();
 }
 重載closeEvent可以實現關閉窗口前詢問是否關閉,注意,自定義關閉按鍵時,connect()函數參數設置如下:
connect(ui->closeButton, &QPushButton::clicked, this, &Widget::close);
 如何寫成如下,則不能實現上述功能,除非子類化QApplication,然后重載quit()函數:
connect(ui->closeButton, &QPushButton::clicked, qApp, &QApplication::quit);
 關閉窗口時,從點擊關閉按鈕,到相應窗口關閉事件,事件傳遞函數的調用流程:
exec()中的processEvent()從事件循環中收到mouseReleaseEvent事件(目標對象為關閉按鈕),通過notify()等20幾層函數調用最終到達目標對象的mousePress()函數,這里會發出clicked()信號,發送clicked()信號后,mousePress()函數等待窗口的槽函數close()調用完畢,槽函數close()中調用sendPost()函數,發送closeEvent事件,到窗口對象,并等待返回結果,其中又經過notify()等很多層函數調用,最終到達窗口的closeEvent()處理函數,處理完成后再逐級返回,當返回到close()槽函數(close()函數又調用了close_helper()函數)時,會根據事件的accept屬性,決定窗口是否關閉,close_helper()函數的部分源碼:
?if (mode == CloseWithSpontaneousEvent)
 ? ? ? ? ? ? QApplication::sendSpontaneousEvent(q, &e);
 ? ? ? ? else
 ? ? ? ? ? ? QApplication::sendEvent(q, &e);
 ? ? ? ? if (!that.isNull() && !e.isAccepted()) {
 ? ? ? ? ? ? data.is_closing = 0;
 ? ? ? ? ? ? return false;
 close()函數運行完畢后,關閉按鍵的mousePress()函數也開始返回,經過20多層返回最終返回到exec()函數(這里是窗口放棄關閉的情況)。可以發現整個過程都是在串行的執行,不管是傳遞事件,還是信號調用槽函數,都會等待后續處理結果,最終都會返回到exec()事件循環中去,所以我們可以將每次調用processEvent()之后的過程都看成簡單的函數相互調用,包括信號和槽的調用,sendEvent()傳遞事件,都可以看成簡單的函數調用,因為我們只開了一個線程。
 所以事件循環的簡單模型可以描述為Qt程序在while循環中一直調用processEvent()函數,processEvent()函數會處理事件并刪除,所以事件循環隊列會減少,但在處理事件的過程中Qt程序可能調用postEvent()函數添加新的事件到事件循環隊列中去,同時操作系統也會添加事件到事件循環隊列中去,所以程序才能一直響應用戶請求,sendEvent()函數不添加事件到隊列中去,但是它可以看成是應用程序處理當前事件的延長,即在本次processEvent()調用中插入了一個事件,處理完插入的事件,才能繼續返回處理本次processEvent()調用要處理的本來事件,并最終又回到事件循環隊列中去,而不用像postEvent()那樣,將傳遞的事件放到以后的processEvent()函數調用中去處理。所以sendEvent()可以看成傳遞的事件被立即處理(同步),postEvent()不能掌控它所傳遞事件到底什么時候被處理,因為它不知道事件循環隊列中排在它所傳遞的事件之前還有多少個事件(異步)。
?
可以參考http://blog.csdn.net/xiaoyink/article/details/79404377
下面我說一個例子(曾經參與過得一個項目的),加深理解
假設我們在主窗口類的某一個槽函數allTransactionDone()中調用了QMessageBox::information()函數,我們都知道,這會產生一個模態的對話框,對話框彈出后,我們不能操作主窗口,并且,此槽函數將阻塞到我們做出操作,按照上述邏輯,我們的第一反應是,在槽函數返回前程序都無法維護事件循環隊列,主窗口發生的事件將得不到響應,例如主窗口有一個定時器一直觸發其相應的槽函數去更新主窗口的圖像,那么,我們肯定認為,此時主窗口的圖像將停止更新,其實不然,如果按照這種思維,那么當我們去操作彈出的MessageBox時所產生的事件也將無法得到響應,那么程序將無法進行下去,真實的情況是QMessageBox::information()函數最終調用了QMessageBox::exec()來維護事件循環隊列,否則,按照上述邏輯,我們同樣不能操作彈出的模態對話框,QMessageBox::exec()程序維護的事件循環隊列和QApplication::exec()維護的是同一個事件循環隊列,并且Qt程序僅此一個事件循環隊列,這就像是Qt程序在處理某個事件時,調用了processEvent()函數來將主線程的控制權交出去,去處理其他事件,處理完成后在收回控制權繼續處理當前事件,當前事件處理完成后最終將控制權返回給主循環。
allTransactionDone槽函數:
void MainWindow::allTransactionDone(QString string)
 {
 ? ? stack2OpenUi->beginPushButton->setEnabled(true);
 ? ? QImage image("C:/Users/yuechip/Desktop/asf/output/7.jpg");
 ? ? ui->resultLabel->setPixmap(QPixmap::fromImage(image).scaled(this->size, Qt::KeepAspectRatio));
 ? ? ui->resultStringLabel->setText(string);
 ? ? if(!this->saveResult)
 ? ? {
 ? ? ? ? QFile::remove(this->tempSaveResultFileName);
 ? ? }
 ? ? QFile::remove(this->tempJpgFileName);
 ? ? //只看這里
 ? ? QMessageBox::information(this, "information", "information");
 }
 最終程序運行結果:
?
可以看出,當MessageBox彈出時,主窗口的更新并沒有停止,這也印證了上述表述。
 ?
總結
以上是生活随笔為你收集整理的QT-qevent 事件的accept()和ignore()的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: nginx 的请求处理、请求的处理流程
 - 下一篇: insertSelective 和 in