QObject有一个eventFilter()函数,用于建立事件过滤器:
virtual bool QObject::eventFilter ( QObject * watched, QEvent * event );
这个函数返回一个 bool 类型,如果你想将参数 event 过滤出来,比如,不想让它继续转发,就返回 true,否则返回 false。事件过滤器的调用时间是目标对象(也就是参数里面的watched对象)接收到事件对象之前。也就是说,如果你在事件过滤器中停止了某个事件,那么,watched对象以及以后所有的事件过滤器根本不会知道这么一个事件。
来看代码:
class MainWindow : public QMainWindow
{
public:
MainWindow();
protected:
//声明 enentFilter()
bool eventFilter(QObject *obj, QEvent *event);
private:
QTextEdit *textEdit;
};
MainWindow::MainWindow()
{
textEdit = new QTextEdit;
setCentralWidget(textEdit);
/*安装过滤器需要调用QObject::installEventFilter()函数。这个函数的签名如下:
void QObject::installEventFilter ( QObject * filterObj )。这个函数接受一个
QObject *类型的参数。记得刚刚我们说的,eventFilter()函数是QObject的一个成员函数,
因此,任意QObject都可以作为事件过滤器(问题在于,如果你没有重写eventFilter()函数,
这个事件过滤器是没有任何作用的,因为默认什么都不会过滤)。*/
textEdit->installEventFilter(this);
}
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
/*不想让textEdit组件处理键盘按下的事件。所以,首先找到这个组件。如果这个事件是键盘
事件,则直接返回true,也就是过滤掉了这个事件,其他事件还是要继续处理,所以返回false。
对于其它的组件,我们并不保证是不是还有过滤器,于是最保险的办法是调用父类的函数。*/
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);
}
}
我们可以向一个对象上面安装多个事件处理器,只要调用多次installEventFilter()函数。如果一个对象存在多个事件过滤器,那么,最后一个安装的会第一个执行,也就是后进先执行的顺序。
注意,如果你在事件过滤器中 delete 了某个接收组件,务必将函数返回值设为 true。否则,Qt 还是会将事件分发给这个接收组件,从而导致程序崩溃。
事件过滤器和被安装过滤器的组件必须在同一线程,否则,过滤器将不起作用。另外,如果在安装过滤器之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。
这句话的意思是,事件过滤器和安装过滤器的组件必须在同一线程。Qt 里面,对象创建之后,可以使用 moveToThread() 函数将一个对象移动到另外的线程。在这种情形下(当然,事件过滤器必须在同一线程时才能被正确安装,这是第一句话说明的),在它们分属在不同线程时,事件过滤器也是不起作用的,只用当它们重新回到同一线程(使用 moveToThread() 或者是线程自然结束)时,过滤器才能重新工作。
总结Qt 学习之路 2(22):事件总结,Qt事件处理的5个层次:
1.重写paintEvent()、mousePressEvent()等事件处理函数。这是最普通、最简单的形式,同时功能也最简单。
2.重写event()函数。event()函数是所有对象的事件入口,QObject和QWidget中的实现,默认是把事件传递给特定的事件处理函数。
3.在特定对象上面安装事件过滤器。该过滤器仅过滤该对象接收到的事件。
4.在QCoreApplication::instance()上面安装事件过滤器。该过滤器将过滤所有对象的所有事件,因此和notify()函数一样强大,但是它更灵活,因为可以安装多个过滤器。全局的事件过滤器可以看到 disabled 组件上面发出的鼠标事件。全局过滤器有一个问题:只能用在主线程。
5.重写QCoreApplication::notify()函数。这是最强大的,和全局事件过滤器一样提供完全控制,并且不受线程的限制。但是全局范围内只能有一个被使用(因为QCoreApplication是单例的)。
Q&A:
1.类开头忘记加Q_OBJECT?
这个因为没有用到 Q_OBJECT 宏的相关内容,所以没有加上。不过在正式开发中,所有 QObject 类的子类都应该添加 Q_OBJECT 宏。
2.结尾为什么不用 return false;而是使用return QMainWindow::eventFilter(obj,event);啊?
由于我们是覆盖了父类的实现,我们只关心 obj == textEdit 这个情况,对于其余情况,依然按照默认实现,也就是要调用父类的实现了。
3.对这句不是很明白:QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event)
这是强制类型转换,类似于 QKeyEvent *keyEvent = (QKeyEvent *)event。
4.安装eventFilter到MainWindow对象为什么要用textEdit来调用?而不是this->installEventFilter(this)?
原来是要monitoredObj->installEventFilter(filterObj);查了帮助。
5.如果在QApplication调用installEventFilter,那是不是所有的widget都应用了事件过滤?
是的。
6.“注意,如果你在事件过滤器中 delete 了某个接收组件,务必将函数返回值设为 true。否则,Qt 还是会将事件分发给这个接收组件,从而导致程序崩溃。”
这个没明白啊, 是delete了 FilterObject* 还是 delete了QTextEdit*?
应该是 delete textEdit 这种。如果你 delete 了 textEdit 却还是返回 false,其它的事件并不知道这个组件已经被销毁,所以可能会出现程序错误。
7.豆哥,我在你写“return QWidget::event(e);”的地方
替换为: return (this->patent())->event(e);
编译不会出错,但是运行的话会报错:The Program has unexpectedly finished。
这两种写法是不一样的:QWidget::event(e) 是调用父类的同名实现;(this->patent())->event(e) 是调用该对象的父对象的实现。Qt 里面的 parent 并不是面向对象意义上的父类,而是 Qt 实现的对象树的父节点。二者是不同的。