MFC,WPF都有各自的事件系统,QT也不例外。事件(event)是由系统或者 Qt 本身在不同的时刻发出的。当用户按下鼠标、敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如键盘事件等;另一些事件则是由系统自动发出,如计时器事件。
Qt 程序需要在main()函数创建一个QApplication对象,然后调用它的exec()函数。这个函数就是开始 Qt 的事件循环。在执行exec()函数之后,程序将进入事件循环来监听应用程序的事件。当事件发生时,Qt 将创建一个事件对象。Qt 中所有事件类都继承于QEvent。在事件对象创建完毕后,Qt 将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是按照事件对象的类型分派给特定的事件处理函数(event handler)。
在Qt中,常见的有键盘事件QKeyEvent、鼠标事件QMouseEvent和定时器事件QTimerEvent等,它们与QEvent类的继承关系如图所示:
方法一:重新实现控件的paintEvent(),mousePressEvent()等事件处理函数。这是最常用也的一种方法,不过它只能用来处理特定部件的特定事件。
方法二:重新实现notify()函数。这个函数功能强大,提供了完全的控制,可以在事件过滤器得到事件之前就获得它们。但是,它一次只能处理一个事件。
方法三:向QApplication对象上安装事件过滤器。因为一个程序只有一个QApplication对象,所以这样实现的功能与使用notify()函数是相同的,优点是可以同时处理多个事件。
方法四:重新实现event()函数。QObject类的event()函数可以在事件到达默认的事件处理函数之前获得该事件。
方法五:在对象上安装事件过滤器。使用事件过滤器可以在一个界面类中同时处理不同子部件的不同事件。
在实际编程中,最常用的是方法一,其次是方法五。因为方法二需要继承自QApplication类;而方法三要使用一个全局的事件过滤器,这将减缓事件的传递,所以,虽然这两种方法功能很强大,但是却很少被用到。
项目结构
新建Widget,重写按键事件
- #ifndef WIDGET_H
- #define WIDGET_H
-
- #include
-
- QT_BEGIN_NAMESPACE
- namespace Ui { class Widget; }
- QT_END_NAMESPACE
-
- class MyLineEdit; // 类的前置声明
-
- class Widget : public QWidget
- {
- Q_OBJECT
- protected:
- void keyPressEvent(QKeyEvent *event) override;
-
- public:
- Widget(QWidget *parent = nullptr);
- ~Widget();
-
- private:
- Ui::Widget *ui;
- MyLineEdit* lineEdit;
-
- };
- #endif // WIDGET_H
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);
- }
-
- void Widget::keyPressEvent(QKeyEvent * event)
- {
- qDebug() << "Widget key PressEvent";
- }
-
- Widget::~Widget()
- {
- delete ui;
- }
新建MyLineEdit类,mylineedit.h
- #ifndef MYLINEEDIT_H
- #define MYLINEEDIT_H
- #include
-
- class MyLineEdit : public QLineEdit
- {
- Q_OBJECT
- public:
- MyLineEdit(QWidget *parent):QLineEdit(parent){};
- ~MyLineEdit();
- protected:
- void keyPressEvent(QKeyEvent *event) override;
-
- };
-
- #endif // MYLINEEDIT_H
mylineedit.cpp
- #include "mylineedit.h"
- #include
- #include
-
- void MyLineEdit::keyPressEvent(QKeyEvent * event)
- {
-
- qDebug() << "MyLineEdit key PressEvent";
- QLineEdit::keyPressEvent(event);
- }
-
-
- MyLineEdit::~MyLineEdit()
- {
-
- }
执行结果:
只执行了MyLineEdit的key事件
MyLineEdit中添加ignore;这样Widget的key事件也可以执行了
- void MyLineEdit::keyPressEvent(QKeyEvent * event)
- {
- qDebug() << "MyLineEdit key PressEvent";
- QLineEdit::keyPressEvent(event); // 执行QLineEdit的默认处理,不然无法输入字符
- event->ignore(); //忽略该事件
- }
结论:
从例子中可以看到,事件是先传递给指定窗口部件的。这里确切的说应该是先传递给获得焦点的窗口部件的。但是如果该部件忽略掉该事件(ignore),那么这个事件就会传递给这个部件的父部件。在重新实现事件处理函数时,一般要调用父类的相应的事件处理函数来实现默认的操作。
在MyLineEdit的头文件中添加函数声明:
bool event(QEvent *event) override;
该函数定义:
- bool MyLineEdit::event(QEvent * event)
- {
- if(event->type() == QKeyEvent::KeyPress) // 获取事件的类型
- {
- qDebug() << "MyLineEdit event";
- }
- return QLineEdit::event(event);//执行QLineEdit类event()函数的默认操作
- }
在widget.cpp中的构造函数中添加最后一行代码
-
- lineEdit = new MyLineEdit(this);
- lineEdit->move(100,100);
- lineEdit->installEventFilter(this);
同时在头文件中添加该事件过滤器的函数
- protected:
-
- bool eventFilter(QObject *obj, QEvent *event) override;
- bool Widget::eventFilter(QObject *obj, QEvent *event) // 事件过滤器
- {
- if(obj == lineEdit) { // 如果本部上的事件
- if(event->type() == QEvent::KeyPress){
- qDebug() << tr("Widget event Filter");
- }
- }
- return QWidget::eventFilter(obj, event);
- }
运行结果
可以看到,事件的传递顺序是这样的:先是事件过滤器,然后是焦点部件的event()函数,最后是焦点部件的事件处理函数;如果焦点部件忽略了该事件,那么会执行父部件的事件处理函数,如图所示。注意,event()函数和事件处理函数是在焦点部件内重新定义的,而事件过滤器却是在焦点部件的父部件中定义的。
参考: