【qt】qt事件分发与处理机制

14. qt事件处理机制


(0)事件分发处理流程

img

┌─────────────────────────────────────────────────────────────┐
│                    事件产生                                   │
│ 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()实现过滤器,在函数内部判断receiverQEvent->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()

img

所以,如果我们想自己处理事件,我们可以重写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表示事件未被处理
}
posted @ 2026-01-18 00:15  仰望星河Leon  阅读(12)  评论(0)    收藏  举报