一、简介
在Qt中,事件作为一个对象,继承自 QEvent 类,常见的有键盘事件 QKeyEvent、鼠标事件 QMouseEvent 和定时器事件 QTimerEvent 等,与 QEvent 类的继承关系图如下所示。本章会详细讲解这3个常见的事件,还会涉及事件过滤器、自定义事件和随机数的知识。关于本章的相关内容,可以在Qt帮助中通过The Event System 关键字查看。
二、Qt中的事件
事件是对各种应用程序需要知道的由应用程序内部或者外部产生的事情或者动作的通称。Qt中使用一个对象来表示一个事件,继承自QEvent类。需要说明的是,事件与信号并不相同,比如单击一下界面上的按钮,那么就会产生鼠标事件 QMouseEvent (不是按钮产生的 ),而因为按钮被按下了 ,所以它会发出 clicked() 单击信号(是按钮产生的)。这里一般只关心按钮的单击信号,而不用考虑鼠标事件,但是如果要设计一个按钮,或者当单击按钮时让它产生别的效果,那么就要关心鼠标事件了。可以看到,事件与信号是两个不同层面的东西,发出者不同,作用也不同。在Qt中,任何 QObject子类实例都可以接收和处理事件。
2.1 事件的处理
一个事件由一个特定的 QEvent 子类来表示,但是有时一个事件又包含多个事件类型,比如鼠标事件又可以分为鼠标按下、双击和移动等多种操作。这些事件类型都由 QEvent 类的枚举型 QEvent::Type 来表示,其中包含了 一百多种事件类型,可以在 QEvent 类的帮助文档中查看。虽然 QEvent 的子类可以表示一个事件,但是却不能用来处理事件,那么应该怎样来处理一个事件呢?在 QCoreApplication 类的notify()函数的帮助文档处给出了5种处理事件的方法:
方法一:重新实现部件的 paintEvent()、mousePressEvent() 等事件处理函数。这是最常用的一种方法,不过它只能用来处理特定部件的特定事件。
方法二:重新实现 notify() 函数。这个函数功能强大,提供了完全的控制,可以在事件过滤器得到事件之前就获得它们。但是,它一次只能处理一个事件。
方法三:向 QApplication 对象上安装事件过滤器。因为一个程序只有一个 QApplication 对象,所以这样实现的功能与使用 notify() 函数是相同的,优点是可以同时处理多个事件。
方法四:重新实现 event() 函数。QObject 类的 event() 函数可以在事件到达默认的事件处理函数之前获得该事件。
方法五:在对象上安装事件过滤器。使用事件过滤器可以在一个界面类中同时处理不同子部件的不同事件。
在实际编程中,最常用的是方法一,其次是方法五。因为方法二需要继承自 QApplication 类;而方法三要使用一个全局的事件过滤器,这将减缓事件的传递,所以,虽然这两种方法功能很强大,但是却很少被用到。
2.2 事件的传递
在每个程序的 main() 函数的最后都会调用 QApplication 类的 exec() 函数,它会使Qt应用程序进人事件循环,这样就可以使应用程序在运行时接收发生的各种事件。一旦有事件发生,Qt 便会构建一个相应的 QEvent 子类的对象来表示,然后将它传递给相应的 QObject 对象或其子对象。下面通过例子来看一下Qt中的事件传递过程。
新建Qt Gui应用,项目名称为myEvent,基类选择QWidget,然后类名保持 Widget 不变。建立完成后向项目中添加新文件,模板选择C+ +类,类名为MyLineEdit,基类手动填写为QLineEdit,自定义了一个MyLineEdit 类。
mylineEdit. h 文件:
#ifndef MYLINEEDIT_H#define MYLINEEDIT_H#includeclass MyLineEdit : public QLineEdit{ Q_OBJECTpublic: explicit MyLineEdit(QWidget *parent = nullptr); //event()函数获取事件的类型 bool event(QEvent *event); protected: //MyLineEdit类的键盘按下事件 void keyPressEvent(QKeyEvent *event);};#endif // MYLINEEDIT_H
这里添加了keyPressEvent()函数和event()函数的声明。
mylineEdit. cpp 文件:
#include "mylineedit.h"#include#include MyLineEdit::MyLineEdit(QWidget *parent) : QLineEdit(parent){}// MyLineEdit类的键盘按下事件void MyLineEdit::keyPressEvent(QKeyEvent *event){ qDebug() << tr("MyLineEdit键盘按下事件"); //让MyLineEdit输入栏能输入字符 QLineEdit::keyPressEvent(event); // 执行QLineEdit类的默认事件处理 event->ignore(); // 忽略该事件}//event()函数获取事件的类型bool MyLineEdit::event(QEvent *event) { //判断触发事件类型是否为键盘按下事件 if(event->type() == QEvent::KeyPress) qDebug() << tr("MyLineEdit的event()函数"); return QLineEdit::event(event); // 执行QLineEdit类event()函数的默认操作}
这里自定义了一个MyLineEdit类,它继承自QWidget,并且实现了MyLineEdit类的keyPressEvent()函数和event()函数。event()函数中使用了 event->type() 来获取事件的类型。如果是键盘按下事件 QEvent::KeyPress,则输出信息,另外返回父类的event()函数的操作结果。
widget.h 文件:
#ifndef WIDGET_H#define WIDGET_H#includeclass MyLineEdit;namespace Ui {class Widget;}class Widget : public QWidget{ Q_OBJECTpublic: explicit Widget(QWidget *parent = 0); ~Widget(); // Widget类的事件过滤器 bool eventFilter(QObject *obj, QEvent *event); private: Ui::Widget *ui; MyLineEdit *lineEdit;protected: // Widget类的键盘按下事件 void keyPressEvent(QKeyEvent *event);};#endif // WIDGET_H
这里也添加了keyPressEvent()函数的声明。
widget.cpp 文件:
#include "widget.h"#include "ui_widget.h"#include "mylineedit.h"#include#include Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget){ ui->setupUi(this); lineEdit = new MyLineEdit(this); lineEdit->move(100, 100);}Widget::~Widget(){ delete ui;}// Widget类的键盘按下事件void Widget::keyPressEvent(QKeyEvent *event){ qDebug() << tr("Widget键盘按下事件");}// Widget类的事件过滤器bool Widget::eventFilter(QObject *obj, QEvent *event) // 事件过滤器{ // 如果是lineEdit部件上的事件 if(obj == lineEdit) { if(event->type() == QEvent::KeyPress) qDebug() << tr("Widget的事件过滤器"); } return QWidget::eventFilter(obj, event);}
这里也实现了Widget类的keyPressEvent()函数,并且会调用MyLineEdit类的keyPressEvent()函数。在事件过滤器中,先判断该事件的对象是不是lineEdit,如果是,再判断事件类型,最后返回QWidget类默认的事件过滤器的执行结果。
运行程序,然后按下键盘的任意键,比如这里按下a键,执行结果如下图所示。
可以看到,事件的传递顺序是这样的:先是事件过滤器,然后是焦点部件的event()函数,最后是焦点部件的事件处理函数,例如这里的键盘按下事件函数;如果焦点部件忽略了该事件,那么会执行父部件的事件处理函数,该部件的函数,最后是该部件的事件处理函数,如上图所示。注意,event()函数和事件处理函数,是在该部件内进行重新定义的,而事件过滤器却是在该部件的父部件中进行定义的。