Qt-事件过滤器(EventFilter)

概述

有时,需要对控件的某一类事件进行处理,而某一类则忽略。比如对话框需要拦截键盘事件不让其他控件接收等。

Qt当中对于事件的分发和处理可以通过继承并实现event函数实现,但随着组件的增多,这个操作就会变得多且繁琐,并且重写event还得注意一大堆问题。

Qt提供了另一种方法去实现:事件过滤器(EventFilter)。

原型

1
virtual bool QObject::eventFilter(QObject *watched, QEvent *event);

此函数正如其名,为事件的过滤器。可以对我们接收到的事件进行筛选,留下想要的,或者继续分发不处理的。

使用

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
34
35
36
37
38
39
// CustomWindow.h
class CustomWindow final
: public QMainWindow
{
Q_OBJECT
public:
explicit CustomWindow(QWidget *parent = nullptr);

protected:
bool eventFilter(QObject *watched, QEvent *event) override;

private:
QLineEdit *edit;
}
---
// CustomWindow.cpp
CustomWindow::CustomWindow(QWidget *parent)
: QMainWindow(parent)
, edit(new QLineEdit(this))
{
setCentralWidget(edit);

edit->installEventFilter(this);
}

bool CustomWindow::eventFilter(QObject *watched, QEvent *event)
{
if(watched == edit)
{
if(event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>evnet;
qDebug() << "key press: " << keyEvnet->key();
return true;
}
return false;
}
return QMainWindow::eventFilter(watched, evnet);
}

在这里,CustomWindow是我们定义的一个用户界面,其中有个输入框。我们重写了它的evnetFilter函数,当此时对象是输入框并且事件为键盘事件时,会输出此键并停止处理,将其过滤,返回true,但是其他事件还是要处理的,所以返回false

对于其他的组件,我们不保证是否还有过滤器,所以交由父类处理。

注意事项

  1. installEventFilter可以向一个对象安装多个事件过滤器,只需要多次调用此函数。如果一个对象上安装有多个事件过滤器,那么最后一个安装的会第一个执行,遵循后进先出的原则。
  2. 因为installEventFilter是类QObject的函数,QApplicationQCoreApplication都是QObject的子类。所以,我们可以向主程序添加事件过滤器,这种全局的事件过滤器会在所有其他特性对象的事件过滤器调用之前调用。虽然此种行为更加强大,但是也有其弊端,那就是会降低整个应用程序的事件分发效率。
  3. 当你在事件过滤器中delete了某个接收组件,务必将其返回值设置为true,否则事件过滤器还是会将事件分发给改组件,导致程序崩溃。
  4. 事件过滤器和被安装的组件必须在同一线程,否则,过滤器不起作用。另外,如果在install之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。
  5. 事件的调用最终都会调用QCoreApplicationnotify函数,因此,最大的控制权实际上是重写QCoreApplicationnotify()函数。由此可以看出,Qt的事件处理实际上是分层五个层次:
    1. 重定义事件处理函数;
    2. 重定义event函数;
    3. 为单个组件安装事件过滤器;
    4. QApplication安装事件过滤器;
    5. 重定义QCoreApplicationnotify函数。
    6. 这几个层次的控制权是逐层增大的。