一.Qt事件处理
QT事件处理大概大概可以分为四个步骤:
事件派发 ->事件过滤 ->事件分发 ->事件处理
-
事件派发:事件派发 是由Qt框架(QApplication 对象)来完成的,它将当前应用程序产生的事件派发给对应的窗口
-
事件过滤:事件在到达对应的窗口之前,可以被其他对象过滤、拦截、处理
-
事件分发(一般事件处理):每个窗口都会有一个事件分发器,事件分发器会对事件进行分类,再将分好类的事件分发给对应的事件处理函数进行处理,每个事件处理函数的功能其实是非常单一的,只处理当前这一类事件
-
事件处理(具体事件处理):窗口的事件处理函数,对当前事件进行处理
Qt中产生事件之后,整个的处理流程是这样:
1.当事件产生之后,Qt使用当前应用程序(QApplication)对象的notify函数将事件派发给对应的窗口
[override virtual] bool QApplication::notify(QObject* receiver, QEvent* e);
// receiver:接收事件的(窗口)对象
// e:事件
2.在事件到达对应的窗口之前,可以被其他的QObject对象截获和处理
[override virtual] bool QObject::eventFilter(QObject* target, QEvent* e);
// target: 事件原本的接收(窗口)对象
// e:事件
3.当事件到达指定的窗口后, 窗口的事件分发器会对事件进行分类
[override virtual] bool QWidget::event(QEvent* event);
4:事件分配器对事件进行分类后,按事件的类型(鼠标事件, 键盘事件, resize事件 等等…),将事件分发给对应的具体的事件处理函数去处理,每个事件处理函数已经有默认实现, 我们也可以在子类中重写这些事件处理函数,列举几个事件处理函数:
[virtual] void paintEvent(QPaintEvent *event);
[virtual] void mousePressEvent(QMouseEvent* event);
[virtual] void enterEvent(QEvent *event);
[virtual] void leaveEvent(QEvent *event);
事件处理函数和事件过滤器
1.事件处理函数的作用是让某个具体的QObject对象处理其自己的事件,参考以上第4步;事件处理函数的返回值是void类型,参数为QxxEvent*;
对于某些类别的事件,如果在整个事件的派发和处理流程结束后还没有被处理,那么这个事件会向上转发给它的父Widget(QObject),直到事件到达顶层窗口,如果顶层窗口也不处理此事件,那么它将被视为未处理的事件,未处理的事件可能会有不同的处理方式,具体取决于事件类型和应用程序的设置。一般情况下,未处理的事件会被丢弃,不会对应用程序产生任何影响;
在有需要时,也可以通过重载顶层窗口的事件处理函数来处理未处理的事件。例如:
bool MainWindow::event(QEvent *event)
{
if (event->type() == QEvent::UnhandledKeyRelease) {
// 处理未处理的按键释放事件
// ...
return true; // 表示事件已经被处理
}
// 其他事件处理逻辑...
return QMainWindow::event(event); // 调用父类的事件处理函数
}
QEvent是所有Qt事件类的基类,它提供了两个函数:ignore()和accept(),用于设置事件的处理状态
1.ignore()函数:
- ignore()函数用于将事件标记为未被处理。调用ignore()函数后,表示事件不会被当前对象处理,并会继续向上级对象传递。
- 如果一个对象在处理事件时调用了ignore()函数,那么该事件会被传递给父对象或者其他相关对象进行处理。
- 通过调用ignore()函数,你可以让事件继续向上级对象传递,直到找到能够处理该事件的对象。
2.accept()函数:
- accept()函数用于将事件标记为已被处理。调用accept()函数后,表示当前对象已经处理了该事件,其他对象将不会再接收到该事件。
- 如果一个对象在处理事件时调用了accept()函数,那么该事件会被标记为已被处理,并且不会继续向上传递给其他对象。
- 通过调用accept()函数,你可以告诉Qt框架该事件已经得到了处理,其他对象不需要再处理该事件。
这两个函数通常在具体的事件处理函数中调用,而且只有用在某些类别事件上是有意义的,这些事件就是上面提到的那些会被转发的事件,包括:鼠标、滚轮、按键等事件。
2.事件过滤器的作用是:在事件到达某个具体对象之前,将事件先处理一遍,参考以上第2步,事件过滤器函数的原型为:
bool QObject::eventFilter(QObject* target, QEvent* e);
bool 返回值表示此事件是否被处理,若返回true,表示该事件已经被处理,那么该事件将不会被传递到target对象;若返回false,则表示该事件还未被处理,此事件将继续传递给target对象去处理
二. QT事件机制
以上介绍了QT事件的处理流程,现在讲讲QT事件的产生。我们产生按照事件的来源,将事件分为两类:
- 系统产生:由操作系统产生,比如鼠标消息,键盘消息,Qt将这些来自操作系统的消息,封装为对应的QEvent后进行事件处理
- QT程序自己产生: 例如刷新QWidget,调用update()函数,就是QT系统自己 new 一个QEvent::UpdateRequest事件,然后调用 QApplication::postEvent();将事件放入消息队列,等待事件循环处理;若是立即绘制调用repaint()函数,也是new一个QEvent::UpdateRequest,调用QApplication::sendEvent(),不经过消息队列和事件循环,直接将事件派发并处理
void QWidgetRepaintManager::sendUpdateRequest(QWidget *widget, UpdateTime updateTime)
{
if (!widget)
return;
switch (updateTime) {
case UpdateLater: //对应 update()函数
QCoreApplication::postEvent(widget, new QEvent(QEvent::UpdateRequest), Qt::LowEventPriority);
break;
case UpdateNow: { //对应repaint()函数
QEvent event(QEvent::UpdateRequest);
QCoreApplication::sendEvent(widget, &event);
break;
}
}
}
[static] void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority)
// postEvent 是将事件投递到receiver所在线程的事件队列,等待事件循环来处理此事件
// 可以给别的线程发送事件。事件会在目的对象所属的线程中运行,异步接口
[static] bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
// sendEvent的事件会立即被处理,实际是调用QApplication::notify(),将事件派发并处理,跳过了事件队列和事件循环
//注意, sendEvent仅用于同一个线程之间发送事件。目的对象必须与当前线程一样
根据以上对QT事件处理的分析,我们可以得到5种级别的事件过滤办法,功能从弱到强:
- 重新实现部件的具体事件处理函数,如paintEvent()、mousePressvent()等。这也是最常用的方法
- 重新实现QObject的event()函数;(QObject: :event()函数可以在事件到达具体的事件处理函数之前获得该事件)
- 在对象上安装事件过滤器。(可以用于在一个界面类中同时处理不同子部件的不同事件,到达指定对象的所有事件,都会先经过这个事件过滤器)
- 给QApplication::instance()对象上安装事件过滤器,那么当前应用程序中所有的事件在发往任何其他的过滤器时,都要先经过当前这个事件过滤器
- 继承QApplication,重新实现notify()函数,可以在任何事件过滤器得到事件之前就获得他们