QEventFilter

​ 在接下来的QT中,我将会是一个相对无厘头的形态,属于是看到哪学到哪,并且大部分时间不会搓代码,只在之后使用一俩个相对不错的项目来进行熟悉。

事件系统

QT中存在着一系列的系统供我们使用,信号与槽系统就是其中最出名的一个,同时,事件机制也是一个相当重要的系统。

​ 事件系统中相当重要的是一套函数:event(QEcent pevent)*

​ QT中通过这套函数来进行事件的传递,我们这里不进行一些详细的分析,只进行一些简单的介绍,主要是我自己也不是很了解这东西。

杂谈

​ 在QT中,基本所有的类都存在着一套继承下来的event机制函数,通过这套函数,可以去使用QT的事件机制。

​ 首先来看一些比较经典的事件函数

1
void MyEdit::keyPressEvent(QKeyEvent * event)

​ 这里的MyEdit是我们继承的LineEdit类,而这里的函数是我们对于父类的keyPressEvent函数的重写,通过这个重写,我们可以自定义处理我们的这个事件的行为。这个没什么好说的。

装饰

主要是来我们事件的传递机制。

​ 在这里,我们其实是对于QLineEdit进行了一次继承的封装,那么,如果从设计模式来看的话,这里其实类似于一种由继承实现的装饰模式。在这个装饰类中,我们可以去使用对应的父类的事件函数来进行重写来添加我们的行为。

1
2
3
4
5
void MyEdit::keyPressEvent(QKeyEvent * event){
qDebug()<<"MyEdit key press event";
QLineEdit::keyPressEvent(event);
event->ignore(); //将这个事件往上抛
}

​ 在这种装饰下,本来的话当我们想要去使用一个QLineEdit控件的时候,其的对应的事件会直接传递给我们的控件基类去进行处理,而我们这里继承了该类并且重写了对应的事件处理函数,这就意味着我们为我们的基类信号处理行为添加了一层代理,所有的信号将会优先通过这个接口,而不是基类的接口。

包装

​ 在这里,你如果没有使用基类的方法,那么你会发现它只是一个日志输出。所以,我们可以使用C++11的特性去进行父类方法的使用,这样的话我们就能够同时使用原有的控件的功能了,这样的话,其实又像极了设计模式中的外观模式。添加了一层额外的包装,这层包装与原来的包装形成了一套更加复杂的系统。但是,我们不必要去了解这上下的层次关系,只需要去关注于当前的设计即可,优秀啊。

传递

​ 需要注意的是,在QT的事件机制中,当一个事件被处理之后,它会被自动的标记为已处理,并且不会再继续地去场传递。但是有时候我们希望我们的这些个事件可以被父对象给进行处理,这时候我们就需要使用我们的Event类的ignore函数。

​ 通过这个函数,我们可以把这个事件重新标记为一种还未被处理的状态,然后这个事件就能够被父对象给进行接受了。以此类推,整个事件链条都支持这种处理方式。

​ 也就是说,这种处理方式其实还是使用了设计模式的行为性设计模式的责任链模式的。

事件传递与责任链模式

Qt 的事件传递机制本质上可以视为一种 责任链模式,因为:

  1. 事件的默认处理方式:
    • 事件被目标对象的事件处理函数捕获。
    • 如果事件被标记为 accepted,则不再继续传递。
  2. 通过 ignore() 实现传递:
    • 调用 event->ignore() 可以将事件标记为未处理。
    • 未处理的事件会沿着父对象链向上传递,直到找到可以处理的对象,或最终丢弃。

阶段归纳

​ 也就是说,QT的事件机制其实可以看做是一种责任链模式的高级应用。在这些个责任链上,基础的消息就是我们的事件。而这些个责任链的传递起始点,就是我们的用户层。第一个处理用户发出的事件的,就是我们对于一些类的额外实现。如果没有,那么就是我们的系列控件类,以此类推。

​ 让我们来重新归纳下这个责任链条。在这个责任链条上,每一种类都是责任链条上的一个处理者。整个责任链条的起始点就是我们的用户层面,我们发出的总总行为动作就是这个责任链条上传递的信息,这些责任链条的构成其实就是我们的一种父子关系的一种体现。可以这么说,每个责任链条上的节点的上一层节点都是他的父对象。注意,不是对应的继承的父对象,而是绑定的父对象。

​ 这个责任链条上,每一层的节点代表的是一个大类,就比如我们的QLineEdit,这个会是对应的使用的原本的QLineEdit的链条上的一个环节。而如果是我们自己实现的MyEdit,这个会是我们另一个使用这个MyEdit对象的链条上的另一个节点,这俩者之间是不一定相交的。

特殊处

​ 我们再来对这个责任链条上的节点进行一些分析,需要注意的是,这些

