QT-qevent 事件的accept()和ignore()

55 篇文章 26 订阅

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弹出时,主窗口的更新并没有停止,这也印证了上述表述。
 

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值