Qt The Event System 事件系统
前言
在Qt中,事件就是对象,继承自QEvent
抽象类。事件可能发生在应用内部,也可能发生在应用外部,都需要被应用捕获。QObject
子类的任何实例可以接受事件、处理事件,经常用在widgets
中。
事件和信号是有所不同的,比如对于一个鼠标单击动作,按钮可以发送一个clicked()
信号,此时我们只是关注鼠标单击这一个信号,而不去考虑鼠标如何点击的。
上图就是一个完整的事件处理流程,当我们处理一个事件时会跳出事件循环,等到事件处理完成之后回到事件循环。
但是信号槽不同,当处理信号时不会跳出事件循环,实际上信号槽是另一种机制,它更像是一种函数调用,当发出信号时接收信号的槽会立即执行,执行完毕后返回到原位置。
事件的递送
当有一个事件发生时,Qt
将会创建一个合适的QEvent
子类实例,通过QObject
或其子类的event()
函数将它传送给特定的QObject
或其子类实例。
event()
不会处理这个事件,而是根据特定类型的事件调用不同的事件处理器,并返回一个状态,表明是否被接受。
事件类型
大部分事件类型都有特殊的类,比如QResizeEvent
,QPaintEvent
,QMouseEvent
,QKeyEvent
和QCloseEvent
等。每个类都继承自QEvent
并添加了特定的函数用于处理特定事件。
一些类支持多于一个实际事件操作,比如QMouseEvent
支持鼠标单击,双击,移动以及其他操作。
每一个事件还有一个关联类型,定义为QEvent::Type
,它可以用作一个运行时类型信息(RTTI)源,能够快速确定给定事件是从哪个子类构造的。
事件处理器
一般事件处理方式是通过event()
分发给不同的事件处理器。
比如QPaintEvent
就通过调用QWidget::paintEvent()
进行传递。这个虚函数负责做出适当的反应,通常是重画界面。如果不想讨论这么多的情况,则可以通过调用基类的实现来完成。
void MyCheckBox::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
// handle left mouse button here
} else {
// pass on other buttons to base class
QCheckBox::mousePressEvent(event);
}
}
上面这个例子就通过mousePressEvent
处理左键单击事件,而其他按钮事件传递给QCheckBox
基类去完成。其中mousePressEvent
就可以看成一个事件处理器。
如果我们希望替换基类的函数,就必须实现每一个事件(一般不会这么做),一般做法就是只处理我们需要的,其他部分交给基类的函数去完成。
上面的例子中我们已经知道事件是QMouseEvent
中的一种,但是大部分情况下我们都不知道具体事件是什么,此时可以重新实现QObject::event()
函数,和上面类似,我们可以只处理我们需要的事件,其他事件交给QWidget::event()
去处理。
其实对于事件处理器可以单纯理解为处理事件的函数或者代码块,一般来说event
是分发事件的函数,具体的mousePressEvent
是处理事件的函数,但有时也可以直接在event
中处理事件,这时对应我们重写event
的情况。
Event Filters 事件过滤器
有时一个对象需要查看或者拦截某个传递给另一个对象的事件,这时候需要用到事件过滤器。
事件过滤器在event()
分发之前拦截。
事件过滤器的特点
通过QObject::installEventFilter()
函数就可以为某个对象设置一个事件过滤器,在这个对象中接收目标对象的事件,这个函数的参数是一个QObject
对象(过滤器对象),这个过滤器对象必须重写eventFilter()
成员函数。
事件过滤器在目标对象之前处理事件,可以根据需要检查和丢弃事件。
bool QObject::eventFilter(QObject *watched, QEvent *event);
这是过滤器对象的eventFilter()
成员函数的参数要求,为了能够识别目标对象,在这个过滤器对象中必须设置一个target
变量保存目标对象。
当一个过滤器对象的eventFilter()
被调用了,它可以接收或丢弃这个事件,并可以允许或拒绝事件的进一步处理。如果所有的事件过滤器都允许某个事件的进一步处理(每一个过滤器都返回false
),这个事件将会被发送给目标对象。如果其中有一个过滤器阻止了事件的进一步传播,则目标对象以及之后的过滤器都将看不到这个事件。
bool FilterObject::eventFilter(QObject *object, QEvent *event)
{
if (object == target && event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Tab) {
// Special tab handling
return true;
} else
return false;
}
return false;
}
上面这个示例先比较事件的目标是否是我们规定的目标对象,然后对其进行相应处理。
我们通过返回true
阻止事件进一步传递,通过返回false
允许事件继续传递给其他事件过滤器或者目标对象。
可以通过QObject::removeEventFilter()
函数删除事件过滤器。
我们也可以在整个应用上添加一个事件过滤器,这样当我们传递某个事件给目标对象时,就会优先调用这个全局过滤器先进行处理。
发送事件
许多应用也想创建并发送它们自己的事件。我们可以通过QCoreApplication::sendEvent()
andQCoreApplication::postEvent()
。
sendEvent()
将会立即处理事件,当它返回时,能够通过调用事件类的isAccepted()
来判断这个事件是否被接收。
postEvent()
会把事件放到队列中,下一次运行Qt主事件循环时,它会根据一些优化策略调度所有被提交的事件。这种优化的例子有很多,比如有多个调整大小的事件,则会将他们压缩成一个。
postEvent()
也经常用在对象初始化期间,因为被提交的事件在对象初始化完成后很快就会被调度。
如果想要创建自定义类型的事件可以到官网查阅QEvent文档。