有时候,对象须要查看、甚至要拦截发送到另外对象的事件。例如,对话框可能想要拦截按键事件,不让别的组件接收到;或者要修改回车键的默认处理。框架
经过前面的章节,咱们已经知道,Qt 建立了QEvent事件对象以后,会调用QObject的event()函数处理事件的分发。显然,咱们能够在event()函数中实现拦截的操做。因为event()函数是 protected 的,所以,须要继承已有类。若是组件不少,就须要重写不少个event()函数。这固然至关麻烦,更不用说重写event()函数还得当心一堆问题。好在 Qt 提供了另一种机制来达到这一目的:事件过滤器。函数
QObject有一个eventFilter()函数,用于创建事件过滤器。函数原型以下:this
virtual bool QObject::eventFilter ( QObject * watched, QEvent * event );spa
这个函数正如其名字显示的那样,是一个“事件过滤器”。所谓事件过滤器,能够理解成一种过滤代码。事件过滤器会检查接收到的事件。若是这个事件是咱们感兴趣的类型,就进行咱们本身的处理;若是不是,就继续转发。这个函数返回一个 bool 类型,若是你想将参数 event 过滤出来,好比,不想让它继续转发,就返回 true,不然返回 false。事件过滤器的调用时间是目标对象(也就是参数里面的watched对象)接收到事件对象以前。也就是说,若是你在事件过滤器中中止了某个事件,那么,watched对象以及之后全部的事件过滤器根本不会知道这么一个事件。线程
咱们来看一段简单的代码:对象
class MainWindow : public QMainWindow继承
{事件
public:开发
MainWindow();rem
protected:
bool eventFilter(QObject *obj, QEvent *event);
private:
QTextEdit *textEdit;
};
MainWindow::MainWindow()
{
textEdit = new QTextEdit;
setCentralWidget(textEdit);
textEdit->installEventFilter(this);
}
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (obj == textEdit) {
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
qDebug() << "Ate key press" << keyEvent->key();
return true;
} else {
return false;
}
} else {
// pass the event on to the parent class
return QMainWindow::eventFilter(obj, event);
}
}
MainWindow是咱们定义的一个类。咱们重写了它的eventFilter()函数。为了过滤特定组件上的事件,首先须要判断这个对象是否是咱们感兴趣的组件,而后判断这个事件的类型。在上面的代码中,咱们不想让textEdit组件处理键盘按下的事件。因此,首先咱们找到这个组件,若是这个事件是键盘事件,则直接返回 true,也就是过滤掉了这个事件,其余事件仍是要继续处理,因此返回 false。对于其它的组件,咱们并不保证是否是还有过滤器,因而最保险的办法是调用父类的函数。
eventFilter()函数至关于建立了过滤器,而后咱们须要安装这个过滤器。安装过滤器须要调用QObject::installEventFilter()函数。函数的原型以下:
void QObject::installEventFilter ( QObject * filterObj )
这个函数接受一个QObject *类型的参数。记得刚刚咱们说的,eventFilter()函数是QObject的一个成员函数,所以,任意QObject均可以做为事件过滤器(问题在于,若是你没有重写eventFilter()函数,这个事件过滤器是没有任何做用的,由于默认什么都不会过滤)。已经存在的过滤器则能够经过QObject::removeEventFilter()函数移除。
咱们能够向一个对象上面安装多个事件处理器,只要调用屡次installEventFilter()函数。若是一个对象存在多个事件过滤器,那么,最后一个安装的会第一个执行,也就是后进先执行的顺序。
还记得咱们前面的那个例子吗?咱们使用event()函数处理了 Tab 键:
bool CustomWidget::event(QEvent *e)
{
if (e->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
if (keyEvent->key() == Qt::Key_Tab) {
qDebug() << "You press tab.";
return true;
}
}
return QWidget::event(e);
}
如今,咱们能够给出一个使用事件过滤器的版本:
bool FilterObject::eventFilter(QObject *object, QEvent *event)
{
if (object == target && event->type() == QEvent::KeyPress)
{
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
if (keyEvent->key() == Qt::Key_Tab) {
qDebug() << "You press tab.";
return true;
} else {
return false;
}
}
return false;
}
事件过滤器的强大之处在于,咱们能够为整个应用程序添加一个事件过滤器。记得,installEventFilter()函数是QObject的函数,QApplication或者QCoreApplication对象都是QObject的子类,所以,咱们能够向QApplication或者QCoreApplication添加事件过滤器。这种全局的事件过滤器将会在全部其它特性对象的事件过滤器以前调用。尽管很强大,但这种行为会严重下降整个应用程序的事件分发效率。所以,除非是不得不使用的状况,不然的话咱们不该该这么作。
注意,事件过滤器和被安装过滤器的组件必须在同一线程,不然,过滤器将不起做用。另外,若是在安装过滤器以后,这两个组件到了不一样的线程,那么,只有等到两者从新回到同一线程的时候过滤器才会有效。
Qt 的事件是整个 Qt 框架的核心机制之一,也比较复杂。说它复杂,更可能是由于它涉及到的函数众多,而处理方法也不少,有时候让人难以选择。如今咱们简单总结一下 Qt 中的事件机制。
Qt 中有不少种事件:鼠标事件、键盘事件、大小改变的事件、位置移动的事件等等。如何处理这些事件,实际有两种选择:
l 全部事件对应一个事件处理函数,在这个事件处理函数中用一个很大的分支语句进行选择,其表明做就是 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;
...
}
l 每一种事件对应一个事件处理函数。Qt 就是使用的这么一种机制:
n mouseEvent()
n keyPressEvent()
n …
Qt 具备这么多种事件处理函数,确定有一个地方对其进行分发,不然,Qt 怎么知道哪种事件调用哪个事件处理函数呢?这个分发的函数,就是event()。显然,当QMouseEvent产生以后,event()函数将其分发给mouseEvent()事件处理器进行处理。
event()函数会有两个问题:
l event()函数是一个 protected 的函数,这意味着咱们要想重写event(),必须继承一个已有的类。试想,个人程序根本不想要鼠标事件,程序中全部组件都不容许处理鼠标事件,是否是我得继承全部组件,一一重写其event()函数?protected 函数带来的另一个问题是,若是我基于第三方库进行开发,而对方没有提供源代码,只有一个连接库,其它都是封装好的。我怎么去继承这种库中的组件呢?
l event()函数的确有必定的控制,不过有时候个人需求更严格一些:我但愿那些组件根本看不到这种事件。event()函数虽然能够拦截,但其实也是接收到了QMouseEvent对象。我连让它收都收不到。这样作的好处是,模拟一种系统根本没有那个事件的效果,因此其它组件根本不会收到这个事件,也就无需修改本身的事件处理函数。这种需求怎么办呢?
这两个问题是event()函数没法处理的。因而,Qt 提供了另一种解决方案:事件过滤器。事件过滤器给咱们一种能力,让咱们可以彻底移除某种事件。事件过滤器能够安装到任意QObject类型上面,而且能够安装多个。若是要实现全局的事件过滤器,则能够安装到QApplication或者QCoreApplication上面。这里须要注意的是,若是使用installEventFilter()函数给一个对象安装事件过滤器,那么该事件过滤器只对该对象有效,只有这个对象的事件须要先传递给事件过滤器的eventFilter()函数进行过滤,其它对象不受影响。若是给QApplication对象安装事件过滤器,那么该过滤器对程序中的每个对象都有效,任何对象的事件都是先传给eventFilter()函数。
事件过滤器能够解决刚刚咱们提出的event()函数的两点不足:
l 首先,事件过滤器不是 protected 的,所以咱们能够向任何QObject子类安装事件过滤器;
l 其次,事件过滤器在目标对象接收到事件以前进行处理,若是咱们将事件过滤掉,目标对象根本不会见到这个事件。
事实上,还有一种方法,咱们没有介绍。Qt 事件的调用最终都会追溯到QCoreApplication::notify()函数,所以,最大的控制权其实是重写QCoreApplication::notify()。这个函数的声明是:
virtual bool QCoreApplication::notify ( QObject * receiver,
QEvent * event );
该函数会将event发送给receiver,也就是调用receiver->event(event),其返回值就是来自receiver的事件处理器。注意,这个函数为任意线程的任意对象的任意事件调用,所以,它不存在事件过滤器的线程的问题。不过咱们并不推荐这么作,由于notify()函数只有一个,而事件过滤器要灵活得多。
如今咱们能够总结一下 Qt 的事件处理,其实是有五个层次:
l 重写paintEvent()、mousePressEvent()等事件处理函数。这是最普通、最简单的形式,同时功能也最简单。
l 重写event()函数。event()函数是全部对象的事件入口,QObject和QWidget中的实现,默认是把事件传递给特定的事件处理函数。
l 在特定对象上面安装事件过滤器。该过滤器仅过滤该对象接收到的事件。
l 在QCoreApplication::instance()上面安装事件过滤器。该过滤器将过滤全部对象的全部事件,所以和notify()函数同样强大,可是它更灵活,由于能够安装多个过滤器。全局的事件过滤器能够看到 disabled 组件上面发出的鼠标事件。全局过滤器有一个问题:只能用在主线程。
l 重写QCoreApplication::notify()函数。这是最强大的,和全局事件过滤器同样提供彻底控制,而且不受线程的限制。可是全局范围内只能有一个被使用(由于QCoreApplication是单例的)。