QT 事件分发与事件过滤

一、事件分发器

        事件分发器是 QObject 基类提供的 虚函数 event(QEvent *e),所有 QObject 子类(如 QWidget、QDialog 等)都继承了该函数。其核心职责是:接收事件、判断事件类型、分发到对应事件处理器,或决定事件是否继续传递。

处理流程 : 事件源(鼠标/键盘/系统) → 事件过滤器(拦截/放行) → 事件分发器(event() 路由) → 事件处理器(如 mousePressEvent)

解读事件分发的完整路径:

  1. 事件产生:系统或应用程序生成事件(如鼠标点击),封装为QEvent对象。

  2. 事件传递
    1. 默认情况下:中间对象的event()函数或特定事件处理器(如mousePressEvent()不会自动触发,除非事件被显式传递(例如调用QWidget::event()或父类事件处理器)。
    2. 事件过滤器:如果中间对象安装了事件过滤器(installEventFilter()),过滤器的eventFilter()会优先被调用,可通过返回值决定是否拦截事件。
    3. event()eventFilter()中,可以通过返回true拦截事件(停止传递),或返回false继续传递。同一个类中过滤器优先级比重写的event()高。
  3. 目标对象处理
    1. 事件到达目标对象后,进入其event()函数,再分发到对应的事件处理器(如mousePressEvent())。
    2. 如果事件处理函数中未调用父类的实现(如QWidget::mousePressEvent()),事件可能被完全消费;否则可能继续向上传递。

关键点说明:

  1. 事件过滤器的优先级
    1. 事件过滤器(eventFilter())在event()之前执行,若返回true,事件会被拦截,不再进入目标对象的event()或处理函数。
  2. event()函数的作用
    1. 重写event()可以拦截所有事件,通过判断event->type()决定是否处理或传递。
    2. 若返回true,事件停止分发;若返回false,事件继续传递到处理函数或父类。
  3. 事件处理函数的默认行为
    1. 例如mousePressEvent()中若不调用父类实现,事件不会传递;若调用父类,可能触发父类的默认行为(如按钮按下动画)。

关键场景分析

场景1:中间对象未处理事件    
  • 若中间对象重写event()并调用父类实现,或通过事件过滤器返回false,事件可能继续传递。
  • 示例:
void ParentWidget::mousePressEvent(QMouseEvent *event) {
    // 默认不会自动触发,除非子对象调用父类实现
    qDebug() << "Parent will NOT receive child's event by default";
}
场景2:中间对象显式处理事件 
  • 若中间对象重写event()并调用父类实现,或通过事件过滤器返回false,事件可能继续传递。
  • 示例:
bool ParentWidget::event(QEvent *event) {
    if (event->type() == QEvent::MouseButtonPress) {
        qDebug() << "Parent intercepted event";
        // 返回false允许事件继续传递
        return false; 
    }
    return QWidget::event(event);
}
事件过滤器拦截
  • 中间对象可通过事件过滤器拦截事件,阻止其到达目标对象。
  • 示例:
// 安装事件过滤器
childWidget->installEventFilter(parentWidget);

// 在ParentWidget中重写eventFilter
bool ParentWidget::eventFilter(QObject *obj, QEvent *event) {
    if (event->type() == QEvent::MouseButtonPress) {
        qDebug() << "Event filtered by parent";
        return true; // 拦截事件
    }
    return false; // 继续传递
}

 二、事件过滤器

        事件过滤器是一种「观察者模式」的实现:一个对象(过滤器对象)可以监控另一个对象(目标对象)的所有事件,在事件到达目标对象前介入处理。

核心依赖两个关键接口

QObject::installEventFilter(QObject *filterObj):给目标对象安装过滤器(过滤器对象需重写 eventFilter());
QObject::eventFilter(QObject *watched, QEvent *event):过滤器的核心处理函数,接收目标对象和事件,返回 bool 决定事件是否放行。

demo : 设置事件过滤器,阻拦事件传递到上述的组件

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    bool eventFilter(QObject *,QEvent *);//重写事件过滤处理

private:
    Ui::Widget *ui;
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
#include <QDebug>

//主窗口
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    /*
        * 事件过滤器可以在程序分发到event事件之前再做一次高级拦截
        * 1.给控件安装事件过滤器
        * 2.重写eventfilter事件
    */
    //安装过滤器
    ui->widget_2->installEventFilter(this);
}

