Qt 的事件是整个 Qt 框架的核心机制之一,也比较复杂。说它复杂,更可能是由于它涉及到的函数众多,而处理方法也不少,有时候让人难以选择。如今咱们简单总结一下 Qt 中的事件机制。app
Qt 中有不少种事件:鼠标事件、键盘事件、大小改变的事件、位置移动的事件等等。如何处理这些事件,实际有两种选择:框架
1.全部事件对应一个事件处理函数,在这个事件处理函数中用一个很大的分支语句进行选择,其表明做就是 win32 API 的WndProc()
函数:函数
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
在这个函数中,咱们须要使用switch
语句,选择message
参数的类型进行处理,典型代码是:测试
switch(message) { case WM_PAINT: // ... break; case WM_DESTROY: // ... break; ... }
2. 每一种事件对应一个事件处理函数。Qt 就是使用的这么一种机制:this
mouseEvent()
keyPressEvent()
Qt 具备这么多种事件处理函数,确定有一个地方对其进行分发,不然,Qt 怎么知道哪种事件调用哪个事件处理函数呢?这个分发的函数,就是event()
。显然,当QMouseEvent
产生以后,event()
函数将其分发给mouseEvent()
事件处理器进行处理。线程
event()
函数会有两个问题:code
event()
函数是一个 protected 的函数,这意味着咱们要想重写event()
,必须继承一个已有的类。试想,个人程序根本不想要鼠标事件,程序中全部组件都不容许处理鼠标事件,是否是我得继承全部组件,一一重写其event()
函数?protected 函数带来的另一个问题是,若是我基于第三方库进行开发,而对方没有提供源代码,只有一个连接库,其它都是封装好的。我怎么去继承这种库中的组件呢?event()
函数的确有必定的控制,不过有时候个人需求更严格一些:我但愿那些组件根本看不到这种事件。event()
函数虽然能够拦截,但其实也是接收到了QMouseEvent
对象。我连让它收都收不到。这样作的好处是,模拟一种系统根本没有那个事件的效果,因此其它组件根本不会收到这个事件,也就无需修改本身的事件处理函数。这种需求怎么办呢?这两个问题是event()
函数没法处理的。因而,Qt 提供了另一种解决方案:事件过滤器。事件过滤器给咱们一种能力,让咱们可以彻底移除某种事件。事件过滤器能够安装到任意QObject
类型上面,而且能够安装多个。若是要实现全局的事件过滤器,则能够安装到QApplication
或者QCoreApplication
上面。这里须要注意的是,若是使用installEventFilter()
函数给一个对象安装事件过滤器,那么该事件过滤器只对该对象有效,只有这个对象的事件须要先传递给事件过滤器的eventFilter()
函数进行过滤,其它对象不受影响。若是给QApplication
对象安装事件过滤器,那么该过滤器对程序中的每个对象都有效,任何对象的事件都是先传给eventFilter()
函数。对象
事件过滤器能够解决刚刚咱们提出的event()
函数的两点不足:首先,事件过滤器不是 protected 的,所以咱们能够向任何QObject
子类安装事件过滤器;其次,事件过滤器在目标对象接收到事件以前进行处理,若是咱们将事件过滤掉,目标对象根本不会见到这个事件。继承
事实上,还有一种方法,咱们没有介绍。Qt 事件的调用最终都会追溯到QCoreApplication::notify()
函数,所以,最大的控制权其实是重写QCoreApplication::notify()
。这个函数的声明是:事件
virtual bool QCoreApplication::notify ( QObject * receiver, QEvent * event );
该函数会将event
发送给receiver
,也就是调用receiver->event(event)
,其返回值就是来自receiver
的事件处理器。注意,这个函数为任意线程的任意对象的任意事件调用,所以,它不存在事件过滤器的线程的问题。不过咱们并不推荐这么作,由于notify()
函数只有一个,而事件过滤器要灵活得多。
如今咱们能够总结一下 Qt 的事件处理,其实是有五个层次
paintEvent()
、mousePressEvent()
等事件处理函数。这是最普通、最简单的形式,同时功能也最简单。event()
函数。event()
函数是全部对象的事件入口,QObject
和QWidget
中的实现,默认是把事件传递给特定的事件处理函数。QCoreApplication::instance()
上面安装事件过滤器。该过滤器将过滤全部对象的全部事件,所以和notify()
函数同样强大,可是它更灵活,由于能够安装多个过滤器。全局的事件过滤器能够看到 disabled 组件上面发出的鼠标事件。全局过滤器有一个问题:只能用在主线程。QCoreApplication::notify()
函数。这是最强大的,和全局事件过滤器同样提供彻底控制,而且不受线程的限制。可是全局范围内只能有一个被使用(由于QCoreApplication
是单例的)。为了进一步了解这几个层次的事件处理方式的调用顺序,咱们能够编写一个测试代码:
class Label : public QWidget { public: Label() { installEventFilter(this); } bool eventFilter(QObject *watched, QEvent *event) { if (watched == this) { if (event->type() == QEvent::MouseButtonPress) { qDebug() << "eventFilter"; } } return false; } protected: void mousePressEvent(QMouseEvent *) { qDebug() << "mousePressEvent"; } bool event(QEvent *e) { if (e->type() == QEvent::MouseButtonPress) { qDebug() << "event"; } return QWidget::event(e); } }; class EventFilter : public QObject { public: EventFilter(QObject *watched, QObject *parent = 0) : QObject(parent), m_watched(watched) { } bool eventFilter(QObject *watched, QEvent *event) { if (watched == m_watched) { if (event->type() == QEvent::MouseButtonPress) { qDebug() << "QApplication::eventFilter"; } } return false; } private: QObject *m_watched; }; int main(int argc, char *argv[]) { QApplication app(argc, argv); Label label; app.installEventFilter(new EventFilter(&label, &label)); label.show(); return app.exec(); }
咱们能够看到,鼠标点击以后的输出结果是:
QApplication::eventFilter eventFilter event mousePressEvent
所以能够知道,全局事件过滤器被第一个调用,以后是该对象上面的事件过滤器,其次是event()
函数,最后是特定的事件处理函数。