责任链条上对于事件的处理是有一定的逻辑的。所有的事件当它到达我们的对应的层级时,它会先被我们这个节点上的event函数进行分析并且处理,如果可能,这个信号还会被继续分发到更下层的处理函数中进行处理,以此类推,这些事件处理可能存在多个层次,但是总的来说一般只有event和具体的event处理这俩个层次。

​ 还有,对于事件的处理,这俩个都会进行接受,但是否进行对应的操作,取决于这个事件中的一个特殊属性。姑且可以把这个属性称之为是否已处理。在进入任何一个event处理函数后,这个对应的事件都会被标记为已处理的,如果已经被标记为以处理,那么这个事件将不会被传递到这个链条的任何一层,哪怕是同一层的更深处也是一样。

event和深层函数处理

​ event是一个节点层次的一个相对来说更接近时间发出者的一个层次,在这个层次中,其控制了这个信号接下来的具体流向,其返回一个bool值,如果我们在这个函数处理后返回了一个true,就代表着这个信号已经处理完毕,接下来这个信号将不会被任何处理函数所接收。如果我们这个函数最后返回了一个false,那么这个函数还是会被接下来的下一层次的节点所进行解析。

​ 但是,这里简单的返回true和返回false有时并不能满足我们的需求,因为这种返回是不会使得函数实现原有的功能的,要实现我们原有的功能,就需要去在这个event函数中去使用C++11中的新特性,去调用对应的事件处理函数。

​ 我们一种常见的用法就是在我们的return语句中去进行对应的父类事件处理函数的调用,毕竟这些个事件处理函数都是一个受保护的状态,我们可以通过对应的操作去进行使用,就比如

1
2
3
4
5
6
7
8
9
10
11
12
13
bool MyEdit::event(QEvent *pevent)
{
//保证重写的是当前类的函数
if(pevent->type() == QEvent::KeyPress){
qDebug() << tr("my edit event") << Qt::endl;
//return false;
//在一层的event函数返回false就代表着这个类的这个信号处理结束
//返回true标志处理好了,接下来会传递但不会继续处理
//返回false会继续向下一层进行处理
}

return QLineEdit::event(pevent);
}

​ 需要注意的是,在在这里面,其实就是进行了一次控制流的更改,将当前流转向对应的事件处理流中,等到全部处理完成后返回这个event才会继续去判断这个是否还要继续去往上层去传递。

​ 也就是说,这种流的转向其实是用于我们当前event函数中去进行特定额外的信号处理的机制的。

​ 接下来我们来时候一下这种流的转向会遇到的一种有趣的情况。

​ 在我们的event函数中调用注入**QLineEdit::event(pavent);**函数后,这个函数会去进行对应的函数的调用,有趣的是,这里产生的效果却是我们重载的事件处理函数的效果。

​ 目前来说这几个函数对我们来说都是黑盒,但是我们不难猜测,这是运用了多态的一种特性。在整个事件处理链条的层次中,每个层次中的事件处理函数只会存在一个。如果我们对于事件处理函数进行了重载,那么这个处理函数就会去替换原有的函数的位置,全部的事件处理函数都遵守这个规则。所以,我们如果需要去进行额外的添加,我们还需要做的是在重载的事件处理函数中去使用原有的事件处理方法去进行这个节点的行为的填充。这种又是一种装饰模式的体现。

接下来给出一个GPT的总结版本

GPT归纳版本


事件机制与责任链模式

  1. 事件的责任链传递
    • 事件从用户层发起,沿着父对象关系形成的链条逐级向上传递。
    • 每个对象是责任链上的一个处理节点,具有决定是否处理该事件的权利。
  2. 父对象 vs. 父类
    • 父对象是通过 QObject::setParent 或构造函数参数指定的对象,形成实际的对象层级关系。
    • 父类是继承层级中的基类,与责任链无直接关系。
  3. 节点上的事件处理逻辑
    • 事件到达一个节点时,优先调用该节点的 event 方法。
    • 如果未处理(返回 false),则事件继续向上层节点传递。

event 函数的深度解析

  1. event 的双层处理机制
    • 第一层event 函数负责高层次的事件分发,比如识别事件类型。
    • 第二层:更具体的事件处理函数(如 keyPressEvent)完成细化逻辑。
  2. 流控制
    • 返回 true:事件被标记为已处理,不再传递。
    • 返回 false:事件未处理,继续向上层或同层其他处理器传递。
  3. 保留原功能的方式
    • 调用基类的 event 或具体的事件处理方法(如 QLineEdit::event),保留该类原有的事件处理逻辑。

