一、事件系统概述
Qt 的事件系统是一个事件驱动架构,它让程序能够响应用户操作(鼠标、键盘等)、系统消息(窗口调整、定时器)、以及自定义事件。所有事件都被封装为 QEvent 对象,通过事件循环分发给对应的 QObject处理。
二、核心概念
| 类 / 概念 | 作用 |
|---|---|
QEvent |
所有事件的基类,封装事件类型和状态信息 |
QObject::event() |
事件的统一入口函数,根据事件类型调用具体事件处理函数 |
installEventFilter() / eventFilter() |
实现事件过滤器机制,拦截监听目标对象的事件 |
三、事件与信号的区别
| 对比维度 | 事件(Event) | 信号(Signal) |
|---|---|---|
| 定义 | QEvent 类的实例,表示底层操作或系统消息 |
QObject 派生类中声明的特殊成员函数 |
| 触发方式 | 由系统或 Qt 事件循环触发(常用),也可通过 postEvent()/sendEvent() 手动发送 |
由开发者显式调用 emit,或由 Qt 内部自动触发 |
| 处理机制 | 重写 event() 或特定事件处理函数(如 mousePressEvent()) |
通过槽函数连接,信号触发时槽函数自动调用 |
| 传播方式 | 可沿对象父子层级向上传播(子->父) | 直接调用连接的槽函数,无层级传播 |
| 典型用途 | 处理底层交互(输入、绘图、定时器)或系统通知 | 实现对象间的松耦合通信 |
四、事件传递流程
QCoreApplication::notify() 派发事件
↓
【第1层】目标对象的 eventFilter()(返回值为bool,返回 true则事件被拦截)
↓ (若未拦截)
【第2层】目标对象的 event() 函数 (事件处理的统一入口,根据事件类型分发到具体的处理函数)
↓
【第3层】具体事件处理函数(如 mousePressEvent()、keyPressEvent()、paintEvent()等,通常重写来让事件“生效”)
↓ (若 ignore()或者调用父处理函数)
【第4层】父组件的 event() → 具体事件处理函数
↓ (若仍 ignore())
【第5层】继续向上冒泡,直到被处理或到达顶层窗口
五、事件过滤器详解
5.1工作原理
事件过滤器允许一个对象(监视对象)拦截并处理另一个对象(目标对象/被监视对象)的事件,无需子类化目标对象
5.2核心步骤
-
在目标对象上调用
installEventFilter()安装过滤器 -
在监视对象中重写
eventFilter(QObject *watched, QEvent *event)函数 -
若
eventFilter()返回true,事件被拦截;返回false则事件继续传递
5.3代码示例
//监视对象
class MyFilter : public QObject {
Q_OBJECT
protected:
bool eventFilter(QObject *watched, QEvent *event) override {
// 检查目标对象
if (watched == targetWidget) {
// 检查事件类型
if (event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
if (keyEvent->key() == Qt::Key_Space) {
// 拦截空格键,不让目标控件处理
qDebug() << "Space key blocked";
return true;
}
}
}
// 其他事件继续传递
return QObject::eventFilter(watched, event);
}
};
//被监视对象
MyFilter *filter = new MyFilter(this);
button->installEventFilter(filter);
六、Event函数
作用就在于事件的分发 。如果想在事件的分发之前就进行一些操作,可以重写event函数,特定事件返回true,就阻断了该时间的传播,并且最后最好要写return QWidget::event(e);(其余事件正常传播)。如果传入的事件已被识别并且处理,则需要返回 true,否则返回 false。如果返回值是 true,那么 Qt 会认为这个事件已经处理完毕,不会再将这个事件发送给其它对象。
bool myWidget::event(QEvent *e)
{
if (e->type() == QEvent::KeyPress)
{
//将QEvent对象转换为真正的QKeyEvent对象
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
if (keyEvent->key() == Qt::Key_Tab)
{
qDebug() << "You press tab.";
return true;//按下tab键,这个事件已经被处理完
}
}
//按照原来的流程来进行事件的分发
return QWidget::event(e);
}
七、事件处理函数
重写paintEvent 、mousePressEvent等事件处理函数。这是最普通、最简单的形式,当发生该事件,就会按照你重写的处理函数执行逻辑。
**问题一:**重写事件处理函数时,什么时候调用基类处理?
解答:
核心在于:你是否需要基类的处理
| 基类函数 | 内部做了什么 | 不调用的后果 |
|---|---|---|
showEvent |
1. 更新窗口系统的可见状态。 2. 触发 QEvent::Show 和 QEvent::ShowToParen-t 的传播。 3. 激活布局和更新区域计算。 |
窗口可能无法正常显示,或者父窗口不知道子窗口已显示(导致焦点/激活状态异常)。 |
hideEvent |
1. 更新隐藏状态。 2. 触发 QEvent::Hide 和 QEvent::HideToParen-t 传播。 |
窗口隐藏后,底层窗口资源未释放,可能造成鼠标穿透或焦点残留。 |
paintEvent |
默认什么都不做(空函数),仅仅是为了防止未重载时调用纯虚函数。 | 无影响。 |
resizeEvent |
1. 更新内部几何缓存。 2. 触发布局重新计算(如果 sizeConstraint 启用)。 |
布局不会自动调整,子控件位置错乱。 |
keyPressEvent |
实现基础的焦点切换(Tab/Backtab)、快捷键(Alt+字母)和默认按钮行为。 | 如果完全自定义键盘处理(如游戏操作),可以不调,但会失去标准的 Tab 切换焦点功能。 |
closeEvent |
接受或忽略关闭事件 。默认调用 event->accept() 允许窗口关闭。 |
如果重写了且不调用基类,除非手动 accept(),否则窗口无法关闭(点击 X 没反应)。 |
结论:
生命周期/尺寸变化事件 :showEvent、hideEvent、moveEvent、resizeEvent这类事件必须处理,且在函数前列加上父类处理;
关闭事件 :closeEvent : 手动 accept()窗口关不掉,ignore(),不关闭
绘制事件 paintEvent,可以不调用。
输入事件 (键盘、鼠标、滚轮、拖拽)
→ 默认不调用基类 ,除非你明确需要保留 Qt 的标准交互(例如你需要 mousePressEvent 同时触发 clicked 信号,或者你需要 Tab 键正常切换焦点),如果调用基类,一般基类处理殿后。
代码示例:
void ArrowWidget::showEvent(QShowEvent* event) {
QWidget::showEvent(event); // 先让基类完成状态设置
m_pTimer->start(1); // 之后再执行自定义延迟计算
emit showed();
}