一、Qt
的事件分发机制
1、QEvent
简介
QEvent
是所有事件类的基类,所有的事件类(如:鼠标事件、绘图事件、键盘事件等事件类)都继承自该类。其只有一个参数:事件的类型type
,主要用来判断所触发事件的类型。- Qt 主事件循环是通过
exec()
函数进行的,它从事件队列中来获取本地的窗口系统事件,并将其转换成QEvent
,然后再将转换的事件QEvent
发送给具体的对象QObject
- 一般来说,事件来自底层的窗口系统,此时为主动自发的。但是也有可能通过手动的方式来发送事件,此时是非自发的,具体使用:
sendEvent
、postEvent
等方法。 - 对象通过调用
event
函数来接收事件,event
函数可以被子类重载,进而实现自定义事件处理和添加新的事件类型。 - 默认情况下,对象通过调用
event
函数来接收到的事件,会自动被分配到对应的事件处理器,如:mousePressEvent
,wheelEvent
,paintEvent
,timerEvent
等。 - 事件过滤器
installEventFilter
允许一个对象拦截住发往另一个对象的事件,即对其进行过滤。
2、事件处理过程
- 对象接收到的所有事件都会发送到
event
中进行处理,event
函数返回值为bool
类型,如果bool
值为true,则意味着用户将在event
函数中自行处理该事件,该事件将不再向下分发传递,否则将会根据事件的类型继续向下分发。默认情况下事件会自动分发到对应的事件处理器中,除非重写event
函数来对一些事件进行特殊处理,否则event
函数不会对事件进行拦截处理操作 - 事件过滤器
installEventFilter
可以在event
之前进行更高层级的过滤操作,让event
都接收不到对应的事件。默认情况下对象是直接将事件传递到event
中进行处理,若需要更高层级的事件过滤操作,就要先在对应的控件上安装事件过滤器,然后再重写事件过滤函数。 - 在不重写
event
函数和事件过滤器eventFilter
函数的情况下,窗口对象在接收到事件之后,都是根据事件的类型,自动转发到对应的事件处理器中进行处理。注:自动转发的前提是要有该特定的事件处理器,若没有将不会自动转发处理。如:鼠标事件有各种对应的鼠标事件处理器(mousePressEvent,mouseMoveEvent,…),而触摸屏事件就没有特定的事件处理器来进行处理,此时就需要自己在event
函数中来对该事件自行处理。
事件处理流程总结:
正常情况下,首先窗口对象会将获取的所有事件都发送给event
函数,然后event()
根据事件的类型不同来进行分发。常见的事件类型Qt
已经定义了对应的事件处理函数,如鼠标事件、键盘事件等为最常用的。其中event()
事件的返回值为bool
类型,如果它对接受的事件进行了处理,不需要再继续往下传了,就返回true
,否则就返回到对应父控件的event
事件中继续进行处理。【注意,必须要将未处理的事件返回给父控件进行处理。因为事件类型太多,不可能自己对每种事件都来自行处理。】
3、应用示例
(1)、event
可拦截事件,使其不再向下分发。如拦截鼠标点击事件
/******** Pr05_QtEvent.cpp *********/
#include "Pr05_QtEvent.h"
#include <QMouseEvent>
#include <QLabel>
Pr05_QtEvent::Pr05_QtEvent(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
}
void Pr05_QtEvent::mousePressEvent(QMouseEvent *event)
{
//当点击的是鼠标左键时执行
if (event->button() == Qt::LeftButton)
{
ui.label->setText("emit by mousePressEvent function!");
}
}
//event会拦截住鼠标单击事件向下传递,即mousePressEvent不会接收到鼠标单击事件
bool Pr05_QtEvent::event(QEvent *e)
{
if (e->type() == QEvent::MouseButtonPress)
{
//当单击鼠标时,将其静态转换成鼠标事件
QMouseEvent *ev = static_cast<QMouseEvent*>(e);
if (ev->button() == Qt::LeftButton)
{
ui.label->setText("emit by event function!");
return true;
}
else
{
return false;
}
}
else
{
return QWidget::event(e);
}
}
二、常用事件简介
1、鼠标事件
enterEvent(QEvent *event)
:鼠标进入事件,如自定义的QLabel
控件中可以实现该功能leaveEvent(QEvent *event)
:鼠标离开事件mouseMoveEvent(QMouseEvent *event)
:鼠标移动事件,默认的是鼠标按下时,移动鼠标才可以触发该事件。可以在构造函数中设置setMouseTracking(bool enable)
为true
,即可在不按鼠标的情况下,就可以追踪到鼠标移动事件。mousePressEvent(QMouseEvent *event)
:鼠标按下事件mouseReleaseEvent(QMouseEvent *event)
:鼠标释放事件
2、键盘事件
keyPressEvent(QKeyEvent *event)
:按键事件,可以根据event->key()
来获取当前按下的键,并对指定的按键进行处理,过滤等。如按下A
键时触发事件的条件为:event->key() == Qt::Key_AkeyReleaseEvent(QKeyEvent *event)
:当松开指定的按键时,才会触发对应的事件。按下时不会触发该事件。
按键事件示例,对指定按键进行特殊处理
void ClassName::keyPressEvent(QKeyEvent *event)
{
switch (event->key())
{
//当按键为Esc时进行处理,调用EscStopTest函数
case Qt::Key_Escape:
this->EscStopTest();
break;
//当按键为A时进行特殊处理
case Qt::Key_A:
Debug() << "This is A Key";
break;
default:
QWidget::keyPressEvent(event); //其它按键事件不进行处理。
}
}
3、触摸屏事件
event()
:触控屏事件没有专有的函数来进行处理,所以需要在event() 函数中进行直接处理
//处理触控屏示例
bool TouchLabel::event(QEvent *e)
{
//对指定的事件进行处理,相当于过滤。若成功处理就返回true,否则返回false,并传给父控件处理
if (e->type() == QEvent::TouchBegin || e->type() == QEvent::TouchUpdate)
{
QTouchEvent *eTouch = static_cast<QTouchEvent*>(e);
emit FuncWork(eTouch);
return true;
}
else if (e->type() == QEvent::TouchEnd)
{
QTouchEvent *eTouch = static_cast<QTouchEvent*>(e);
emit FuncEnd(eTouch);
return true;
}
//未处理的事件将其返回给对应父控件来进行处理(必须)
return QLabel::event(e);
}
4、其它一些常用事件
paintEvent(QPaintEvent *event)
:绘图事件,所有的绘图操作都需要在该函数中进行重新实现changeEvent(QEvent *event)
:改变事件,该函数主要通过重新实现来处理状态的改变。如切换语言等操作可以在该函数中实现,来进行响应。QApplication::postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority)
:添加事件到接受对象的事件队列中去,由于事件必须要分配到堆中去,所以待添加的事件要用new来进行生成。然后用QObject::customEvent(QEvent *event)
来接受该事件并进行处理。应用详解QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
:直接将事件发送到接受方,并返回处理的结果。该方法与postEvent 不同,该方式是同步的形式来进行的,发送之后不会立即删除,一直等到处理结果返回,其事件是创建在栈上 的。而postEvent 事件是异步的,创建在堆上的事件被放在队列中后,就立即返回了。
三、事件过滤器
当一个窗口中有很多控件,且有多个控件需要过滤事件时,使用event
函数将变的繁琐,因为每个控件都要重写event
来进行过滤对应的事件。此时,可以通过安装事件过滤器来进行便捷的操作。
如下图每个红色方框表示一个控件时,若要对每个控件进行事件过滤,就需要对每个控件的event
函数进行重写。此时通过安装事件过滤器将是最好的方式。
1、首先在需要过滤事件的控件对象上安装事件过滤器installEventFilter(QObject *filterObj)
该函数的原型为:void QObject::installEventFilter(QObject *filterObj)
,参数为事件过滤器对象。将事件过滤器对象安装到指定的控件上,事件过滤器对象为要进行的过滤操作。
- 事件过滤器对象(filterObj)接受所有发送到当前控件上的事件
- filterObj 通过
eventFilter()
来接受事件,并进行过滤操作
2、过滤事件bool QObject::eventFilter(QObject *watched, QEvent *event)
watched
:为要进行过滤事件的控件,即所追踪的控件event
:为要进行过滤的事件类型,即对什么样的事件进行过滤操作
指定控件在安装了事件过滤器后,事件就会首先进入eventFilter
函数,重写该函数就可以实现对指定的事件进行过滤处理。如果成功进行过滤处理,就返回true
,否则就返回false
,并且该事件不再继续向后传递。注意必须要将未处理的事件返回给父控件。
【注】事件过滤器对象和被安装过滤器的控件必须要在同一线程下,否则事件过滤器将不起作用。如果两个控件进入到不同的线程中,只有等这两个控件重新进入到同一线程下事件过滤器才能起作用。
3、应用示例
通过事件过滤器对鼠标点击进行过滤操作
主程序如下:
Pr05_QtEvent::Pr05_QtEvent(QWidget *parent)
: QWidget(parent)
{
ui.setupUi(this);
ui.label->installEventFilter(this); //安装事件过滤器
}
//重写事件过滤函数
bool Pr05_QtEvent::eventFilter(QObject *watched, QEvent *event)
{
if (watched == ui.label)
{
if (event->type() == QEvent::MouseButtonPress || event->type() == QEvent::MouseButtonDblClick)
{
QMouseEvent *ev = static_cast<QMouseEvent*>(event);
if (ev->button() == Qt::LeftButton)
{
ui.label->setText("emit by eventFilter function!");
return true;
}
else
{
return false;
}
}
else
{
return false;
}
}
else
{
return QWidget::eventFilter(watched, event);
}
}