Qt的事件系统
在Qt中,事件是对象,派生自抽象的QEvent类,它表示应用程序内部发生的事情或作为应用程序需要知道的外部活动的结果。事件可以由QObject子类的任何实例接收和处理,但它们与小部件特别相关。以下描述了在典型应用程序中如何传递和处理事件。
需要注意:
事件与信号并不相同,比如单击某个按钮,就会产生鼠标事件(QMouseEvent),这个事件不是按钮产生的,但因为按钮按下了,所以会发射clicked()
单击信号(这个是按钮产生的)。如果只关心信号呢,就不用考虑鼠标事件;但如果要设计一个按钮,或者单击产生别的效果,就要关心鼠标事件了。这两者是两个层面的东西,发出者不同,作用也不同。
此外,在Qt中,任何QObject子类实例都可以接收和处理事件。
QEvent类关系图
如下:
以下链接是拖放事件介绍和使用示例:
【Qt开发流程】之拖放操作1:介绍
链接: https://blog.csdn.net/MrHHHHHH/article/details/134626484
【Qt开发流程】之拖放操作2:使用
链接: https://blog.csdn.net/MrHHHHHH/article/details/134632006
事件类型
大多数事件类型都有特殊的类,特别是QResizeEvent
, QPaintEvent
, QMouseEvent
, QKeyEvent
和QCloseEvent
。每个类都是QEvent
的子类,并添加特定于事件的函数。例如,QResizeEvent
添加了size()和oldSize(),使小部件能够发现它们的尺寸是如何被改变的。
有些类支持不止一种实际事件类型。QMouseEvent
支持鼠标按键、双击、移动和其他相关操作。
每个事件都有一个关联的类型,在QEvent:: type
中定义,这可以用作运行时类型信息的方便来源,以快速确定给定事件对象是从哪个子类构造的。
由于程序需要以各种复杂的方式作出反应,Qt的事件传递机制是灵活的。
当事件发生时,Qt通过构造适当的QEvent子类的实例来创建一个事件对象来表示它,并通过调用其event()函数将其传递给QObject的特定实例(或其子类之一)。
此函数不处理事件本身;根据所交付的事件类型,它为该特定类型的事件调用事件处理程序,并根据该事件是被接受还是被忽略发送响应。
一些事件,如QMouseEvent和QKeyEvent,来自窗口系统;一些,如QTimerEvent,来自其他来源;有些来自应用程序本身。
事件的处理
如何处理一个事件呢?有以下5中处理事件的方法。
根据QCoreApplication::notify(QObject *receiver, QEvent *event)
:
发送事件给接收者:receiver->event(事件)。返回从接收者的事件处理程序返回的值。请注意,对于发送给任何线程中的任何对象的所有事件,都会调用此函数。
对于某些类型的事件(例如鼠标和键事件),如果接收者对事件不感兴趣(即,它返回false),事件将被传播到接收者的父对象,依此类推直到顶级对象。
处理事件有五种不同的方式;重新实现这个虚函数只是其中之一。下面列出了所有五种方法:
- 重新实现了paintEvent(), mousePressEvent()等。这是最常见、最简单、也是最不强大的方法。
- 重新实现这个函数
notify()
。这是非常强大的,提供完全的控制;但是一次只能有一个子类处于活动状态。 - 在QCoreApplication::instance()上安装事件过滤器。这样的事件过滤器能够处理所有小部件的所有事件,因此它与重新实现notify()一样强大;此外,可以有多个应用程序全局事件过滤器。全局事件过滤器甚至可以查看禁用小部件的鼠标事件。请注意,应用程序事件筛选器仅对位于主线程中的对象调用。
- 重新实现QObject::event()(与QWidget一样)。如果这样做,可以按Tab键,并且可以在任何特定于小部件的事件过滤器之前看到事件。
- 在对象上安装事件筛选器。这样的事件过滤器获取所有事件,包括Tab和Shift+Tab键按下事件,只要它们不改变焦点小部件。
在实际编程中,方法1最常用,其次是方法五,方法二和方法三虽然功能强大,但会减缓事件的传递,因此很少用到。
事件过滤
有时,对象需要查看(可能还需要拦截)传递给另一个对象的事件。例如,对话框通常需要过滤某些小部件的按键;例如,修改返回键处理。
QObject::installEventFilter()
函数通过设置一个事件过滤器来实现这一点,使指定的过滤器对象在其QObject::eventFilter()
函数中接收目标对象的事件。事件过滤器在目标对象处理事件之前处理事件,允许它根据需要检查和丢弃事件。现有的事件过滤器可以使用QObject::removeEventFilter()
函数删除。
当调用过滤器对象的eventFilter()实现时,它可以接受或拒绝事件,并允许或拒绝事件的进一步处理。如果所有事件过滤器都允许对事件进行进一步处理(通过每个过滤器返回false),则将事件发送到目标对象本身。如果其中一个停止处理(通过返回true),则目标和任何后续事件过滤器根本无法看到该事件。
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) {// Special tab handlingreturn true;} elsereturn false;}return false;}
上面的代码显示了拦截发送到特定目标小部件的Tab键按下事件的另一种方法。在这种情况下,过滤器处理相关事件并返回true以阻止它们被进一步处理。所有其他事件都被忽略,过滤器返回false以允许它们通过安装在目标小部件上的任何其他事件过滤器发送到目标小部件。
通过在QApplication或QCoreApplication对象上安装事件过滤器,也可以过滤整个应用程序的所有事件。在特定于对象的筛选器之前调用此类全局事件筛选器。这是非常强大的,但它也减慢了整个应用程序中每个事件的事件交付;通常应该使用讨论的其他技术。
事件的传递
每个程序main()
函数最后都会调用QApplication()
的exec()
方法,它会使Qt应用程序进入事件循环,这样就可以使应用程序在运行时接收发生的各种事件。一旦有事件发生,Qt便会构建一个相应的QEvent子类对象表示,之后它会传递相应的QObject对象或子对象。
传递事件的正常方式是调用虚函数。例如,QPaintEvent是通过调用QWidget::paintEvent()传递的。这个虚拟函数负责做出适当的反应,通常是通过重新绘制小部件。如果在虚函数的实现中没有执行所有必要的工作,则可能需要调用基类的实现。
例如,下面的代码处理自定义复选框小部件上的鼠标左键点击,同时将所有其他按钮点击传递给基类QCheckBox:
void MyCheckBox::mousePressEvent(QMouseEvent *event){if (event->button() == Qt::LeftButton) {// handle left mouse button here} else {// pass on other buttons to base classQCheckBox::mousePressEvent(event);}}
如果想替换基类的函数,必须自己实现所有的东西。但是,如果只想扩展基类的功能,那么可以实现想要的内容,并调用基类来获取不想处理的任何情况的默认行为。
有时,没有这样一个特定于事件的函数,或者特定于事件的函数是不够的。最常见的例子包括按Tab键。通常,QWidget会拦截这些键来移动键盘焦点,但是一些小部件本身需要Tab键。
这些对象可以重新实现QObject::event()
(通用事件处理程序),并在通常的处理之前或之后处理它们的事件,或者它们可以完全替换函数。一个非常不寻常的小部件,既解释Tab,又具有特定于应用程序的自定义事件,可能包含以下event()函数:
bool MyWidget::event(QEvent *event){if (event->type() == QEvent::KeyPress) {QKeyEvent *ke = static_cast<QKeyEvent *>(event);if (ke->key() == Qt::Key_Tab) {// special tab handling herereturn true;}} else if (event->type() == MyCustomEventType) {MyCustomEvent *myEvent = static_cast<MyCustomEvent *>(event);// custom event handling herereturn true;}return QWidget::event(event);}
注意,对于所有未处理的情况,仍然调用QWidget::event(),并且返回值指示是否处理了事件;true值防止将事件发送给其他对象。
事件传递流程图如下:
发送事件
许多应用程序都希望创建和发送它们自己的事件。通过构造合适的事件对象并使用QCoreApplication::sendEvent()
和QCoreApplication::postEvent()
发送事件,可以以与Qt自己的事件循环完全相同的方式发送事件。
sendEvent()
立即处理事件。当它返回时,事件过滤器和/或对象本身已经处理了事件。对于许多事件类,都有一个名为isAccepted()的函数,它告诉您事件是被最后调用的处理程序接受还是拒绝。
postEvent()
将事件发送到队列中以供以后调度。下次Qt的主事件循环运行时,它会调度所有发布的事件,并进行一些优化。例如,如果有多个调整大小事件,它们将被压缩为一个事件。这同样适用于绘制事件:QWidget::update()
调用postEvent()
,这消除了闪烁并通过避免多次重绘提高了速度。
postEvent()
也在对象初始化期间使用,因为提交的事件通常会在对象初始化完成后很快被分派。在实现小部件时,重要的是要认识到,事件可以在其生命周期的早期交付,因此,在其构造函数中,一定要在早期初始化成员变量,以免它有可能接收到事件。
要创建自定义类型的事件,需要定义一个事件号,该事件号必须大于QEvent::User
,并且可能需要创建QEvent
的子类,以便传递关于自定义事件的特定信息。
键盘事件
以下是一个示例,根据打印结果,看下其传递的整个流程:
新建一个自定义QLineEdit子类
customlineedit.h
#ifndef CUSTOMLINEEDIT_H
#define CUSTOMLINEEDIT_H#include <QLineEdit>class CustomLineEdit : public QLineEdit
{Q_OBJECT
public:explicit CustomLineEdit(QWidget *parent = nullptr);signals:public slots:protected:void keyPressEvent(QKeyEvent *e);bool event(QEvent *event);
};#endif // CUSTOMLINEEDIT_H
customlineedit.cpp
#include "customlineedit.h"#include <QKeyEvent>
#include <QDebug>CustomLineEdit::CustomLineEdit(QWidget *parent) : QLineEdit(parent)
{}void CustomLineEdit::keyPressEvent(QKeyEvent *e)
{qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << "自定义单行编辑框 键盘按下事件";QLineEdit::keyPressEvent(e);e->ignore();
}bool CustomLineEdit::event(QEvent *event)
{if(event->type() == QEvent::KeyPress){qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << "自定义单行编辑框 event()函数";}return QLineEdit::event(event);
}
主窗口:
mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>namespace Ui {
class MainWindow;
}class MainWindow : public QMainWindow
{Q_OBJECTpublic:explicit MainWindow(QWidget *parent = nullptr);~MainWindow();protected:void keyPressEvent(QKeyEvent *event);bool eventFilter(QObject *watched, QEvent *event);private:Ui::MainWindow *ui;
};#endif // MAINWINDOW_H
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"#include <QKeyEvent>
#include <QDebug>MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);ui->lineEdit->installEventFilter(this);
}MainWindow::~MainWindow()
{delete ui;
}void MainWindow::keyPressEvent(QKeyEvent *event)
{qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << "Mainwindow 键盘按下事件";
}bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{if(watched == ui->lineEdit){if(event->type() == QEvent::KeyPress){qDebug().noquote() << "[" << __FILE__ << __LINE__ << "]" << "Mainwindow 事件过滤器";}}return QMainWindow::eventFilter(watched, event);
}
运行,输出如下:
得出结论:
- 先进入父对象事件过滤器,判断对象是不是,是,判断类型,最后返回父类的过滤器事件;
- 进入目标对象的event()事件
- 进入目标对象的具体处理事件
- 进入父对象的事件
流程图如下:
结论
天没降大任于我,照样苦我心智,劳我筋骨
。