程序的入口main()函数的最后都会调用QApplication类的exec()函数,该函数会使 Qt 应用程序进入事件循环。
一旦有事件发生,Qt 框架会构建一个QEvent对象,然后将它传递给相应的QObject对象或其子对象。
Qt 中的事件由特定的QEvent子类表示,比如鼠标事件对应QMouseEvent,键盘事件对应QKeyEvent
通常,一个事件包含多个事件类型,比如:
- 鼠标事件,包含鼠标按下、鼠标移动和鼠标释放三个事件类型
- 键盘事件,包含键盘按下和键盘释放两个事件类型
这些事件类型都由QEvent类的枚举型QEvent::Type来表示,其中包含了 一百多种事件类型,可以在QEvent类的帮助文档中查看。
虽然QEvent的子类用来表示一个事件,那么应该怎样来处理一个事件呢?在QCoreApplication类的notify()函数的帮助文档处给出了5种处理事件的方法:
- 方法一:重新实现部件的
paintEvent()、mousePressEvent()等事件处理函数。这是最常用的一种方法,用来处理特定部件的特定事件。 - 方法二:重新实现
notify()函数。这个函数功能强大,提供了完全的控制,可以在事件过滤器得到事件之前就获得它们。但是,它一次只能处理一个事件。 - 方法三:向
QApplication对象上安装事件过滤器。因为一个程序只有一个QApplication对象,所以这样实现的功能与使用notify()函数是相同的,优点是可以同时处理多个事件。 - 方法四:重新实现
event()函数。QObject类的event()函数可以在事件到达默认的事件处理函数之前获得该事件。 - 方法五:在对象上安装事件过滤器。使用事件过滤器可以在一个界面类中同时处理不同子部件的不同事件。
在实际编程中,最常用的是方法一,其次是方法五。因为方法二需要继承自QApplication类;而方法三要使用一个全局的事件过滤器,这将减缓事件的传递,所以,虽然这两种方法功能很强大,但是却很少被用到。
接下来通过一个案例,来演示事件的传递流程。
自定义一个标签控件PropagateLabel,让它继承自QLabel,然后重写父类的 mousePressEvent()。
1. 创建 PropagateLabel 类
首先,在左侧项目文件名上右键,选择 “Add New…”,选择 “C++ Class”,先继承自QWidget,如下:
然后,把父类由QWidget修改为QLabel,如下:
// labelpropagate.h
#include <QLabel>
class LabelPropagate : public QLabel {
Q_OBJECT
public:
explicit LabelPropagate(QWidget *parent = nullptr);
signals:
};
// labelpropagate.cpp
#include "labelpropagate.h"
LabelPropagate::LabelPropagate(QWidget *parent) : QLabel{parent}
{
}
2. 将 LabelPropagate 显示到界面
首先,在propagate_widget.h中,声明PropagateLabel类型成员变量:
#include "labelpropagate.h"
class PropagateWidget : public QWidget {
private:
LabelPropagate* lbl;
};
然后,来到propagate_widget.cpp ,在构造函数中添加 PropagateLabel控件,如下:
PropagateWidget::PropagateWidget(QWidget* parent) : QWidget{parent}
{
QVBoxLayout* verticalLayout = new QVBoxLayout(this);
verticalLayout->setSpacing(0);
verticalLayout->setContentsMargins(0, 0, 0, 0);
// 1. 添加一个自定义的标签 La
lbl = new LabelPropagate();
lbl->setFrameShape(QFrame::Box);
lbl->setFixedHeight(50);
lbl->setAlignment(Qt::AlignCenter);
lbl->setStyleSheet("background-color: red;color: white;font-size: 25px");
verticalLayout->addWidget(lbl);
}
此时,运行效果:
3. 重写 mousePressEvent()
修改LabelPropagate类,声明并实现mousePressEvent()函数,如下:
// labelpropagate.h
class LabelPropagate : public QLabel {
protected:
void mousePressEvent(QMouseEvent *ev);
};
// labelpropagate.cpp
#include <QDebug>
void LabelPropagate::mousePressEvent(QMouseEvent *ev)
{
qDebug() << "LabelPropagate::mousePressEvent()";
}
此时,点击标签,就会执行自定义的PropagateLabel控件的mousePressEvent()函数
到目前为止,都是前面讲解过的内容。
4. 接受、忽略事件
4.1 原理
以上,在PropagateLabel的mousePressEvent()函数中处理完事件后,可以接受或忽略这个事件。
Qt 框架传递过来的这个event对象,有一个m_accept标志位,我们可以设置该标志位:
设置为 true
表示接受该事件,事件的传递,到此为止。则该事件不会再传递给其父控件,也就是PropagateWidget设置为 false
表示忽略该事件,事件会再传递给其父控件,也就是PropagateWidget
那么,具体如何设置m_accept标志位呢?
- 忽略事件(事件继续传递给父控件)
// 方法一
// 跳转到 ignore() 的实现,可见它其实就是直接将 m_accept 设置为了 false
event->ignore();
// 方法二
// 调用父类的实现
// QLineEdit 是直接继承自父类 QWidget 中的 keyReleaseEvent
QLineEdit::keyReleaseEvent(event);
// 而父类 QWidget 中的 keyReleaseEvent 实现,就是直接调用 event->ignore() 如下:
void QWidget::keyReleaseEvent(QKeyEvent *event)
{
event->ignore();
}
// 可见,不管是方法一,还是方法二,最终都是设置 event 中的 m_accept 标志位。
- 接受事件(事件到此为止,不再传递给其父控件)
// 跳转到 accept() 的实现,可见它其实就是直接将 m_accept 设置为了 true
event->accept();
// 然而,由于该标志位刚传递过来的值为 true(可以加打印查看),因此不需要显示地调用 accept()
4.2 具体实现
接下来,看LabelPropagate控件的鼠标按下事件能否传递到其父控件 PropagateWidget
首先,修改PropagateWidget类,声明并实现mousePressEvent()函数,如下:
// propagate_widget.h
class PropagateWidget : public QWidget {
protected:
void mousePressEvent(QMouseEvent* event);
};
// propagate_widget.cpp
void PropagateWidget::mousePressEvent(QMouseEvent* event)
{
qDebug() << "PropagateWidget::mousePressEvent()";
}
然后,修改labelpropagate.cpp中的mousePressEvent()函数,来决定事件是否继续向上传递:
void LabelPropagate::mousePressEvent(QMouseEvent *ev)
{
qDebug() << "LabelPropagate::mousePressEvent()";
// 接受事件(事件到此为止,不再传递给其父控件)
// 方法一:
// ev->accept();
// 方法二:什么都不用写。因为传递过来的 event 其 accept 标志位默认为 true,可打印验证
// qDebug() << ev->isAccepted();
// 忽略事件(事件继续传递给父控件)
ev->ignore();
}
此时, 忽略事件后的运行效果:







