弄清事件的传递流程,其实就是为了弄清如何砍断事件的传递,比如你希望某个事件只传给窗口里的控件,而不传给窗口等,这样在一个由多个widget和控件多层嵌套的界面中做出符合想要的事件处理动作,下面看有哪几个时机可以砍断事件的传递
事件处理函数中调用accep()还是ignore()
首先,在重写控件的事件函数时。我们可以在事件函数中通过调用QEvent的accep()方法或者ignore()方法
- accept():表示接受这个事件,该事件到了这个控件被处理后,就不再传递给他的父控件,其父控件就不会收到这个事件
- ignore():表示忽略这个事件,该事件到达这个控件被处理后,依然可以传给他的父控件,他的父控件可以在收到这个事件后继续对该事件做出处理
举例:
先自定义1个标签,重写mousePressEvent函数,鼠标按下后弹出对话框,在重写这个函数时,我们调用accept函数,来中断这个事件的传递,这样其父控件就不会收到这个事件了
同样,该控件所在的父控件/窗口也重写mousePressEvent函数,鼠标按下后弹出对话框,来作区分
自定义标签:
#ifndef MYLABEL_H
#define MYLABEL_H
#include<QLabel>
#include<QMouseEvent>
#include<QMessageBox>
class MyLabel : public QLabel
{
Q_OBJECT
public:
using QLabel::QLabel;
protected:
//重写鼠标按下事件
void mousePressEvent(QMouseEvent* ev)override
{
QMessageBox::information(this,"","来自MyLabel!");
//显示调用accept方法,阻止该事件的传递
//其父窗口/控件就不会收到该事件了
ev->accept();
//也可以什么都不调用,默认就是accept
}
};
#endif // MYLABEL_H
重写其父窗口的鼠标按下事件:
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include<QVBoxLayout>
#include"MyLabel.h"
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr):QWidget(parent)
{
this->resize(500,500);
QVBoxLayout* layout=new QVBoxLayout(this);
layout->setSpacing(0);
layout->setContentsMargins(0,0,0,0);
//把MyLabel放进来
MyLabel* label=new MyLabel(this);
label->setFixedHeight(100);
label->setStyleSheet(".MyLabel{background-color:red}");
layout->addWidget(label);
}
~Widget(){}
protected:
//重写父窗口/控件的鼠标按下事件,来做对比
void mousePressEvent(QMouseEvent* ev)override
{
QMessageBox::information(this,"","来自父窗口!");
}
};
#endif // WIDGET_H
把这个父窗口/控件显示出来,然后点击这个标签,只弹出了1个提示框,可以看到事件传递到MyLabel后就不再往外层传递了
接下来我们再在MyLabel的事件处理函数中调用ignore,让事件接着传递到父窗口/控件的事件处理函数,那么再次点击就会看到2个对话框:
#ifndef MYLABEL_H
#define MYLABEL_H
#include<QLabel>
#include<QMouseEvent>
#include<QMessageBox>
class MyLabel : public QLabel
{
Q_OBJECT
public:
using QLabel::QLabel;
protected:
//重写鼠标按下事件
void mousePressEvent(QMouseEvent* ev)override
{
QMessageBox::information(this,"","来自MyLabel!");
//显示调用accept方法,阻止该事件的传递
//其父窗口/控件就不会收到该事件了
//ev->accept();
//也可以什么都不调用,默认就是accept
ev->ignore();
}
};
#endif // MYLABEL_H
效果如下,可以看到事件接着传递给了他的父窗口/控件,弹出了对话框:
事件分发函数event
然后,在控件的事件分发函数event()中,我们同样可以进行事件的截获
查看Qt源码qwidget.cpp,可以看到even()函数的默认实现,就是根据不同的事件类型,调用默认的事件处理函数:我们重写控件的事件分发函数event(),
在event函数中提前捕获鼠标按下事件,最后再调用父类的event函数,我们重写的鼠标按下事件也会被触发
#ifndef MYLABEL_H
#define MYLABEL_H
#include<QLabel>
#include<QMouseEvent>
#include<QMessageBox>
class MyLabel : public QLabel
{
Q_OBJECT
public:
using QLabel::QLabel;
protected:
//重写鼠标按下事件
void mousePressEvent(QMouseEvent* ev)override
{
QMessageBox::information(this,"","来自MyLabel!");
//显示调用accept方法,阻止该事件的传递
//其父窗口/控件就不会收到该事件了
//ev->accept();
//也可以什么都不调用,默认就是accept
ev->ignore();
}
bool event(QEvent* ev) override
{
if(ev->type()==QEvent::MouseButtonPress)
{
//在事件分发函数中我们提前截获鼠标按下事件,然后进行处理
QMessageBox::information(this,"","来自MyLabel的Event函数!");
}
//调用父类的默认event函数,事件继续分发下去,前面重写的鼠标按下事件函数也会被调用
return QLabel::event(ev);
}
};
#endif // MYLABEL_H
最后运行效果就是:先进入MyLabel的事件分发函数,再进入MyLabel的鼠标按下事件函数,最后进入父窗口/控件的鼠标按下事件函数:
但是,我们可以在重写event函数时,提前返回true或false,来截断事件的传递
- 提前返回true:事件到了MyLabel的event函数就不会往下传递了,MyLabel的鼠标按下事件函数也不会被调用,其父窗口/空间的鼠标按下事件函数也不会被调用
#ifndef MYLABEL_H
#define MYLABEL_H
#include<QLabel>
#include<QMouseEvent>
#include<QMessageBox>
class MyLabel : public QLabel
{
Q_OBJECT
public:
using QLabel::QLabel;
protected:
//重写鼠标按下事件
void mousePressEvent(QMouseEvent* ev)override
{
QMessageBox::information(this,"","来自MyLabel!");
//显示调用accept方法,阻止该事件的传递
//其父窗口/控件就不会收到该事件了
//ev->accept();
//也可以什么都不调用,默认就是accept
ev->ignore();
}
bool event(QEvent* ev) override
{
if(ev->type()==QEvent::MouseButtonPress)
{
//在事件分发函数中我们提前截获鼠标按下事件,然后进行处理
QMessageBox::information(this,"","来自MyLabel的Event函数!");
//提前返回true,事件到此为止
return true;
}
//调用父类的默认even函数,事件继续分发下去,前面重写的鼠标按下事件函数也会被调用
return QLabel::event(ev);
}
};
#endif // MYLABEL_H
- 提前返回false,表示事件没有被识别,事件会接着传递到父窗口/控件。但是由于提前return了false,没有调用父类的事件分发函数event,因此事件不会分发到mousePressEvent函数。事件就只到达了MyLabel的event函数和MyLabel的父窗口/控件鼠标按下事件处理函数
#ifndef MYLABEL_H
#define MYLABEL_H
#include<QLabel>
#include<QMouseEvent>
#include<QMessageBox>
class MyLabel : public QLabel
{
Q_OBJECT
public:
using QLabel::QLabel;
protected:
//重写鼠标按下事件
void mousePressEvent(QMouseEvent* ev)override
{
QMessageBox::information(this,"","来自MyLabel!");
//显示调用accept方法,阻止该事件的传递
//其父窗口/控件就不会收到该事件了
//ev->accept();
//也可以什么都不调用,默认就是accept
ev->ignore();
}
bool event(QEvent* ev) override
{
if(ev->type()==QEvent::MouseButtonPress)
{
//在事件分发函数中我们提前截获鼠标按下事件,然后进行处理
QMessageBox::information(this,"","来自MyLabel的Event函数!");
//提前返回true,事件到此为止
//return true;
//提前返回false,表示事件没有识别,会接着传递到父窗口/控件
//但没有调用后面的QLabel::event(ev),事件不会到达重写的mousePressEvent函数了
return false;
}
//调用父类的默认even函数,事件继续分发下去,前面重写的鼠标按下事件函数也会被调用
return QLabel::event(ev);
}
};
#endif // MYLABEL_H
事件过滤函数eventFilter
前面的事件分发函数event()和事件处理函数比如mousePressEvent()函数都是事件已经到达了控件,我们还可以在事件到达控件之前,提前拦截这个事件。
就是在父窗口/控件中给子控件安装事件过滤器
然后重写父窗口/控件eventFilter函数
- 在这个函数中我们返回true,那么事件到父窗口/控件eventFilter函数中截获处理之后就到此为止了,不会再往下传递了
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include<QVBoxLayout>
#include"MyLabel.h"
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr):QWidget(parent)
{
this->resize(500,500);
QVBoxLayout* layout=new QVBoxLayout(this);
layout->setSpacing(0);
layout->setContentsMargins(0,0,0,0);
//把MyLabel放进来
MyLabel* label=new MyLabel(this);
label->setFixedHeight(100);
label->setStyleSheet(".MyLabel{background-color:red}");
layout->addWidget(label);
label->setObjectName("mylabel");
//给MyLabel安装事件过滤器
label->installEventFilter(this);
}
~Widget(){}
protected:
//重写父窗口/控件的鼠标按下事件,来做对比
void mousePressEvent(QMouseEvent* ev)override
{
QMessageBox::information(this,"","来自父窗口!");
}
//重写事件过滤器函数
bool eventFilter(QObject* watched,QEvent* ev) override
{
if(watched==this->findChild<MyLabel*>("mylabel")&&
ev->type()==QEvent::MouseButtonPress)
{
QMessageBox::information(this,"","来自父窗口的eventFiletr!");
//返回true,这个事件就到此为止了,不会往下传递给MyLabel和父窗口/控件了
return true;
}
return QObject::eventFilter(watched,ev);
}
};
#endif // WIDGET_H
- 如果我们返回false,那么就表示事件接着往下传递
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include<QVBoxLayout>
#include"MyLabel.h"
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr):QWidget(parent)
{
this->resize(500,500);
QVBoxLayout* layout=new QVBoxLayout(this);
layout->setSpacing(0);
layout->setContentsMargins(0,0,0,0);
//把MyLabel放进来
MyLabel* label=new MyLabel(this);
label->setFixedHeight(100);
label->setStyleSheet(".MyLabel{background-color:red}");
layout->addWidget(label);
label->setObjectName("mylabel");
//给MyLabel安装事件过滤器
label->installEventFilter(this);
}
~Widget(){}
protected:
//重写父窗口/控件的鼠标按下事件,来做对比
void mousePressEvent(QMouseEvent* ev)override
{
QMessageBox::information(this,"","来自父窗口!");
}
//重写事件过滤器函数
bool eventFilter(QObject* watched,QEvent* ev) override
{
if(watched==this->findChild<MyLabel*>("mylabel")&&
ev->type()==QEvent::MouseButtonPress)
{
QMessageBox::information(this,"","来自父窗口的eventFiletr!");
//返回true,这个事件就到此为止了,不会往下传递给MyLabel和父窗口/控件了
//return true;
//事件就会接着往下传递
return false;
}
return QObject::eventFilter(watched,ev);
}
};
#endif // WIDGET_H
总结事件传递流程
以鼠标按下事件为例:
1.鼠标按下事件产生,然后Notify()
2.进入到父窗口/控件的eventFilter函数,在该函数中可以提前捕获鼠标按下事件做处理(相应的子控件要安装事件过滤器),处理完后返回true,那么该事件到此为止;返回false那么事件会继续往下传递
3.进入到子控件的事件分发函数event(),在该函数中同样可以捕获到鼠标按下事件做相应处理,处理完后返回true,那么该事件到此为止;返回false,那么事件就会继续到达父窗口/控件的鼠标按下事件函数;返回父类的默认的事件分发函数event(ev),那么事件就会到达子控件的鼠标按下事件处理函数
4.进入子控件的鼠标按下事件函数,如果调用accept()方法,那么事件到此为止;调用ignore()方法,那么事件会接着往下传递到父窗口/控件的鼠标按下事件函数