多态与装饰模式的结合

  1. 多态性
    • 当调用 QLineEdit::event(pevent) 时,实际上调用的是 MyEdit 的重载实现(如果存在)。
    • 这是 C++ 中多态特性的体现,通过虚函数机制确保子类对基类行为的覆盖。
  2. 装饰模式的体现
    • 在子类的 event 方法中,通过调用基类的 event,在其基础上添加额外逻辑,形成装饰模式的效果。

示例补充

可以扩展现有代码的注释,进一步强调核心逻辑:

1
2
3
4
5
6
7
8
9
10
11
bool MyEdit::event(QEvent *pevent) {
// 判断事件类型
if (pevent->type() == QEvent::KeyPress) {
qDebug() << tr("my edit event") << Qt::endl;
// 处理自定义逻辑,但不阻止事件向下传递
return false; // 返回 false 表示未完全处理,允许后续处理
}

// 调用父类的事件处理逻辑,保留 QLineEdit 原有行为
return QLineEdit::event(pevent);
}

小结

你已经很好地总结了 Qt 事件机制与相关设计模式的核心思路。可以进一步结合代码实践,验证如下几个问题:

  • 事件传递:在某节点标记为已处理后,观察事件是否会继续传递到父对象。
  • 多态行为:通过不同的继承与重写组合,探究多态在事件处理中的实际效果。

事件过滤器

​ 在QT中的事件处理机制中,还存在着一个相当重要的机制,就是我们的过滤器机制。通过这个机制,我们可以对于一个用户发出的事件进行过滤,没错,就是你想的那种过滤。

使用事件过滤器的步骤

  1. 继承 QObject 并重写 eventFilter 方法:
    • 事件过滤器需要继承自 QObject 并实现 eventFilter() 方法。
    • eventFilter() 接收两个参数:目标对象和事件本身。
  2. 安装事件过滤器:
    • 通过 installEventFilter() 方法,将事件过滤器安装到目标对象上。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <QCoreApplication>
#include <QDebug>
#include <QKeyEvent>
#include <QWidget>

class MyEventFilter : public QObject
{
Q_OBJECT

public:
bool eventFilter(QObject *watched, QEvent *event) override {
// 判断事件类型
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
qDebug() << "Key pressed:" << keyEvent->text();
// 返回 true 阻止事件继续传播,返回 false 允许传播
return true; // 事件被拦截
}
return QObject::eventFilter(watched, event); // 调用基类处理其他事件
}
};

int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);

QWidget w;
MyEventFilter *filter = new MyEventFilter();
w.installEventFilter(filter); // 将事件过滤器安装到 QWidget 上

w.show();
return a.exec();
}

事件过滤器的作用

  • 拦截和修改事件:比如,你可以拦截按键事件,修改或阻止某些特定的按键。
  • 监听多个对象的事件:事件过滤器可以安装到多个对象上,统一处理这些对象的事件。
  • 事件传播控制:通过返回 truefalse,你可以控制事件是否继续传递,或是否被完全阻止。

事件过滤器的优缺点

优点

  • 可以在不改变目标对象的情况下,灵活地处理事件。
  • 适用于需要跨多个对象进行事件监听或处理的场景。

简单分析

​ 不难猜想到,这个事件过滤器其实是我们事件链条上的一环。再者,在这个链条上,这个事件过滤器的优先级甚至于要高于event函数的,所有的事件都需要先通过这个事件过滤器去进行一次过滤,所有返回true的事件将会被拦截。在经过我们的事件过滤器后,如果我们还想要在本层次中去进行对应的事件处理,我们需要使得这些个事件在最后在这个层级中能够返回false被event函数所接收。

​ 对于一个经过事件处理器的事件,如果最后这个事件过滤器对这个事件的处理返回值是true,就代表这个事件被拦截了,如果返回false,那么这个事件就会被传递到对应的这个层次上的event中去进行后续的处理。当然,我们在这里还可以进行进一步的过滤,就比如再次的去调用另一个事件过滤器,但是总体来说处理逻辑都是一样的。

​ 现在就可以对于这个节点的层次逻辑进行进一步的填充,所有的事件,都会先经过当前节点的事件过滤器去进行识别,当经过这个事件过滤器并且返回值是false后,这个事件将会被分发到event中去进行我们上面已经分析过的处理,这俩者之间没有一个紧密的联系。俩者之间的耦合度相当低使得我们可以只专注于当前事件过滤器的设计。

​ 需要注意一点的是,事件过滤器的安装需要一个事件过滤器指针,这个指针可以有多种情况,最简单的就是去定义一个通用的事件过滤器,所有的都可以使用这个事件过滤器,如果我们是在一个类中去进行了我们事件过滤器函数的重写。如果我们想要安装这个重写的事件过滤器,需要注意的是,我们只需要去在对应的install函数中去传入这个对象的指针即可。

事需缓图,欲速不达也。

得找个时间去练练手,今天就先到这,溜溜球。

-------------本文结束 感谢阅读-------------