如果某个派生自 QObject 的类重写 eventFilter 方法,那它就成了事件过滤器(Event Filter)。该方法的声明如下:
virtual bool eventFilter(QObject *watched, QEvent *event);
watched 参数是监听事件的对象,即事件的接收者;event 参数当然就是待处理的事件了。事件过滤器(也可以翻译为“筛选器”)可在接收者之前拦截事件,处理完毕后还可以决定是否把事件转发给接收者。如果不想转发给事件接收者,就返回 true;若还想让事件继续传播就返回 false。
这玩意儿最有益的用途就是:你的顶层窗口上有 K 个子级组件(正常情形是 QWidget 的子类),如果组件没有定义你想用的信号,只能通过处理事件的途径解决,可你又不想只为了处理一个事件就派生一个类(比如,QLabel组件在鼠标悬浮时做点事情),就可以用上事件过滤器了。顶层窗口类重写 eventFilter 方法,拦截发往子组件的事件(如mouseMove)直接处理,这样能节省 N 百行代码。
重写了 eventFilter 方法的类就成了事件的过滤者,而调用 installEventFilter 方法安装过滤器的类才是事件的原始接收者。就拿上文咱们举的 QLabel 组件的例,假设顶层窗口的类名是 DuckWindow,那么,DuckWindow 重写 eventFilter 方法,它就是事件的拦截者;而 QLabel 组件就是事件的原始接收者,所以,调用 installEventFilter 方法的是它。即 QLabel::installEventFilter( DuckWindow )。
不知道老周这样说大伙伴们能否理解。就是负责过滤事件的对象重写 eventFilter 方法;被别人过滤的对象才调用 installEventFilter 方法。
我们用示例说事。下面咱们要做的练习是这样的:
我定义了一个类叫 MyWindow,继承 QWidget 类,作为顶层窗口。然后在窗口里,我用一个 QHBoxLayout 布局,让窗口内的子级组件水平排列。但每个子组件的颜色不同。常规做法是写个自定义组件类,从构造函数或通过成员函数传一个 QColor 对象过去,然后重写 paintEvent 方法绘图。这种做法肯定没问题的。但是!我要是不想写自定义类呢,那就得考虑事件过滤器了,把 paintEvent 事件过滤,直接用某颜色给子组件画个背景就行了。
头文件声明 MyWindow 类。
#ifndef MYWIN #define MYWIN #include <QWidget> #include <QHBoxLayout> #include <QPainter> #include <QEvent> #include <QColor> #include <QRect> class MyWindow : public QWidget { Q_OBJECT public: MyWindow(QWidget* parent=nullptr); bool eventFilter(QObject *obj, QEvent *event) override; private: // 私有成员,画痘痘用的 void paintSomething(QPainter *p, const QColor &color, const QRect &paintRect); // 布局 QHBoxLayout *layout; // 三个子级组件 QWidget *w1, *w2, *w3; }; #endif
这里提一下这个 eventFilter 方法,这厮声明为 public 和 protected 都是可行的。老周这里就声明为 public,与基类的声明一致。
paintSomething 是私有方法,自定义用来画东西的。有伙伴们会问:QPainter 的 paintDevice 不是可以获取到绘图设置(这里指窗口或组件)的大小的矩形区域吗,为啥要从参数传个 QRect?因为这个 rect 来自 QPaintEvent 对象的事件参数,它指的可不一定窗口/组件的整个矩形区域。如果是局部重绘,这个矩形可能就是其中一小部分区域。所以,咱们用事件传递过来的矩形区域绘图。
窗口布局用的是 QHBoxLayout,非常简单的布局方式,子级组件在窗口上水平排列。
下面代码实现构造函数,初始化各个对象。
MyWindow::MyWindow(QWidget *parent) : QWidget(parent) { // 初始化 layout = new QHBoxLayout; this->setLayout(layout); w1 = new QWidget(this); w2 = new QWidget(this); w3 = new QWidget(this); layout->addWidget(w1); layout->addWidget(w2); layout->addWidget(w3); // 安装事件过滤器 w1->installEventFilter(this); w2->installEventFilter(this); w3->installEventFilter(this); }
只有在被拦截的对象上调用 installEventFilter 方法绑定过滤器后,事件过滤器才会生效。此处,由于 MyWindow 类重写了 eventFilter 方法,所以过滤器就是 this。
下面是 eventFilter 方法的实现代码,只过滤 paint 事件即可,其他传给基类自己去玩。
bool MyWindow::eventFilter(QObject *obj, QEvent *event) { // 如果是paint事件 // 这里“与”判断事件接收者是不是在那三个子组件中 // 防止有其他意外对象出现 // 不过这里不会发生,因为只有install了过滤器的对象才会被拦截事件 if(event->type() == QEvent::Paint && (obj==w1 || obj==w2 || obj==w3)) { QPaintEvent* pe = static_cast<QPaintEvent*>(event); QWidget* uiobj = static_cast<QWidget*>(obj); QPainter painter; // 注意这里,绘图设备不是this了,而是接收绘图事件的对象 // 由于它要求的类型是QPaintDevcie*,所以要进行类型转换 // 转换后的uiobj变量的类型是QWidget*,传参没问题 painter.begin(uiobj); if(w1 == uiobj) { // 红色 paintSomething(&painter, QColor("red"), pe->rect()); } if(w2 == uiobj) { // 橙色 paintSomething(&painter, QColor("orange"), pe->rect()); } if(w3 == uiobj) { // 紫色