//重写eventfilter事件
bool Widget::eventFilter(QObject *obj,QEvent *ev)
{
	//对传递到自定义组件的事件进行拦截
    if(obj == ui->widget_2){
        if(ev->type() == QEvent::MouseButtonPress){
             qDebug()<< "事件过滤拦截";
            return true; //表示拦截
        }
    }
	//其他组件的事件交由父级处理
    return QWidget::eventFilter(obj,ev);
}

Widget::~Widget()
{
    delete ui;
}

  过滤器卸载           

// 窗口关闭时卸载
void MainWindow::closeEvent(QCloseEvent *e) {
    targetBtn->removeEventFilter(filter); // 卸载过滤器
    QWidget::closeEvent(e);
}

   全局过滤器

int main(int argc, char *argv[]) {
    QApplication a(argc, argv);

    MyGlobalFilter *globalFilter = new MyGlobalFilter(&a);
    a.installEventFilter(globalFilter); // 安装全局过滤器

    MainWindow w;
    w.show();

    return a.exec();
}

类型转换:
eventFilter() 接收的是 QEvent*(基类),若需获取事件详情(如鼠标坐标、键盘按键),需用 dynamic_cast 转换为对应事件子类(如 QMouseEvent*、QKeyEvent*),避免类型错误

bool MyFilter::eventFilter(QObject *watched, QEvent *event) {
    if (event->type() == QEvent::MouseMove) {
        // 安全转换为鼠标事件(多态类,dynamic_cast 确保转换有效)
        QMouseEvent *mouseEv = dynamic_cast<QMouseEvent*>(event);
        if (mouseEv) { // 转换成功才处理
            qDebug() << "鼠标移动坐标:" << mouseEv->pos();
        }
    }
    return false;
}

 三、区别与联系   

特性事件过滤器eventFilter()事件分发器event()
触发时机事件到达目标对象前事件到达目标对象后
作用范围可监控多个对象的事件仅处理单个对象内部事件
安装方式需调用 installEventFilter ()自动存在,可重写
典型场景跨组件监控、全局事件处理类内部事件处理、定制化响应

 四、 常见问题  

  1. 过滤器不触发?

    原因 1:未给目标对象调用 installEventFilter(filterObj)(忘记安装);
    原因 2:过滤器对象未重写 eventFilter()(函数签名错误,如漏写 override);
    原因 3:目标对象被销毁后,过滤器仍在监控(野指针);
    原因 4:事件被其他优先级更高的过滤器拦截(返回 true);
    解决:检查安装步骤、函数签名,用 qDebug() 打印 watched 和 event->type(),确认过滤器是否收到事件。


  2. 拦截事件后,目标对象功能失效?

    原因:返回 true 拦截了目标对象的核心事件(如按钮的鼠标按下事件),导致目标对象无法响应;
    解决:仅拦截需要的事件,非关键事件返回 false 放行;若需监控但不拦截,可在处理后返回 false。

  3. 内存泄漏?

    原因 1:过滤器对象是堆分配(new 创建),未手动释放(需确保父对象或目标对象管理其生命周期);
    原因 2:目标对象销毁后,未调用 removeEventFilter(),过滤器链中仍保留无效指针;
    解决:
    过滤器对象优先用栈分配(如 GlobalFilter filter;),或设父对象(new MyFilter(this));
    目标对象销毁前,调用 removeEventFilter() 卸载过滤器。

  4. 全局过滤器性能差?

    原因:全局过滤器会处理应用内所有事件(频繁触发如鼠标移动),若逻辑复杂会占用大量资源;

bool GlobalFilter::eventFilter(QObject *watched, QEvent *event) {
    // 优先判断事件类型,非键盘事件直接放行
    if (event->type() != QEvent::KeyPress) return false;
    // 后续处理键盘事件...
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值