# Qt学习05：Event事件（处理分发传播与过滤）

category: 后端开发
date: 2023/06/02
status: Published
summary: Qt学习记录 - Event事件消息详解
tags: Qt
type: Post

**文章首发于我的个人博客：[欢迎大佬们来逛逛](https://www.helloylh.com/)**

# **事件系统**

> The Event System
> 

在Qt中，事件是派生自抽象`QEvent`类的对象，它表示应用程序内发生的事情或应用程序需要知道的外部活动的结果。事件可以由QObject子类的任何实例接收和处理，但它们与小部件尤其相关。

Qt程序需要在main()函数创建一个QApplication对象，然后调用它的exec()函数。这个函数就是开始Qt的事件循环。在执行exec()函数之后，程序将进入事件循环来监听应用程序的事件，当事件发生时，Qt将创建一个**事件对象**。

## **事件是如何传递的？**

当事件发生时，Qt通过构造适当的`QEvent`子类的实例来创建一个事件对象来表示它，并通过调用它的event()函数将它交付给QObject的一个特定实例(或它的一个子类)。

这个函数`event()`不处理事件本身；**根据交付的事件类型，它为该特定类型的事件调用事件处理程序，并根据事件是被接受还是被忽略发送响应。**

一些事件，如QMouseEvent和QKeyEvent，来自窗口系统；还有一些，比如QTimerEvent，来自其他来源；有些来自应用程序本身。

## **事件类型**

大多数事件类型都有特殊的类，特别是QResizeEvent、QPaintEvent、QMouseEvent、QKeyEvent和QCloseEvent。**每个类都是QEvent的子类**，并添加特定于事件的函数。例如，QResizeEvent添加了size()和oldSize()，以使小部件能够发现它们的尺寸是如何被更改的。

有些类支持多个实际事件类型。QMouseEvent支持按下鼠标按钮、双击、移动和其他相关操作。

每个事件都有一个**关联的类型**，在QEvent:: type中定义，这可以用作运行时类型信息的方便来源，以快速确定给定事件对象是从哪个子类构建的。

由于程序需要以各种复杂的方式进行响应，Qt的事件传递机制非常灵活。

- QCoreApplication::notify()的文档简洁地讲述了整个故事;
- Qt季刊(Qt Quarterly)的文章《另一个看事件》[(Another Look at Events)](https://doc.qt.io/archives/qq/qq11-events.html)对它进行了重新梳理，但没有那么简洁。这里我们将对95%的应用程序进行足够的解释。

## **事件处理**

传递事件的通常方式是调用**虚函数**。例如，QPaintEvent通过调用QWidget::paintEvent()来传递。这个虚函数负责进行适当的响应，通常是重新绘制小部件。如果在虚函数的实现中不执行所有必要的工作，则可以调用基类的实现。

### **事件处理函数**

例如，下面的代码处理自定义按钮控件上的**鼠标左键单击**，同时将所有其他按钮单击传递给基本QPushButton类:

```cpp
void CustomButton::mousePressEvent(QMouseEvent* ev)override
{
    if (ev->button() == Qt::MouseButton::LeftButton)
    {
        //在这里处理鼠标左键
        qInfo() << "left buttondown";
    }
    else
    {
        //将其他案件传递给基类处理
        QPushButton::mousePressEvent(ev);
    }
}
```

注意：如果这样做了，按钮的clicked()和pressed()信号将不能被触发！

```cpp
class CustomButton : public QPushButton
{
public:
    CustomButton(QWidget* parent = nullptr)
        :QPushButton(parent)
    {
        //不能触发信号
        connect(this, &QPushButton::clicked, this, []() {qInfo() << "clicked"; });
        connect(this, &QPushButton::pressed, this, []() {qInfo() << "pressed"; });
    }
    void mousePressEvent(QMouseEvent* ev)override
    {
        if (ev->button() == Qt::MouseButton::LeftButton)
        {
            //在这里处理鼠标左键
            qInfo() << "left buttondown";
        }
        else
        {
            //将其他案件传递给基类处理
            QPushButton::mousePressEvent(ev);
        }
    }
};
```

想要让父类也能够处理左键消息，可以把父类的mousePressEvent放在最后调用(不放在else中)。

```cpp
void CustomButton::mousePressEvent(QMouseEvent* ev)override
{
    if (ev->button() == Qt::MouseButton::LeftButton)
    {
        //在这里处理鼠标左键
        qInfo() << "left buttondown";
    }
    //将其他案件传递给基类处理
    QPushButton::mousePressEvent(ev);
}
```

---

## 鼠标事件

### **鼠标按下**

```cpp
void CustomButton::mousePressEvent(QMouseEvent* ev)override
{
    //获取鼠标按键 类型为枚举:Qt::MouseButton::
    qInfo() << ev->button();

    //如果同时有多个鼠标按键按下,需要判断左键是否按下
    qInfo() << (ev->buttons() & Qt::MouseButton::LeftButton);

    //获取鼠标坐标 position()返回浮点坐标，可以使用pos()获取整型坐标
    qInfo() << ev->position();          //鼠标在本控件上的坐标
    qInfo() << ev->scenePosition();     //鼠标在本控件所在的窗口位置
    qInfo() << ev->globalPosition();    //鼠标相对于屏幕的坐标

    //将其他案件传递给基类处理
    QPushButton::mousePressEvent(ev);
}
```

### **鼠标释放**

```cpp
void CustomButton::mouseReleaseEvent(QMouseEvent*ev)override
{
    QPushButton::mouseReleaseEvent(ev);
}
```

### **鼠标双击**

```cpp
void CustomButton::mouseDoubleClickEvent(QMouseEvent*ev)override
{
    qInfo() << __FUNCTION__;
}
```

### **鼠标移动**

如果关闭了鼠标跟踪，则只有在移动鼠标时按下鼠标按钮时才会发生鼠标移动事件。如果打开了鼠标跟踪，即使没有按下鼠标按钮，也会发生鼠标移动事件。

按钮是自动追踪鼠标的，但是QWidget是不会的，如果要给QWidget的子类重写鼠标移动，需要使用`void setMouseTracking(bool enable)`启用鼠标追踪。

```cpp
void CustomButton::mouseMoveEvent(QMouseEvent* ev)override
{
    qInfo() << __FUNCTION__ << ev->pos();
}
```

### **鼠标滚轮**

返回轮子旋转的相对量，单位为八分之一度。正值表示转轮向前旋转，远离用户；负值表示转轮向后向用户旋转。angleDelta().y()提供自上一个事件以来旋转普通垂直鼠标滚轮的角度。如果鼠标有水平滚轮，angleDelta().x()提供水平鼠标滚轮旋转的角度，否则就是0。有些鼠标允许用户倾斜滚轮来进行水平滚动，有些触摸板支持水平滚动手势；它也会出现在angleDelta().x()中。

大多数鼠标类型的工作步长为15度，在这种情况下，delta值是120的倍数；即120单位* 1/8 = 15度。

然而，有些鼠标的滚轮分辨率更高，发送的delta值小于120单位(小于15度)。为了支持这种可能性，可以累计添加来自事件的增量值，直到达到120的值，然后滚动小部件，或者可以部分滚动小部件以响应每个轮事件。

```cpp
void CustomButton::wheelEvent(QWheelEvent* ev)override
{
    //获取滚轮滚动方向
    QPoint numDegrees = ev->angleDelta();
    qInfo() <<"水平:" << numDegrees.x()/8 <<"垂直:" << numDegrees.y()/8;
}
```

### **Example：无边框窗口拖动**

在做窗口应用程序时，为了使窗口更简洁，有时候会将窗口setWindowFlag();属性设置为`Qt::WindowType::FramelessWindowHint`，这时，窗口将不可拖动，但我们可以通过以下代码尝试解决无边框窗体的拖动问题。

步骤：

1. 在`mousePressEvent`时获取鼠标点击的pos局部位置，并且保存
2. 在`mouseMoveEvent`时与该起始位置相减得到移动的位置。

**代码：**

```cpp
class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget* parent = nullptr)
        :QWidget(parent)
    {
        setWindowFlag(Qt::WindowType::FramelessWindowHint);
    }
    void mousePressEvent(QMouseEvent*ev)override
    {
        pressPos = ev->pos(); //保存起始位置
    }
    void mouseMoveEvent(QMouseEvent* ev)override
    {
        if (ev->buttons() & Qt::MouseButton::LeftButton)
        {
            move(ev->globalPosition().toPoint() - pressPos); //获取全局位置
        }
    }
private:
    QPoint pressPos;
};
```

---

## 键盘事件

### **按键按下**

```cpp
void keyPressEvent(QKeyEvent* ev)override
{
    //获取按键
    qInfo() <<"key:" << Qt::Key(ev->key());
    //返回此键生成的Unicode文本。不同平台按下Shift、Control、Alt和Meta等修饰键时的返回值不同，可能返回空字符串。
    qInfo() << "text:" << ev->text();

    //获取按下了什么键盘修饰符(shift、control、alt、meta(windows键)、keypad(小键盘，即数字键))
    qInfo() << ev->modifiers();
    //1,判断是否按下了组合键(快捷键、加速键) 事实上，**我们可以忽略这种方法，直接使用下面的第二个方法**
    if (ev->modifiers() & Qt::KeyboardModifier::ControlModifier && ev->key() == Qt::Key::Key_S)
    {
        qInfo() << "1 保存，保存";
    }

    //2,判断是否按下了组合键，方便的函数： 简单！！！
    if (ev->matches(QKeySequence::StandardKey::Save))
    {
        qInfo() <<"2 保存、保存";
    }
}
```

### **按键释放**

```cpp
void keyReleaseEvent(QKeyEvent* ev)override
{
    //同按下
}
```

### **Example：按钮移动**

```cpp
class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget* parent = nullptr)
        :QWidget(parent)
    {
        setWindowFlag(Qt::WindowType::FramelessWindowHint);
        resize(640, 480);
        button = new QPushButton("按钮", this);
        button->setFocusPolicy(Qt::FocusPolicy::NoFocus);
    }
    void keyPressEvent(QKeyEvent* ev)override
    {
        switch (ev->key())
        {
        case Qt::Key::Key_Up:
            button->move(button->pos() + QPoint(0, -1));
            break;
        case Qt::Key::Key_Down:
            button->move(button->pos() + QPoint(0, 1));
            break;
        case Qt::Key::Key_Left:
            button->move(button->pos() + QPoint(-1, 0));
            break;
        case Qt::Key::Key_Right:
            button->move(button->pos() + QPoint(1, 0));
            break;
        }
    }
private:
    QPushButton* button = nullptr;
};
```

---

## **窗口事件**

### **窗口关闭**

当Qt从窗口系统接收到一个顶级小部件的窗口关闭请求时，将用给定的事件调用此事件处理程序。

默认情况下，接受事件并关闭小部件。您可以重新实现此函数，以更改小部件响应窗口关闭请求的方式。例如，您可以通过在所有事件上调用ignore()来防止窗口关闭。

主窗口应用程序通常使用该函数的重新实现来检查用户的工作是否已保存，并在关闭前请求权限。

```cpp
void Widget::closeEvent(QCloseEvent* ev)override
{
    auto ret = QMessageBox::question(this, "温馨提示", "你有未保存的操作，是否保存并关闭？");
    if (ret == QMessageBox::StandardButton::Yes)
    {
        //保存并关闭
        ev->accept();
    }
    else
    {
        //不保存也不关闭
        ev->ignore();
    }
}
```

### **窗口隐藏、显示**

除隐藏和显示窗口外，窗口最小化会发送窗口隐藏事件，正常显示会发送窗口显示事件。

```cpp
void Widget::showEvent(QShowEvent* ev)override
{
    qInfo() << "我显示啦~";
}
void Widget::hideEvent(QHideEvent* ev)override
{
    qInfo() << "我隐藏啦~";
}
```

### **窗口移动**

```cpp
void Widget::moveEvent(QMoveEvent* ev)override
{
    qInfo() << "Widget moved" << "oldPos" << ev->oldPos() << "newPos" << ev->pos();
}
```

### **窗口大小改变**

```cpp
void  Widget::resizeEvent(QResizeEvent* ev)override
{
    qInfo() << "Widget SizeChanged" << "oldSize" << ev->oldSize() << "newSize" << ev->size();
}
```

### **窗口焦点**

这个事件处理程序可以在子类中重新实现，以接收小部件的键盘焦点事件(焦点接收)。

小部件通常必须将focuspolicy()设置为Qt::NoFocus以外的东西，以便接收焦点事件。(注意，程序员可以在任何小部件上调用setFocus()，即使是那些通常不接受焦点的小部件。)

默认实现更新小部件(不指定focusPolicy()的窗口除外)。

```cpp
void  Widget::focusInEvent(QFocusEvent* event)override
{
    qInfo() << "我有焦点啦？";
}
void  Widget::focusOutEvent(QFocusEvent* event)override
{
    qInfo() << "我有焦点没啦？";
}
```

### example: 编辑框颜色改变

重写此 `QLineEdit` 以实现焦点放到此上面会实现颜色的改变，焦点取消则恢复。

```cpp
class LineEdit:public QLineEdit{
public:
    using QLineEdit::QLineEdit;
    void focusInEvent(QFocusEvent* ev)override{
        this->setStyleSheet("background-color: lightblue");
    }
    void focusOutEvent(QFocusEvent* ev)override{
        this->setStyleSheet("background-color: white");
    }
private:

};
```

### **右键菜单**

```cpp
 void  Widget::contextMenuEvent(QContextMenuEvent* ev)
 {
     qInfo() << "报告长官，请求弹出上下文菜单，也就是右键菜单！"
             << "请弹出在\n"
             << "全局坐标:"<<ev->globalPos()
             << "局部坐标:"<<ev->pos() << "位置";
 }
```

### **程序状态改变**

如果需要检测程序中，某些东西是否发生了改变，可以通过`void QWidget::changeEvent(QEvent *event)`来检测。

- 以下是常用事件：
    - QEvent::FontChange
    - QEvent::WindowTitleChange
    - QEvent::IconTextChange
    - QEvent::ModifiedChange
    - QEvent::MouseTrackingChange
    - QEvent::WindowStateChange
- 以`QEvent::WindowStateChange`窗口状态改变为例！

```cpp
void Widget::changeEvent(QEvent* ev)override
{
    if (ev->type() == QEvent::WindowStateChange)
    {
        auto we = static_cast<QWindowStateChangeEvent*>(ev);
        qInfo() << "oldState" << we->oldState();
        qInfo() << "curState" << this->windowState();
    }
}
```

### **定时器事件**

```cpp
class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget* parent = nullptr)
        :QWidget(parent)
    {
         //启动一个定时器，并返回定时器ID
        _timerID = startTimer(1000);
    }
    void timerEvent(QTimerEvent* ev)override
    {
        if (ev->timerId() == _timerID)
        {
            //把控件往右移动5个像素
            move(this->pos() + QPoint(5, 0));
            if(x()>1080)
            {
                 killTimer(_timerID);
            }
            qInfo()<<_timerID<<"timeout";
        }
    }
private:
    int _timerID;
};
```

### **拖拽事件(剪切板)**

> Drag and Drop
> 

设置`QWidget`窗口接受拖拽：

```cpp
setAcceptDrops(true);
```

```cpp
void dragEnterEvent(QDragEnterEvent *ev);
void dropEvent(QDropEvent *ev)
```

之后通过 QMimaData来获得内容：

```cpp
auto thing=ev->mimeData();
```

---

# **事件分发**

传递事件的通常方式是调用**虚函数**。例如，QMouseEvent通过调用QWidget::mousePressEvent()来传递。这个虚函数负责进行适当的响应，通常是处理鼠标点击，并发出对应的信号。如果在虚函数的实现中不执行所有必要的工作，则可能需要调用基类的实现。

如果希望替换基类的事件处理函数，则必须自己实现所有内容。但是，如果您只想扩展基类的功能，那么您可以实现您想要的功能，并调用基类来获得您不想处理的任何情况的默认行为。

有时，没有这样一个特定于事件的函数，或者特定于事件的函数是不够的。最常见的例子是按Tab键。通常情况下，QWidget会拦截这些来移动键盘焦点，但也有一些小部件本身需要Tab键。

这些对象可以重新实现QObject::event()(通用事件处理程序)，并在通常处理之前或之后执行它们的事件处理，或者它们可以完全替换函数。一个非常不寻常的小部件既解释Tab又具有特定于应用程序的自定义事件，它可能包含以下event()函数:

```cpp
bool Widget::event(QEvent* ev)override
{
    //如果是按键按下事件
    if (ev->type() == QEvent::Type::KeyPress)
    {
        QKeyEvent* ke = static_cast<QKeyEvent*>(ev);
        if (ke->key() == Qt::Key::Key_Tab)
        {
            //对tab键按下进行处理
            qInfo() << "处理 tab 按键...";
            return true;    //返回true表示此事件已经处理了，不会继续传播
        }
    }
    return QWidget::event(ev);
}
```

注意，对于所有未处理的情况，仍然调用QWidget::event()，并且返回值指示是否处理了事件;true值防止事件被发送到其他对象。

### **Example：无边框窗口拖动**

前面通过**事件处理函数**实现了无边框窗口的拖动，如何通过**事件分发函数**实现呢？

```cpp
bool event(QEvent* ev)override
{
    if (ev->type() == QEvent::MouseButtonPress)
    {
        auto me = static_cast<QMouseEvent*>(ev);
        pressPos = me->pos();
        return true;
    }
    else if (ev->type() == QEvent::MouseMove)
    {
        auto me = static_cast<QMouseEvent*>(ev);
        //如果鼠标左键是按下的
        if(me->buttons() & Qt::MouseButton::LeftButton)
            this->move(me->globalPosition().toPoint() - pressPos);
        return true;
    }
    return QWidget::event(ev);
}
```

## **发送事件**

许多应用程序都希望创建和发送它们自己的事件。通过构造合适的事件对象并使用`QCoreApplication::sendEvent()`和`QCoreApplication::postEvent()`发送事件，您可以以与Qt自己的事件循环完全相同的方式发送事件。

**sendEvent()**立即处理事件。当它返回时，事件过滤器和/或对象本身已经处理了该事件。对于许多事件类，都有一个名为isAccepted()的函数，它告诉您事件是被最后一个调用的处理程序接受还是拒绝的。

**postEvent()**将事件发送到队列中，以便稍后进行分派。下次Qt的主事件循环运行时，它会分发所有发布的事件，并进行一些优化。例如，如果有几个调整大小事件，它们将被压缩为一个。同样适用于绘制事件:QWidget::update()调用postEvent()，它通过避免多次重绘来消除闪烁并提高速度。

### **自定义事件**

要创建自定义类型的事件，需要定义一个事件号，该事件号必须大于QEvent::User，并且可能需要继承QEvent的子类，以便传递关于自定义事件的特定信息。有关更多详细信息，请参阅QEvent文档。

- 定义事件号

```cpp
enum EventType
{
    NumberEvent = QEvent::Type::User
};
```

- 定义事件类

```cpp
class NumberChangeEvent : public QEvent
{
public:
    NumberChangeEvent(qint32 num)
        :QEvent(QEvent::Type(EventType::NumberEvent)) //关键一步！ 注册事件类型枚举
        , _number(num)
    {
    }
    ~NumberChangeEvent()
    {
        qInfo() << __FUNCTION__;
    }
    qint32 number() { return _number; }
private:
    qint32 _number = 0;
};
```

- 发送和处理事件

```cpp
class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget* parent = nullptr)
        :QWidget(parent)
    {
        resize(640, 480);
        setWindowFlag(Qt::WindowType::FramelessWindowHint);

        button = new QPushButton("发送事件", this);
        //点击按钮发送NumberChangeEvent事件
        connect(button, &QPushButton::clicked, this, [=]()
            {
                static int i = 0;
                NumberChangeEvent ne(i++);
                QApplication::sendEvent(this, &ne);
            });
    }
    //customEvent专门用来接受自定义事件
    void customEvent(QEvent* ev)override
    {
        if (ev->type() == EventType::NumberEvent)
        {
            auto ne = static_cast<NumberChangeEvent*>(ev);
            qInfo() << "number Event"<<ne->number();
        }
    }
private:
    QPushButton* button = nullptr;
};
```

---

### **sendEvent、postEvent区别**

### **sendEvent**

```cpp
connect(button, &QPushButton::clicked, this, [=]()
    {
        static int i = 0;

        //会自动释放
        NumberChangeEvent ne(i);
        QApplication::sendEvent(this, &ne);
        //需要手动释放
        auto pne = new NumberChangeEvent(i);
        QApplication::sendEvent(this, pne);
        delete pne;

        ++i;
    });
```

sendEvent会让接受事件的对象立即处理事件，处理完成之后函数返回

- 对于栈区的事件会**自动释放**(超出作用域释放)
- 对于堆区的事件需要函数返回之后**自己释放** (超出作用域，也不会自动释放)

### **postEvent**

```cpp
connect(button, &QPushButton::clicked, this, [=]()
    {
        static int i = 0;
        //不能放在栈区
        //---1,postEvent提交的事件，会在事件处理后，自动delete(栈区的无法delete)
        //---2,因为事件提交之后还没有处理，这个函数结束会释放事件（访问错误）
        //NumberChangeEvent ne(i);
        //QApplication::postEvent(this, &ne);

        //只能放在堆区，但是不需要手动释放
        auto pne = new NumberChangeEvent(i);
        QApplication::postEvent(this, pne);
        ++i;
    });
```

postEvent是把事件提交到事件队列，不管有没有处理事件，都立即返回

- **必须把事件放在堆区**，处理完毕之后自动释放

---

# **事件传播**

## **事件分发**

Qt的事件产生之后，不是直接传递给了对象的，需要经过一系列的过程。

1. 事件首先由Qt的ServerApplication去接收来自于外部或内部的一些行为，鼠标点击，键盘输入，时钟事件等，分析并决定送往对应的对象去处理(内部管理机制)，最后会调用[virtual] bool QCoreApplication::notify ( QObject * receiver, QEvent * event ) 去处理，当然这个是虚函数，你可以在子类去重新实现它 。
2. 在notify(…)中，在发给对应的接收者前，会先把消息送给QApplication。所以如果想在你界面的Widget前先处理那些事 件，那么你可以给QApplication对象installEventFilter,然后在对应的eventFilter()里先把这些事件都给过一 遍，然后你可以过滤一些不必要事件。
3. 如果QApplication没有处理那些事件，然后就是交给事件接收对象了。在这个对象接收前，也可以为这对象加一个事件过滤器，同样是 installEventFilter。
4. 如果eventFilter没有过滤某些事件，那么就会将事件交给接收者的event()函数（你可以根据不同类型的事件尽情处理），如果event事件在接受者处理后，也不会上传给父类的event，否则会上传进入父类的event。
5. 默认event()函数根据事件类型会调用不同的事件处理函数,类似mousePressEvent(),keyPressEvent()去分别处理他们。

## **事件传播**

当控件忽略事件时，事件会继续往上传播，这里的传播是指**传播给父组件**。

- QEvent有 `accept()`函数 和`ignore()`函数：
    - **accept()：**本组件处理该事件，这个事件就不会被继续传播给其父组件；
    - **ignore()：**本组件不想要处理这个事件，这个事件会被继续传播给其父组件；
    - 值得注意的是所有的事件默认都是接受的
- 忽略和接受案例

```cpp
class Button : public QPushButton
{
public:
	Button(const QString& text,QWidget* parent = nullptr)
		:QPushButton(text,parent)
	{

	}
	void mousePressEvent(QMouseEvent* ev)override
	{
		qInfo() << __FUNCTION__;
		//默认是接受事件的，父组件不会收到鼠标点击事件
		//ev->accept();
		//如果忽略事件，父组件就会收到鼠标点击事件
		//ev->ignore();
	}
};

class Widget : public QWidget
{
	Q_OBJECT
public:
	Widget(QWidget* parent = nullptr)
		:QWidget(parent)
	{
		resize(640, 480);
		auto button = new Button("button", this);
	}
	void mousePressEvent(QMouseEvent* ev)override
	{
		qInfo() << __FUNCTION__;
	}
};
```

- event()，eventFilter()函数返回值作用

```cpp
class Button : public QPushButton
{
public:
	Button(const QString& text,QWidget* parent = nullptr)
		:QPushButton(text,parent)
	{

	}
	bool event(QEvent* ev)override
	{
		if (ev->type() == QEvent::Type::MouseButtonPress)
		{
			qInfo() << __FUNCTION__;
			//return false;	//返回false，表示我没有处理这个事件，可以传递给父组件
			//return true;	//返回true，表示我处理了这个事件，不再继续传递了
		}
		return QPushButton::event(ev);
	}
};

class Widget : public QWidget
{
	Q_OBJECT
public:
	Widget(QWidget* parent = nullptr)
		:QWidget(parent)
	{
		resize(640, 480);
		auto button = new Button("button", this);
	}
	bool event(QEvent* ev)override
	{
		if (ev->type() == QEvent::Type::MouseButtonPress)
		{
			qInfo() << __FUNCTION__;
		}
		return QWidget::event(ev);
	}
};
```

## **事件过滤**

有时，对象需要查看(可能还需要拦截)传递给另一个对象的事件。例如，对话框通常想要过滤一些小部件的按键;例如，修改返回键处理。

QObject::installEventFilter()函数通过设置一个事件过滤器来实现这一点，使指定的过滤器对象在其QObject::eventFilter()函数中接收目标对象的事件。事件过滤器在目标对象处理事件之前处理事件，允许它根据需要检查和丢弃事件。可以使用QObject::removeEventFilter()函数删除现有的事件过滤器。

当调用过滤器对象的eventFilter()实现时，它可以接受或拒绝事件，并允许或拒绝对事件的进一步处理。如果所有事件过滤器都允许对事件进行进一步处理(每个返回false)，则将事件发送到目标对象本身。如果其中一个停止处理(通过返回true)，目标和任何后面的事件过滤器都不会看到事件。

```cpp
 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 handling
             return true;
         } else
             return false;
     }
     return false;
 }
```

上面的代码展示了截获发送到特定目标小部件的Tab键按下事件的另一种方法。在这种情况下，过滤器处理相关事件并返回true以阻止它们被进一步处理。所有其他事件都被忽略，过滤器返回false，允许通过安装在目标小部件上的任何其他事件过滤器将它们发送到目标小部件。

通过在QApplication或QCoreApplication对象上安装事件过滤器，还可以为整个应用程序筛选所有事件。这种全局事件过滤器在特定于对象的过滤器之前调用。这是非常强大的，但它也减慢了整个应用程序中每个单个事件的事件交付;通常应该使用讨论的**其他技术**。

处理事件有五种不同的方法，以下列出了所有五种方法:

**其他技术：**

- 重新实现paintEvent()， mousePressEvent()等。这是最常见、最简单、最强大的方法。
- 重新实现`bool QCoreApplication::notify()`。这是非常强大的，提供完全的控制，但是一次只能有一个子类是活动的(就是说不能实现不同方式的处理，因为实例化应用程序对象只能有一个)。
- 在QCoreApplication::instance()上安装一个事件过滤器。这样的事件过滤器能够处理所有小部件的所有事件，因此它与重新实现notify()一样强大；此外，可以有多个应用程序全局事件过滤器。全局事件过滤器甚至可以看到禁用小部件的鼠标事件。注意，应用程序事件过滤器只对驻留在主线程中的对象调用。
- 重新实现QObject::event()(像QWidget那样)。如果您这样做，就可以按Tab键，并且可以在任何特定于小部件的事件过滤器之前看到事件。
- 在对象上安装事件过滤器。这样的事件过滤器获得所有事件，包括Tab和Shift+Tab键按下事件，只要它们不改变焦点小部件。

### **Example：无边框窗口拖动**

前面通过事件处理函数、事件分发实现了无边框窗口的拖动，如何通过事件过滤实现呢？

```cpp
class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget* parent = nullptr)
        :QWidget(parent)
    {
        resize(640, 480);
        setWindowFlag(Qt::WindowType::FramelessWindowHint);
        setMouseTracking(true);
        this->installEventFilter(this);
    }
    bool eventFilter(QObject* object, QEvent* ev)override
    {
        auto w = qobject_cast<QWidget*>(object);
        if (!w)
            return false;

        if (ev->type() == QEvent::MouseButtonPress)
        {
            auto me = static_cast<QMouseEvent*>(ev);
            pressPos = me->pos();
            return true;
        }
        else if (ev->type() == QEvent::MouseMove)
        {
            auto me = static_cast<QMouseEvent*>(ev);
            if (me->buttons() & Qt::MouseButton::LeftButton)
                w->move(me->globalPosition().toPoint() - pressPos);
             return true;
        }
        return QObject::eventFilter(object, ev);
    }
private:
    QPoint pressPos;
};
```

这是最简单的，在自己类里面重写eventFilter来过滤事件，但是如果我有很多个无边框窗口，都要实现移动，这个方法就不太方便了。

可以先写一个拖拽控件的过滤器对象，然后哪里需要就在那里添加！

```cpp
class DragWidgetFilter :public QObject
{
public:
    DragWidgetFilter(QObject* parent = nullptr)
        :QObject(parent)
    {

    }
    bool eventFilter(QObject* object,QEvent*ev)override
    {
        auto w = qobject_cast<QWidget*>(object);
        if (!w)
            return false;

        if (ev->type() == QEvent::MouseButtonPress)
        {
            auto me = static_cast<QMouseEvent*>(ev);
            pressPos = me->pos();
        }
        else if (ev->type() == QEvent::MouseMove)
        {
            auto me = static_cast<QMouseEvent*>(ev);
            if (me->buttons() & Qt::MouseButton::LeftButton)
                w->move(me->globalPosition().toPoint() - pressPos);
        }
        return QObject::eventFilter(object,ev);
    }
private:
    QPoint pressPos;
};

class Widget : public QWidget
{
    Q_OBJECT
public:
    Widget(QWidget* parent = nullptr)
        :QWidget(parent)
    {
        resize(640, 480);
        setWindowFlag(Qt::WindowType::FramelessWindowHint);
        setMouseTracking(true);
        this->installEventFilter(new DragWidgetFilter(this));
    }
}
```

# **事件类型**

## **QEvent::Type**

这个枚举类型定义了Qt中有效的事件类型。事件类型和每个类型的专门类如下：

| 常量 | 值 | 描述 |
| --- | --- | --- |
| QEvent::None | 0 | 不是一个事件 |
| QEvent::ActionAdded | 114 | 一个新 action 被添加（QActionEvent） |
| QEvent::ActionChanged | 113 | 一个 action 被改变（QActionEvent） |
| QEvent::ActionRemoved | 115 | 一个 action 被移除（QActionEvent） |
| QEvent::ActivationChange | 99 | Widget 的顶层窗口激活状态发生了变化 |
| QEvent::ApplicationActivate | 121 | 这个枚举已被弃用，使用 ApplicationStateChange 代替 |
| QEvent::ApplicationActivated | ApplicationActivate | 这个枚举已被弃用，使用 ApplicationStateChange 代替 |
| QEvent::ApplicationDeactivate | 122 | 这个枚举已被弃用，使用 ApplicationStateChange 代替 |
| QEvent::ApplicationFontChange | 36 | 应用程序的默认字体发生了变化 |
| QEvent::ApplicationLayoutDirectionChange | 37 | 应用程序的默认布局方向发生了变化 |
| QEvent::ApplicationPaletteChange | 38 | 应用程序的默认调色板发生了变化 |
| QEvent::ApplicationStateChange | 214 | 应用程序的状态发生了变化 |
| QEvent::ApplicationWindowIconChange | 35 | 应用程序的图标发生了变化 |
| QEvent::ChildAdded | 68 | 一个对象获得孩子（QChildEvent） |
| QEvent::ChildPolished | 69 | 一个部件的孩子被抛光（QChildEvent） |
| QEvent::ChildRemoved | 71 | 一个对象时区孩子（QChildEvent） |
| QEvent::Clipboard | 40 | 剪贴板的内容发生改变 |
| QEvent::Close | 19 | Widget 被关闭（QCloseEvent） |
| QEvent::CloseSoftwareInputPanel | 200 | 一个部件要关闭软件输入面板（SIP） |
| QEvent::ContentsRectChange | 178 | 部件内容区域的外边距发生改变 |
| QEvent::ContextMenu | 82 | 上下文弹出菜单（QContextMenuEvent） |
| QEvent::CursorChange | 183 | 部件的鼠标发生改变 |
| QEvent::DeferredDelete | 52 | 对象被清除后将被删除（QDeferredDeleteEvent） |
| QEvent::DragEnter | 60 | 在拖放操作期间鼠标进入窗口部件（QDragEnterEvent） |
| QEvent::DragLeave | 62 | 在拖放操作期间鼠标离开窗口部件（QDragLeaveEvent） |
| QEvent::DragMove | 61 | 拖放操作正在进行（QDragMoveEvent） |
| QEvent::Drop | 63 | 拖放操作完成（QDropEvent） |
| QEvent::DynamicPropertyChange | 170 | 动态属性已添加、更改或从对象中删除 |
| QEvent::EnabledChange | 98 | 部件的 enabled 状态已更改 |
| QEvent::Enter | 10 | 鼠标进入部件的边界（QEnterEvent） |
| QEvent::EnterEditFocus | 150 | 编辑部件获得焦点进行编辑，必须定义 QT_KEYPAD_NAVIGATION |
| QEvent::EnterWhatsThisMode | 124 | 当应用程序进入“What’s This?”模式，发送到 toplevel 顶层部件 |
| QEvent::Expose | 206 | 当其屏幕上的内容无效，发送到窗口，并需要从后台存储刷新 |
| QEvent::FileOpen | 116 | 文件打开请求（QFileOpenEvent） |
| QEvent::FocusIn | 8 | 部件或窗口获得键盘焦点（QFocusEvent） |
| QEvent::FocusOut | 9 | 部件或窗口失去键盘焦点（QFocusEvent） |
| QEvent::FocusAboutToChange | 23 | 部件或窗口焦点即将改变（QFocusEvent） |
| QEvent::FontChange | 97 | 部件的字体发生改变 |
| QEvent::Gesture | 198 | 触发了一个手势（QGestureEvent） |
| QEvent::GestureOverride | 202 | 触发了手势覆盖（QGestureEvent） |
| QEvent::GrabKeyboard | 188 | Item 获得键盘抓取（仅限 QGraphicsItem） |
| QEvent::GrabMouse | 186 | 项目获得鼠标抓取（仅限 QGraphicsItem） |
| QEvent::GraphicsSceneContextMenu | 159 | 在图形场景上的上下文弹出菜单（QGraphicsScene ContextMenuEvent） |
| QEvent::GraphicsSceneDragEnter | 164 | 在拖放操作期间，鼠标进入图形场景（QGraphicsSceneDragDropEvent） |
| QEvent::GraphicsSceneDragLeave | 166 | 在拖放操作期间鼠标离开图形场景（QGraphicsSceneDragDropEvent） |
| QEvent::GraphicsSceneDragMove | 165 | 在场景上正在进行拖放操作（QGraphicsSceneDragDropEvent） |
| QEvent::GraphicsSceneDrop | 167 | 在场景上完成拖放操作（QGraphicsSceneDragDropEvent） |
| QEvent::GraphicsSceneHelp | 163 | 用户请求图形场景的帮助（QHelpEvent） |
| QEvent::GraphicsSceneHoverEnter | 160 | 鼠标进入图形场景中的悬停项（QGraphicsSceneHoverEvent） |
| QEvent::GraphicsSceneHoverLeave | 162 | 鼠标离开图形场景中一个悬停项（QGraphicsSceneHoverEvent） |
| QEvent::GraphicsSceneHoverMove | 161 | 鼠标在图形场景中的悬停项内移动（QGraphicsSceneHoverEvent） |
| QEvent::GraphicsSceneMouseDoubleClick | 158 | 鼠标在图形场景中再次按下（双击）（QGraphicsSceneMouseEvent） |
| QEvent::GraphicsSceneMouseMove | 155 | 鼠标在图形场景中移动（QGraphicsSceneMouseEvent） |
| QEvent::GraphicsSceneMousePress | 156 | 鼠标在图形场景中按下（QGraphicsSceneMouseEvent） |
| QEvent::GraphicsSceneMouseRelease | 157 | 鼠标在图形场景中释放（QGraphicsSceneMouseEvent） |
| QEvent::GraphicsSceneMove | 182 | 部件被移动（QGraphicsSceneMoveEvent） |
| QEvent::GraphicsSceneResize | 181 | 部件已调整大小（QGraphicsSceneResizeEvent） |
| QEvent::GraphicsSceneWheel | 168 | 鼠标滚轮在图形场景中滚动（QGraphicsSceneWheelEvent） |
| QEvent::Hide | 18 | 部件被隐藏（QHideEvent） |
| QEvent::HideToParent | 27 | 子部件被隐藏（QHideEvent） |
| QEvent::HoverEnter | 127 | 鼠标进入悬停部件（QHoverEvent） |
| QEvent::HoverLeave | 128 | 鼠标留离开悬停部件（QHoverEvent） |
| QEvent::HoverMove | 129 | 鼠标在悬停部件内移动（QHoverEvent） |
| QEvent::IconDrag | 96 | 窗口的主图标被拖走（QIconDragEvent） |
| QEvent::IconTextChange | 101 | 部件的图标文本发生改变（已弃用） |
| QEvent::InputMethod | 83 | 正在使用输入法（QInputMethodEvent） |
| QEvent::InputMethodQuery | 207 | 输入法查询事件（QInputMethodQueryEvent） |
| QEvent::KeyboardLayoutChange | 169 | 键盘布局已更改 |
| QEvent::KeyPress | 6 | 键盘按下（QKeyEvent） |
| QEvent::KeyRelease | 7 | 键盘释放（QKeyEvent） |
| QEvent::LanguageChange | 89 | 应用程序翻译发生改变 |
| QEvent::LayoutDirectionChange | 90 | 布局的方向发生改变 |
| QEvent::LayoutRequest | 76 | 部件的布局需要重做 |
| QEvent::Leave | 11 | 鼠标离开部件的边界 |
| QEvent::LeaveEditFocus | 151 | 编辑部件失去编辑的焦点，必须定义 QT_KEYPAD_NAVIGATION |
| QEvent::LeaveWhatsThisMode | 125 | 当应用程序离开“What’s This?”模式，发送到顶层部件 |
| QEvent::LocaleChange | 88 | 系统区域设置发生改变 |
| QEvent::NonClientAreaMouseButtonDblClick | 176 | 鼠标双击发生在客户端区域外 |
| QEvent::NonClientAreaMouseButtonPress | 174 | 鼠标按钮按下发生在客户端区域外 |
| QEvent::NonClientAreaMouseButtonRelease | 175 | 鼠标按钮释放发生在客户端区域外 |
| QEvent::NonClientAreaMouseMove | 173 | 鼠标移动发生在客户区域外 |
| QEvent::MacSizeChange | 177 | 用户更改了部件的大小（仅限 OS X） |
| QEvent::MetaCall | 43 | 通过 QMetaObject::invokeMethod() 调用异步方法 |
| QEvent::ModifiedChange | 102 | 部件修改状态发生改变 |
| QEvent::MouseButtonDblClick | 4 | 鼠标再次按下（QMouseEvent） |
| QEvent::MouseButtonPress | 2 | 鼠标按下（QMouseEvent） |
| QEvent::MouseButtonRelease | 3 | 鼠标释放（QMouseEvent） |
| QEvent::MouseMove | 5 | 鼠标移动（QMouseEvent） |
| QEvent::MouseTrackingChange | 109 | 鼠标跟踪状态发生改变 |
| QEvent::Move | 13 | 部件的位置发生改变（QMoveEvent） |
| QEvent::NativeGesture | 197 | 系统检测到手势（QNativeGestureEvent） |
| QEvent::OrientationChange | 208 | 屏幕方向发生改变（QScreenOrientationChangeEvent） |
| QEvent::Paint | 12 | 需要屏幕更新（QPaintEvent） |
| QEvent::PaletteChange | 39 | 部件的调色板发生改变 |
| QEvent::ParentAboutToChange | 131 | 部件的 parent 将要更改 |
| QEvent::ParentChange | 21 | 部件的 parent 发生改变 |
| QEvent::PlatformPanel | 212 | 请求一个特定于平台的面板 |
| QEvent::PlatformSurface | 217 | 原生平台表面已创建或即将被销毁（QPlatformSurfaceEvent） |
| QEvent::Polish | 75 | 部件被抛光 |
| QEvent::PolishRequest | 74 | 部件应该被抛光 |
| QEvent::QueryWhatsThis | 123 | 如果部件有“What’s This?”帮助，应该接受事件 |
| QEvent::ReadOnlyChange | 106 | 部件的 read-only 状态发生改变 |
| QEvent::RequestSoftwareInputPanel | 199 | 部件想要打开软件输入面板（SIP） |
| QEvent::Resize | 14 | 部件的大小发生改变（QResizeEvent） |
| QEvent::ScrollPrepare | 204 | 对象需要填充它的几何信息（QScrollPrepareEvent） |
| QEvent::Scroll | 205 | 对象需要滚动到提供的位置（QScrollEvent） |
| QEvent::Shortcut | 117 | 快捷键处理（QShortcutEvent） |
| QEvent::ShortcutOverride | 51 | 按下按键，用于覆盖快捷键（QKeyEvent） |
| QEvent::Show | 17 | 部件显示在屏幕上（QShowEvent） |
| QEvent::ShowToParent | 26 | 子部件被显示 |
| QEvent::SockAct | 50 | Socket 激活，用于实现 QSocketNotifier |
| QEvent::StateMachineSignal | 192 | 信号被传递到状态机（QStateMachine::SignalEvent） |
| QEvent::StateMachineWrapped | 193 | 事件是一个包装器，用于包含另一个事件（QStateMachine::WrappedEvent） |
| QEvent::StatusTip | 112 | 状态提示请求（QStatusTipEvent） |
| QEvent::StyleChange | 100 | 部件的样式发生改变 |
| QEvent::TabletMove | 87 | Wacom 写字板移动（QTabletEvent） |
| QEvent::TabletPress | 92 | Wacom 写字板按下（QTabletEvent） |
| QEvent::TabletRelease | 93 | Wacom 写字板释放（QTabletEvent） |
| QEvent::OkRequest | 94 | Ok 按钮在装饰前被按下，仅支持 Windows CE |
| QEvent::TabletEnterProximity | 171 | Wacom 写字板进入接近事件（QTabletEvent），发送到 QApplication |
| QEvent::TabletLeaveProximity | 172 | Wacom 写字板离开接近事件（QTabletEvent），发送到 QApplication |
| QEvent::ThreadChange | 22 | 对象被移动到另一个线程。这是发送到此对象的最后一个事件在上一个线程中，参见：QObject::moveToThread() |
| QEvent::Timer | 1 | 定时器事件（QTimerEvent） |
| QEvent::ToolBarChange | 120 | 工具栏按钮在 OS X 上进行切换 |
| QEvent::ToolTip | 110 | 一个 tooltip 请求（QHelpEvent） |
| QEvent::ToolTipChange | 184 | 部件的 tooltip 发生改变 |
| QEvent::TouchBegin | 194 | 触摸屏或轨迹板事件序列的开始（QTouchEvent） |
| QEvent::TouchCancel | 209 | 取消触摸事件序列（QTouchEvent） |
| QEvent::TouchEnd | 196 | 触摸事件序列结束（QTouchEvent） |
| QEvent::TouchUpdate | 195 | 触摸屏事件（QTouchEvent） |
| QEvent::UngrabKeyboard | 189 | Item 失去键盘抓取（QGraphicsItem） |
| QEvent::UngrabMouse | 187 | Item 失去鼠标抓取（QGraphicsItem、QQuickItem） |
| QEvent::UpdateLater | 78 | 部件应该排队在以后重新绘制 |
| QEvent::UpdateRequest | 77 | 部件应该被重绘 |
| QEvent::WhatsThis | 111 | 部件应该显示“What’s This”帮助（QHelpEvent） |
| QEvent::WhatsThisClicked | 118 | 部件的“What’s This”帮助链接被点击 |
| QEvent::Wheel | 31 | 鼠标滚轮滚动（QWheelEvent） |
| QEvent::WinEventAct | 132 | 发生了 Windows 特定的激活事件 |
| QEvent::WindowActivate | 24 | 窗口已激活 |
| QEvent::WindowBlocked | 103 | 窗口被模态对话框阻塞 |
| QEvent::WindowDeactivate | 25 | 窗户被停用 |
| QEvent::WindowIconChange | 34 | 窗口的图标发生改变 |
| QEvent::WindowStateChange | 105 | 窗口的状态（最小化、最大化或全屏）发生改变（QWindowStateChangeEvent） |
| QEvent::WindowTitleChange | 33 | 窗口的标题发生改变 |
| QEvent::WindowUnblocked | 104 | 一个模态对话框退出后，窗口将不被阻塞 |
| QEvent::WinIdChange | 203 | 本地窗口的系统标识符发生改变 |
| QEvent::ZOrderChange | 126 | 部件的 z 值发生了改变，该事件不会发送给顶层窗口 |

用户事件的值应该介于 QEvent:: 和 QEvent::MaxUser之间。

| 常量 | 值 | 描述 |
| --- | --- | --- |
| QEvent::User | 1000 | 用户定义的事件 |
| QEvent::MaxUser | 65535 | 最后的用户事件 ID |

为方便起见，可以使用 `[static] int QEvent::registerEventType(int *hint* = -1)` 函数来注册和存储一个自定义事件类型，这样做会避免意外地重用一个自定义事件类型。

# **事件和信号的区别**

|  | 事件（QEvent） | 信号（SIGNAL） |
| --- | --- | --- |
| 与QObject的关系 | 由具体对象进行处理 | 由具体对象主动产生 |
| 对程序影响 | 改写事件处理函数可能导致程序行为发生改变 | 信号是否存在对应的槽函数不会改变程序行为 |
| 两者的联系 | 一般而言，信号在具体的事件处理函数中产生 |  |

信号和事件是两个不同层面的东西，发出者不同，作用不同。Qt中，所有的QObject的子类实例均可对事件接收和处理！

# **自定义按钮**

[知乎-Qt 中的事件系统](https://zhuanlan.zhihu.com/p/50053079)

[QGraphicsView](https://www.cnblogs.com/aiguona/category/1347051.html)

# 本节完整代码

```cpp
# Widget.h
--------------
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QDebug>
#include <QMessageBox>
#include <QMoveEvent>
#include <QResizeEvent>
#include <QPushButton>
#include <QLineEdit>
#include <QMimeData>
#include <QTimer>
#include <QEvent>
#include <QApplication>

class Button:public QPushButton{
public:
    using QPushButton::QPushButton;
    void enterEvent(QEnterEvent* ev)override{
        setStyleSheet("background-color:red");
    }
    void leaveEvent(QEvent* ev)override{
        setStyleSheet("background-color:white");
    }
    //测试事件传播
    void mousePressEvent(QMouseEvent* ev)override{
        //默认接受，当前组件接受事件处理，不会传播给父控件了
        //ev->accept();
        qInfo()<<"Button::mousePressEvent";

        //如果忽略，则会传播给父控件处理，当前不处理
        //ev->ignore();
    }
    bool event(QEvent* ev)override{
        if (ev->type()==QEvent::Type::MouseButtonPress){
            qInfo()<<"Button::event::MouseButtonPress";
            //return false; //会传递给父控件
            //return true;  //不会传递给父控件
        }
        return QPushButton::event(ev);
    }
private:

};
class LineEdit:public QLineEdit{
public:
    using QLineEdit::QLineEdit;
    void focusInEvent(QFocusEvent* ev)override{
        this->setStyleSheet("background-color: lightblue");
    }
    void focusOutEvent(QFocusEvent* ev)override{
        this->setStyleSheet("background-color: white");
    }
private:
};

//自定义事件
enum EventType{
    My_Event=QEvent::Type::User+1
};
class CustomEvent:public QEvent{
public:
    CustomEvent(int age,const QString& name)
        :QEvent(QEvent::Type(EventType::My_Event)),age(age),name(name){}
    void show(){
        qInfo()<<name<<age;
    }
private:
    int age;
    QString name;
};

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
public:
    //关闭事件，close时调用
    void closeEvent(QCloseEvent* ev)override;
    void showEvent(QShowEvent* ev)override;
    void moveEvent(QMoveEvent* ev)override;
    void resizeEvent(QResizeEvent* ev)override;

    void enterEvent(QEnterEvent* ev)override;
    void leaveEvent(QEvent* ev)override;

    void mousePressEvent(QMouseEvent* ev)override;
    void mouseReleaseEvent(QMouseEvent* ev)override;
    void mouseDoubleClickEvent(QMouseEvent* ev)override;
    void mouseMoveEvent(QMouseEvent* ev)override;
    void wheelEvent(QWheelEvent* ev)override;

    void keyPressEvent(QKeyEvent* ev)override;
    void keyReleaseEvent(QKeyEvent* ev)override;

    void focusInEvent(QFocusEvent* ev)override;
    void focusOutEvent(QFocusEvent* ev)override;

    void changeEvent(QEvent* ev)override;

    void contextMenuEvent(QContextMenuEvent* ev)override;

    void dragEnterEvent(QDragEnterEvent* ev)override;
    void dropEvent(QDropEvent* ev)override;

    void timerEvent(QTimerEvent* ev)override;

    bool event(QEvent* ev)override; //事件分发函数
    void customEvent(QEvent* ev)override; //处理自定义事件的分发函数
    void testEventDiffuse(); //事件传播
public:
    //定时器
    void testTimer();
    void testDIYEvent();
public slots:
    //定时器处理的槽函数
    void onTimerHandle();
private:
    Button* btn;
    QPoint pressPos;
    LineEdit* edit;
    int timeId;
    int cnt=0;

};
#endif // WIDGET_H
```

```cpp
# Widget.cpp
-------------
#include "widget.h"

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    resize(640,480);

    //启用鼠标追踪:mousemove事件
    setMouseTracking(true);
    //设置窗口无边框
    //setWindowFlags(Qt::WindowType::FramelessWindowHint);
    //设置关闭时销毁
    setAttribute(Qt::WidgetAttribute::WA_DeleteOnClose);
    //设置窗口接受拖拽
    setAcceptDrops(true);

    btn=new Button("变色按钮");
    btn->show();

    edit=new LineEdit(this);
    edit->setGeometry(100,100,100,30);
    edit->setFocus();

    testEventDiffuse();
}

Widget::~Widget()
{
    btn->deleteLater();
    edit->deleteLater();
}

void Widget::closeEvent(QCloseEvent *ev)
{
    //QMessageBox::warning(this,"提示","你有未保存的操作，是否继续")   ;
    qInfo()<<__FUNCTION__;
}

void Widget::showEvent(QShowEvent *ev)
{
    qInfo()<<__FUNCTION__;
}

void Widget::moveEvent(QMoveEvent *ev)
{
    qInfo()<<__FUNCTION__<<ev->oldPos()<<ev->pos();
}

void Widget::resizeEvent(QResizeEvent *ev)
{
    qInfo()<<__FUNCTION__<<ev->oldSize()<<ev->size();
}

void Widget::enterEvent(QEnterEvent *ev)
{

}

void Widget::leaveEvent(QEvent *ev)
{

}

void Widget::mousePressEvent(QMouseEvent* ev){
    //qInfo()<<"Widget::mousePressEvent";
    if (ev->button()==Qt::MouseButton::LeftButton){
        btn->move(btn->pos()+QPoint(5,0));

        //保存第一次拖动的点
        pressPos=ev->pos();
    }
}
void Widget::mouseReleaseEvent(QMouseEvent* ev){
    if (ev->button()==Qt::MouseButton::LeftButton){
        btn->move(btn->pos()+QPoint(0,5));
    }
}
void Widget::mouseDoubleClickEvent(QMouseEvent* ev){
    //qInfo()<<ev->button();
}
void Widget::mouseMoveEvent(QMouseEvent* ev){
    //qInfo()<<"Widget::mouseMoveEvent";

    //如果有鼠标按下再移动，则无法通过ev->button获得按键
    //通过buttons获得之前已经捕获的状态
    if (ev->buttons() & Qt::MouseButton::LeftButton){
        //qInfo()<<__FUNCTION__<<ev->buttons();

        //拖动窗口
        this->move(ev->globalPosition().toPoint()-pressPos);
    }
}
void Widget::wheelEvent(QWheelEvent* ev){
    //qInfo()<<__FUNCTION__<<ev->angleDelta()<<ev->angleDelta().x(),ev->angleDelta().y();
}

void Widget::keyPressEvent(QKeyEvent *ev)
{
    //Q_UNUSED(ev);
    switch (ev->key()) {
    case Qt::Key_Up:
        btn->move(btn->pos()+QPoint(0,-1));
        break;
    case Qt::Key_Left:
        btn->move(btn->pos()+QPoint(-1,0));
        break;
    case Qt::Key_Right:
        btn->move(btn->pos()+QPoint(1,0));
        break;
    case Qt::Key_Down:
        btn->move(btn->pos()+QPoint(0,1));
        break;
    default:
        break;
    }

    //快捷键
    if (ev->matches(QKeySequence::SelectAll)){
        qInfo()<<"Ctrl + A";
    }
}

void Widget::keyReleaseEvent(QKeyEvent *ev)
{

}

void Widget::focusInEvent(QFocusEvent *ev)
{
    //焦点事件
}

void Widget::focusOutEvent(QFocusEvent *ev)
{

}

void Widget::changeEvent(QEvent *ev)
{
    //qInfo()<<__FUNCTION__;
}

void Widget::contextMenuEvent(QContextMenuEvent *ev)
{
    qInfo() << "报告长官，请求弹出上下文菜单，也就是右键菜单！"
            << "请弹出在\n"
            << "全局坐标:"<<ev->globalPos()
            << "局部坐标:"<<ev->pos() << "位置";
}

void Widget::dragEnterEvent(QDragEnterEvent *ev)
{
    //拖拽
    ev->acceptProposedAction(); //接受拖拽
    auto thing=ev->mimeData();
    //qInfo()<<thing->text();
}

void Widget::dropEvent(QDropEvent *ev)
{
    const QMimeData* thing=ev->mimeData();
    //qInfo()<<thing->text();
}

void Widget::timerEvent(QTimerEvent *ev)
{
    //处理定时器事件
    if (ev->timerId()==timeId){
        //qInfo()<<__FUNCTION__<<timeId;
    }
}

bool Widget::event(QEvent *ev)
{
    //在此来控制相应事件的处理
    switch (ev->type()) {
    case QEvent::Type::MouseButtonPress:
        qInfo()<<"Widget::event::MouseButtonPress";
        break;
    default:
        break;
    }
    return QWidget::event(ev); //事件分发
}

void Widget::customEvent(QEvent *ev)
{
    switch (ev->type()) {
    case EventType::My_Event:
    {
        auto cev=static_cast<CustomEvent*>(ev);
        cev->show();
        break;
    }
    default:
        break;
    }
}

void Widget::testEventDiffuse()
{
    //事件传播
    auto btn=new Button("事件传播",this);
    btn->setGeometry(10,10,100,30);
}

void Widget::testTimer()
{
    QTimer* time=new QTimer(this);

    //1. 方法一
    //connect(time,&QTimer::timeout,this,&Widget::onTimerHandle);
    //2. 方法二： 推荐！
    time->callOnTimeout(this,&Widget::onTimerHandle); //绑定槽函数
    time->start(1000); //每隔1s进行一次

    //只会触发一次的定时器
    QTimer::singleShot(5000,[](){qInfo()<<"我只会被调用一次!";});

    timeId = startTimer(2000); //QObject的定时器

}

void Widget::testDIYEvent()
{
    auto btn=new QPushButton("发送事件",this);
    connect(btn,&QPushButton::clicked,this,[=](){
        //1. 使用sendEvent发送事件: 栈区变量，堆区需要delete
        CustomEvent ev1(18,"于良浩");
        QApplication::sendEvent(this,&ev1);

        //2. 使用postEvent发送事件：只能是堆区变量，栈区无效
        auto ev2=new CustomEvent(20,"喜羊羊");
        QApplication::postEvent(this,ev2);
    });
}

void Widget::onTimerHandle()
{
    //qInfo()<<__FUNCTION__<<cnt++;
}
```

```cpp
# main.cpp
-----------
#include "widget.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    Widget w;
    w.show();
    return a.exec();
}
```