1.2.1 Qt中事件是如何进行传递
1.2.2 Qt中的事件过滤器(eventFilter)
1.2.3 如何自己模拟发送事件消息
一、Qt中事件过滤器详解
我们先看下另外两个相关的方法,一个是给对象安装某个事件过滤器,一个是移除对应的事件过滤器。
void QObject::installEventFilter(QObject *filterObj)
void QObject::removeEventFilter(QObject *obj)
下方代码使用 installEventFilter方法 给对象objA安装objB的事件过滤器,这样objB对象的eventFilter方法中就可以接收到objA对象的所有事件了,如果objA对象不想objB对象再监听自己的事件了就使用 removeEventFilter方法移除objB对象对事件的监听。
QObject* objA = new MyQObjectA;
QObject* objB = new MyQObjectB;
objA->installEventFilter(objB);
objA->removeEventFilter(objB);
bool QObject::eventFilter(QObject *watched, QEvent *event)
事件过滤器我们听到这个名字可能就会理解为对事件进行过滤,但是实际上,事件过滤器并不只是过滤事件,也可以对事件进行捕捉、并做出相应的处理操作。对象A只有安装了对象B的事件过滤器,才会在对象B的eventFilter方法中进行监控对象A的所有事件。
事件过滤器使用的三种方式:
1、父窗口类通过重写eventFilter方法来监听子控件的相关事件进行处理。
使用这种方式的好处是不需要通过重写控件的方式获取某些事件,对于安装了事件过滤器的对象,他们所有的事件都会经过这个事件过滤器,所以就可以直接在父窗口中进行监测。比如某个窗口中有个QLabel对象,我们想要监听他的鼠标点击事件,那我们就需要继承QLabel类,然后重写mousePressEvent事件,如果有其他类型的控件也需要获取某个事件,那是不是都需要继续控件并重写某个事件了,所以我们通过事件过滤器就可以很方便获取某个对象的某个事件。
下面这个例子中MyLineEdit和MyBtn继承了QLineEdit和QPushButton,分别重写了两者的键盘按下(keyPressEvent)和鼠标按下事件(mousePressEvent),然后在他们的父窗口EventTestWgt中重写了事件过滤器(eventFilter),并给MyLineEdit和MyBtn对象及本身都安装了事件过滤器。
在此过滤器中捕捉到相应的事件,通过返回true,过滤输入框的键盘按下事件、过滤按钮的鼠标按下事件,过滤本身的鼠标按下事件,通过返回false,让本身的键盘按下事件继续传递,所以我们看到MyLineEdit的keyPressEvent方法、MyBtn的mousePressEvent以及EventTestWgt的mousePressEvent都不会被调用,只有EventTestWgt的keyPressEvent会被调用。
通过这个例子,我们看到事件过滤器可以对本身以及其他类的对象捕捉事件进行处理/过滤,同时也验证了第一种方式中的说法,可以不继承QLineEdit或者QPushButton就可以捕获子部件的相关事件进行处理,不需要对此类进行重写。
EventTestWgt.h
class MyLineEdit : public QLineEdit
{
public:
MyLineEdit(QWidget* parent = nullptr);
private:
void keyPressEvent(QKeyEvent *event);
};
class MyBtn : public QPushButton
{
Q_OBJECT
public:
MyBtn(QWidget* parent = nullptr);
private:
void mousePressEvent(QMouseEvent *event);
};
class EventTestWgt : public QWidget
{
Q_OBJECT
public:
EventTestWgt(QWidget *parent = nullptr);
~EventTestWgt();
private:
void initWgt();
void initConnections();
private:
void keyPressEvent(QKeyEvent *event);
void mousePressEvent(QMouseEvent *event);
private:
bool eventFilter(QObject *watched, QEvent *event);
private slots:
void onBtnClicked();
private:
MyLineEdit* m_lineEdit;
MyBtn* m_pBtn;
};
EventTestWgt.cpp
#include "EventTestWgt.h"
#include <QDebug>
#include <QHBoxLayout>
#include <QKeyEvent>
#include <QMouseEvent>
#include <QEvent>
MyLineEdit::MyLineEdit(QWidget* parent )
: QLineEdit(parent)
{
}
void MyLineEdit::keyPressEvent(QKeyEvent *event)
{
qDebug() << "MyLineEdit::keyPressEvent" << event->key();
return QLineEdit::keyPressEvent(event);
}
MyBtn::MyBtn(QWidget* parent )
{
}
void MyBtn::mousePressEvent(QMouseEvent *event)
{
qDebug() << "MyBtn::mousePressEvent";
return QPushButton::mousePressEvent(event);
}
EventTestWgt::EventTestWgt(QWidget *parent)
: QWidget(parent)
{
initWgt();
initConnections();
this->resize(300, 200);
}
EventTestWgt::~EventTestWgt()
{
}
void EventTestWgt::initWgt()
{
this->installEventFilter(this);
m_lineEdit = new MyLineEdit;
m_lineEdit->installEventFilter(this);
m_pBtn = new MyBtn;
m_pBtn->setText("MyBtn");
m_pBtn->installEventFilter(this);
QHBoxLayout* hLayout = new QHBoxLayout(this);
hLayout->addStretch();
hLayout->addWidget(m_lineEdit);
hLayout->addStretch();
hLayout->addWidget(m_pBtn);
}
void EventTestWgt::initConnections()
{
connect(m_pBtn, &QPushButton::clicked, this, &EventTestWgt::onBtnClicked);
}
void EventTestWgt::keyPressEvent(QKeyEvent *event)
{
qDebug() << "EventTestWgt::keyPressEvent";
return QWidget::keyPressEvent(event);
}
void EventTestWgt::mousePressEvent(QMouseEvent *event)
{
qDebug() << "EventTestWgt::mousePressEvent";
return QWidget::mousePressEvent(event);
}
bool EventTestWgt::eventFilter(QObject *watched, QEvent *event)
{
if (watched == m_lineEdit)
{
if (QEvent::KeyPress == event->type())
{
return true;
}
}
if (watched == m_pBtn)
{
if (QEvent::MouseButtonPress == event->type())
{
return true;
}
}
if (watched == this)
{
if (QEvent::MouseButtonPress == event->type())
{
return true;
}
if (QEvent::KeyPress == event->type())
{
return false;
}
}
return QWidget::eventFilter(watched, event);
}
void EventTestWgt::onBtnClicked()
{
qDebug() << "EventTestWgt::onBtnClicked";
}
下方是本示例的事件传递图,通过此图我们可以很清晰地看到事件传递的顺序、不同类之间事件的传递以及事件过滤器的作用。
在本例中我们过滤了按钮的鼠标按下事件(mousePressEvent),我特意在代码中加了此按钮点击的信号槽连接,实际因为鼠标事件被过滤,槽函数未被触发,因为Qt在按钮控件的内部也是通过事件的捕捉来发送clicked信号的,我们这里过滤了按下事件,影响了信号的发送,所以大家在重写或者过滤事件的时候需要注意。
本例中事件传递流程图
2、专门的事件过滤器类,对特定的对象/特定的事件进行处理。
事件过滤器类只需对当前安装的对象进行处理,无需关心其他操作,且一个事件过滤器类可以被多个对象使用,例如Qt文档中的按键过滤示例,KeyPressEater类中的eventFilter过滤了所有的键盘按下事件,只要安装此事件过滤器的控件,都接收不到键盘按键按下的事件,这种就是对某个通用的事件进行过滤,可以进行多次复用。
class KeyPressEater : public QObject
{
Q_OBJECT
...
protected:
bool eventFilter(QObject *obj, QEvent *event) override;
};
bool KeyPressEater::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
qDebug("Ate key press %d", keyEvent->key());
return true;
} else {
return QObject::eventFilter(obj, event);
}
}
void test()
{
KeyPressEater *keyPressEater = new KeyPressEater(this);
QPushButton *pushButton = new QPushButton(this);
QListView *listView = new QListView(this);
pushButton->installEventFilter(keyPressEater);
listView->installEventFilter(keyPressEater);
}
3、给QApplication安装事件过滤器,达到全局事件监听的效果。
在notify方法下发事件的时候,QApplication对象可以拿到第一控制权,对某些事件优先进行处理,比如全局的快捷键操作。
使用上方的KeyPressEater类对全局的键盘按下事件进行过滤.
QApplication a(argc, argv);
KeyPressEater *keyPressEater = new KeyPressEater(&a);
a.installEventFilter(keyPressEater);
再提一点:
当一个对象安装多个事件过滤器的时候,我们通过文章上方提到,先安装的后调用,下方代码中EventFilterObjA和EventFilterObjB都实现了对鼠标按下事件的过滤,而EventFilterObjB类对象的事件过滤器是后安装的,所以先调用,我们运行代码发现,在EventFilterObjB中过滤完之后EventFilterObjA中的eventFilter就接收不到了,所以只要在一处先过滤,后面就都接收不到了,所以大家在实际运用过程中一定要注意,就算同是事件过滤器也分先后,先过滤了的事件,后面就再也收不到了。
class EventFilterObjA : public QObject
{
public:
EventFilterObjA(QObject* parent = nullptr)
{}
private:
bool eventFilter(QObject *watched, QEvent *event)
{
if (QEvent::MouseButtonPress == event->type())
{
qDebug() << "EventFilterObjA::eventFilter"
<< "Class Name:" << watched->metaObject()->className()
<< "Event:" << event->type();
return true;
}
return QObject::eventFilter(watched, event);
}
};
class EventFilterObjB : public QObject
{
public:
EventFilterObjB(QObject* parent = nullptr)
{}
private:
bool eventFilter(QObject *watched, QEvent *event)
{
if (QEvent::MouseButtonPress == event->type())
{
qDebug() << "EventFilterObjB::eventFilter"
<< "Class Name:" << watched->metaObject()->className()
<< "Event:" << event->type();
return true;
}
return QObject::eventFilter(watched, event);
}
};
void test()
{
QWidget* myWgt = new QWidget;
EventFilterObjA* eFilterObjA = new EventFilterObjA(myWgt);
EventFilterObjB* eFilterObjB = new EventFilterObjB(myWgt);
myWgt->installEventFilter(eFilterObjA);
myWgt->installEventFilter(eFilterObjB);
}
EventFilterObjB::eventFilter Class Name: QWidget Event: QEvent::MouseButtonPress
本例中事件传递流程图
尾
我们通过上篇文章的分析得知,eventFilter的优先级是比较高的,一般来说我们很少通过重写QApplication的notify方法来监测某个控件的某个事件,那样太小题大做了,如果都这样做会导致notify异常庞大,效率也有所降低,所以较常用的就是本篇文章中讲到的事件过滤器方法,既可以监听自己,又可以监听其他对象。
注意点:
1、事件过滤器可以安装在任何继承QObject的对象上,也可以安装在QApplication对象上(全局事件过滤器);
2、事件过滤器(eventFilter方法)返回值为true,表示将当前事件进行过滤,不会发送到对象本身;如果返回false,表示对当前事件不做任何处理,会通过event()方法将事件分发给原来的对象。如果不知道怎么处理或者返回什么,那就返回父类的eventFilter方法(类似 return QObject::eventFilter(watched, event));
3、一个对象可以安装多个事件过滤器(也就是一个对象的事件可以被多个对象进行监控/处理/过滤), 并且最先安装的事件过滤器是最后被调用的,类似于栈的操作,先进后出;
4、一个事件过滤器可以被多个对象安装,但是如果在事件过滤器(eventFilter方法)中把该对象删除了, 一定要将返回值设为true。否则 Qt会将事件继续分发给这个对象,从而导致程序崩溃。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)