【qt】qt事件分发与处理机制
14. qt事件处理机制
(0)事件分发处理流程
┌─────────────────────────────────────────────────────────────┐
│ 事件产生 │
│ 1. 系统事件 (鼠标、键盘等) │
│ 2. 定时器事件 │
│ 3. 网络/IO事件 │
│ 4. 用户代码生成的事件 │
└──────────────────────────────┬──────────────────────────────┘
│
▼
┌─────────────────────┐
│ 如何处理事件? │
└──────────┬──────────┘
│
┌─────────────┴─────────────┐
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ 同步立即发送 │ │ 异步延迟发送 │
│ sendEvent( │ │ posetEvent()
│ QObject* receiver,│ │ │
│ QEvent* ev) │ │
└──────────┬──────────┘ └──────────┬──────────┘
│ │
▼ ▼
┌─────────────────────┐ ┌─────────────────────┐
│ 直接调用 │ │ 放入事件队列 │
│ notify() │ │ QEventQueue │
└─────────────────────┘ └──────────┬──────────┘
│
▼
┌─────────────────────┐
│ 事件循环取出 │
│ QEventLoop::exec() │
└──────────┬──────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ QCoreApplication::notify(QObject*, QEvent*) │
│ (应用程序级分发,分发到不同的子控件) │
│ │
│ 【调用 receiver->event() 前先检查包括通过install安装的在内的所有事件过滤器】
│ for (每个过滤器) { │
│ if (filter->eventFilter(receiver, event)) │
│ return true; // 返回true表示过滤器处理了事件 │
│ } │
│ │
│ 【有过滤器都未处理,才调用 receiver->event()】 │
│ return receiver->event(event); │
└──────────────────────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ QObject::event(QEvent*) │
│ (对象级事件分发,根据事件类型分发到不同的handler) │
│ │
│ switch (event->type()) { │
│ case QEvent::MouseButtonPress: │
│ mousePressEvent((QMouseEvent*)event); │
│ break; │
│ case QEvent::KeyPress: │
│ keyPressEvent((QKeyEvent*)event); │
│ break; │
│ case ... // 其他事件类型 │
│ } │
└──────────────────────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ 具体事件处理函数 │
│ 如: mousePressEvent(), keyPressEvent()等 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ sendEvent(receiver, event) │
│ (同步立即执行) │
├─────────────────────────────────────────────────────────────┤
│ 1. 直接调用 receiver->event(event) │
│ 2. 或者调用 QCoreApplication::notify(receiver, event) │
│ 3. 立即处理,阻塞当前线程 │
│ 4. 需要手动管理 event 的内存 │
└──────────────────────────────┬──────────────────────────────┘
▼
事件立即被处理,函数返回处理结果
QMouseEvent* event = new QMouseEvent(
QEvent::MouseButtonPress,
QPoint(100, 100),
Qt::LeftButton,
Qt::LeftButton,
Qt::NoModifier
);
// 同步发送,立即处理
bool result = QCoreApplication::sendEvent(widget, event);
// 需要手动删除 event,因此使用中这种事件最好创建在栈上不要new出来
delete event;
┌─────────────────────────────────────────────────────────────┐
│ postEvent(receiver, event) │
│ (异步延迟执行) │
├─────────────────────────────────────────────────────────────┤
│ 1. 将事件放入接收者的事件队列 │
│ 2. 返回立即,不等待处理结果 │
│ 3. 事件循环下次迭代时处理 │
│ 4. Qt 自动管理 event 的内存 │
└──────────────────────────────┬──────────────────────────────┘
▼
┌─────────────────────────────────────────────────────────────┐
│ 事件队列 (QEventQueue) │
│ 每个线程有自己的事件队列 │
│ 每个 QObject 有自己的待处理事件列表 │
└──────────────────────────────┬──────────────────────────────┘
▼
等待事件循环迭代
│
▼
┌─────────────────────────────────────────────────────────────┐
│ QEventLoop::processEvents() │
│ (处理事件队列) │
├─────────────────────────────────────────────────────────────┤
│ while (队列不为空) { │
│ 取出下一个事件 │
│ 调用 notify() 处理 │
│ } │
└──────────────────────────────┬──────────────────────────────┘
▼
进入正常的 notify() 流程
QMouseEvent* event = new QMouseEvent(
QEvent::MouseButtonPress,
QPoint(100, 100),
Qt::LeftButton,
Qt::LeftButton,
Qt::NoModifier
);
// 异步发送,放入队列
QCoreApplication::postEvent(widget, event);
// 立即返回,event 由 Qt 自动删除
Qt 使用一个事件队列对所有发出的事件进行维护,当新的事件产生时,会被追加到事件队列的尾部。前一个事件完成后,取出后面的事件进行处理。但是,必要的时候, Qt 的事件也可以不进入事件队列,而是直接处理。信号一旦发出,对应的槽函数一定会被执行。但是,事件则可以使用“事件过滤器”进行过滤,对于有些事件进行已有特定处理函数额外的处理,另外的事件则不关心。
在QApplication::exec()的事件循环中,会不断判断事件的产生,然后把产生的事件分派给对应的控件。比如,在QPushButton中产生了keyPressEvent()事件,这时候QApplication::exec()会把事件分派给QPushButton::event(),而如果QPushButton::event()忽略了keyPressEvent(),QMainWindow::event()可以选择是否对这个事件进行处理。
(1)重载特定事件处理函数
Qt提供了更具体的事件处理函数,这些函数可以更直接地用于处理特定的事件类型。
(2)事件过滤器
(2.1) 概念
事件过滤器:
理解为【拦截器】可能更加合适。它通过对receiver/QEvent进行判断,可以提前进行处理,然后通过return false事件继续向下分发,或者通过return true阻止其继续向下分发。
-
是
QObject的一个函数virtual bool QObject::eventFilter(QObject* watched, QEvent* event),凡是继承了QObject的类(不一定需要是可控件类)均可以override该函数,其他想使用该过滤器的控件就可以通过向installEventFilter()传入该对象,而将该过滤器安装在该控件上。
控件可以安装多个过滤器形成过滤链,任何一个事件都会经过所有的过滤器,若都没有return true,而是全部都返回return false则会进入【该控件】的QEvent(QEvent* ev)根据类型QEvent->type()分发到该控件处理具体事件类型的函数。 -
各个控件都有对不同类型事件的处理函数,比如:
onMouseMove()、onKeyPressed() -
一个事件过滤器是可以同时被多个控件进行安装的,因此也就可以同时监视多个控件。这也就是其参数
QObject watched存在的必要性,函数体中判断这个事件是发给谁的。这个参数并不是表明eventFilter()是否则将事件分发给不同控件的,这是不正确的理解。 -
一个控件可以安装多个过滤器,一个事件会经过该控件的所有过滤器拦截。
(2.2) 安装自定义过滤器拦截并处理事件
根据上面的叙述,通常的做法是在QObject的子类中override eventFilter()实现过滤器,在函数内部判断receiver或QEvent->type(),编写自己的的事件处理逻辑,一般自己处理后return true阻止事件继续分发到默认的处理函数上(如果是内置事件话,内置事件有默认的处理函数),return false则会继续向下分发。
处理了的事件就返回true,就给它过滤、拦截了,不会再向下转发。返回false,就表示继续向下转发。
-
多个过滤器时,最后安装的过滤器先被调用
filter1 = new Filter("Filter1", this); filter2 = new Filter("Filter2", this); widget->installEventFilter(filter1); // 第二个被调用 widget->installEventFilter(filter2); // 第一个被调用 // 调用顺序:filter2 → filter1 → widget 自身的 event() -
动态安装、卸载过滤器
EventFilter* filter = new EventFilter(this); // 开启过滤 widget->installEventFilter(filter); // 关闭过滤 widget->removeEventFilter(filter); -
因为只要继承了
QObject的类都可以override eventFilter(),因此控件安装过滤器有几种可能性:-
控件安装自身的过滤器
class MyWidget : public QWidget { public: MyWidget() { // 安装自己过滤自己的事件 installEventFilter(this); // this 过滤 this // 这是真正的"自己过滤" // MyWidget 过滤 MyWidget 自己的事件 } bool eventFilter(QObject* watched, QEvent* event) override { // watched 将是 this(MyWidget 自己) if (watched == this) { qDebug() << "过滤自己的事件"; } return QWidget::eventFilter(watched, event); } }; -
父控件过滤子控件的事件(主要使用)
class MyWidget : public QWidget { public: MyWidget() { button = new QPushButton("Click", this); // 父窗口(MyWidget)过滤子控件(button)的事件 button->installEventFilter(this); // 更准确的说法: // "MyWidget 实例成为 button 的事件过滤器" // 或 // "button 让 MyWidget 过滤它的事件" } }; -
专门的类封装过滤器,实践中可对某一类事件封装一个类or某类控件封装一个类
//专门处理键盘事件 class KeyFilter : public QObject { public: bool eventFilter(QObject* obj, QEvent* event) override { return QObject::eventFilter(obj, event); } }; //使用 KeyFilter* keyHandler = new KeyFilter(this); button->installEventFilter(keyHandler); //KeyFilter 实例将过滤 button 的事件
-
#include <QTextEdit>
#include <QObject>
#include <QEvent>
class MainWindow : public QMainWindow
{
Q_OBJECT
private:
QTextEdit *textedit;
protected:
bool eventFilter(QObject *obj, QEvent *event);
public:
MainWindow();
~MainWindow();
};
MainWindow::MainWindow()
{
textedit=new QTextEdit;
setCentralWidget(textedit);
textedit->installEventFilter(this);//子控件textedit安装父控件MainWindow的过滤器
}
//MainWindow override了过滤器
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if(obj==textedit)//拦截textedit控件的事件
{
if(event->type()==QEvent::KeyPress) //拦截textedit的KeyPress事件
{
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); //把event转为实际类型
qDebug() << "Ate key press" << keyEvent->key();
return true;
}
else
{
return false;
}
if(event->type()==QEvent::HoverEnter)
{
qDebug() << "Mouse hovered over the widget";
return true;
}
else
{
return false;
}
}
return QMainWindow::eventFilter(obj,event); //没安装过滤器的obj,调用默认的过滤器
}
注:
-
不使用obj判断过滤的对象的话,则所有安装了该事件过滤器的子控件都会被过滤事件并处理。
-
如果你在事件过滤器中 delete 了某个接收组件,务必将函数返回值设为 true。否则,Qt 还是会将事件分发给这个接收组件,从而导致程序崩溃。事件过滤器和被安装过滤器的组件
-
必须在同一线程,否则,过滤器将不起作用。另外,如果在安装过滤器之后,这两个组件到了
-
不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。
事件过滤器给我们一种能力,让我们能够完全移除某种事件。事件过滤器可以安装到任意QObject 类型上面,并且可以安装多个。如果要实现全局的事件过滤器,则可以安装到QApplication 或者 QCoreApplication 上面。这里需要注意的是,如果使用installEventFilter()函数给一个对象安装事件过滤器,那么该事件过滤器只对该对象有效,
只有这个对象的事件需要先传递给事件过滤器的 eventFilter()函数进行过滤,其它对象不受影响。如果QApplication 对象安装事件过滤器,那么该过滤器对程序中的每一个对象都有效,任何对象的事件都是先传给 eventFilter()函数。
(3)全局事件过滤器
qt中给QApplication对象一个宏定义qApp,给它安装过滤器,过滤器将作用在程序所有的对象都有效。
全局事件过滤器通常是在主窗口MainWindow中实现的
qApp->installEventFilter(this);
// MainWindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QWidget>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
protected:
bool eventFilter(QObject *watched, QEvent *event) override;
private:
void installGlobalEventFilter();
};
#endif // MAINWINDOW_H
// MainWindow.cpp
#include "MainWindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
{
installGlobalEventFilter();
}
MainWindow::~MainWindow()
{
}
bool MainWindow::eventFilter(QObject *watched, QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
// 处理按键事件
if (keyEvent->key() == Qt::Key_Space) {
// 例如,当按下空格键时,执行某些操作
return true; // 表示事件已处理
}
}
// 标准事件处理
return QMainWindow::eventFilter(watched, event);
}
void MainWindow::installGlobalEventFilter()
{
// 安装全局事件过滤器
qApp->installEventFilter(this);
}
(4)重载event()
所以,如果我们想自己处理事件,我们可以重写QPushButton::event()事件,也可以重写QPushButton::keyPressEvent()。
一般不用这个
event()是每个控件都有的,每个要过滤的事件的控件,都得重载写event,麻烦。所以在事件过滤器中【提前拦截】事件进行处理,再让控件安装该过滤器是更好的实践。
事件过滤器可以解决刚刚我们提出的 event()函数的两点不足:首先,事件过滤器不是protected 的,因此我们可以向任何 QObject 子类安装事件过滤器;其次,事件过滤器在目标对象接收到事件之前进行处理,如果我们将事件过滤掉,目标对象根本不会见到这个事件。
MyWidget是一个自定义的控件,它重载了event函数来处理不同类型的事件。event函数接收一个QEvent对象作为参数,你可以通过检查这个对象的类型来决定如何处理它。如果事件被处理了,函数应该返回true;如果事件没有被处理,函数应该返回false,这样Qt就会继续将事件传递给其他下游可能的处理器。
QEvent::type()函数可以检查事件的实际类型,其返回值是 QEvent::Type类型的枚举
isAccept()表示该事件已经被处理。event()返回值,当事件被处理返回true,否则返回false。
isAccepted()方法`在事件处理链的下游使用,避免重复处理已经被其他处理器接受的事件。
bool MyWidget::event(QEvent *event)
{
switch (event->type()) {
case QEvent::KeyPress
// 处理Type1类型的事件
QKeyEvent *keyEvent=static_cast<QKeyEvent *>(e);
if(keyEvent->key()==Qt::Key_Tab)
{
qDebug()<<"tab键被按下";
return true;
}
// 标记事件已经被接受并处理
event->setAccepted(true);
break;
case QEvent::Type2:
// 处理Type2类型的事件
event->setAccepted(true);
break;
// 可以添加更多的case来处理不同类型的事件
default:
// 对于未处理的事件,调用基类的event函数,执行默认的事件处理
return QWidget::event(event);
}
return true; // 返回true表示事件已被处理,false表示事件未被处理
}

浙公网安备 33010602011771号