我不是很理解Qt中的事件,比较疑惑的是QObject的event()函数是何时被调用,被谁调用的。并且疑惑如果有keyPressEvent()函数这个两个会不会重复调用
你这个问题其实已经抓住了 Qt 事件系统的核心:
👉 event() 和 keyPressEvent() 的关系,本质是 “统一入口 + 分发处理”,而不是两个独立系统。
我按“谁调用谁、什么时候调用、会不会重复”给你讲清楚。
一、event() 是什么时候被调用的?谁调用?
先说结论:
👉 QObject::event() 不是你直接调用的,而是 Qt 事件循环调用的
完整调用链大致是:
1 2 3
| 操作系统 → Qt事件循环 → QCoreApplication::notify() → QObject::event() → 具体事件函数(如 keyPressEvent)
|
更具体一点:
- 操作系统产生事件(比如按键)
- Qt 把它封装成
QEvent(例如 QKeyEvent)
- 进入事件队列(event loop)
- Qt 主循环(
exec())开始处理:
1
| QCoreApplication::notify(receiver, event);
|
notify() 内部会调用:
👉 所以:
✅ event() 是由 QCoreApplication::notify() 调用的
二、event() 到底干嘛?
👉 它是一个“总入口函数”(统一分发器)
你可以理解为:
1 2 3 4 5 6 7 8 9 10 11
| bool QObject::event(QEvent *e) { switch (e->type()) { case QEvent::KeyPress: keyPressEvent(static_cast<QKeyEvent*>(e)); break; case QEvent::MouseButtonPress: mousePressEvent(...); break; ... } }
|
当然真实源码更复杂,但逻辑就是这个。
👉 关键点:
event() 负责 把不同类型事件分发到对应的虚函数
三、那 keyPressEvent() 和 event() 会不会重复调用?
👉 不会重复处理,是“调用链关系”
调用顺序是:
1
| event() → keyPressEvent()
|
不是:
1 2 3
| event() keyPressEvent() (两个独立调用 ❌)
|
举个实际例子
你写:
1 2 3
| void MyWidget::keyPressEvent(QKeyEvent *e) { qDebug() << "keyPressEvent"; }
|
运行时发生的是:
1 2 3
| notify() → event() → keyPressEvent()
|
👉 只会执行一次 keyPressEvent()
四、如果你重写了 event() 会发生什么?
这是重点 ⚠️
1 2 3 4 5 6 7
| bool MyWidget::event(QEvent *e) { if (e->type() == QEvent::KeyPress) { qDebug() << "event 捕获 key"; return true; } return QWidget::event(e); }
|
结果:
👉 keyPressEvent() 不会被调用
因为:
你在 event() 里“截断”了事件,没有继续往下分发
正确写法(如果你还想让 keyPressEvent 生效)
1 2 3 4 5 6
| bool MyWidget::event(QEvent *e) { if (e->type() == QEvent::KeyPress) { qDebug() << "event 捕获 key"; } return QWidget::event(e); // 继续分发 }
|
五、总结一句话(非常重要)
👉 Qt 事件系统本质:
event() 是统一入口,具体事件函数(keyPressEvent 等)是它内部调用的分支处理
六、再帮你建立一个“脑模型”
你可以把 Qt 想成一个“总机 + 分机系统”:
notify() 👉 总机
event() 👉 分线员
keyPressEvent() 👉 具体接电话的人
流程:
1 2 3 4 5 6 7
| 用户按键 ↓ notify(总机接电话) ↓ event(分线员判断是什么电话) ↓ keyPressEvent(转接给键盘处理的人)
|
七、什么时候用 event()?什么时候用 keyPressEvent()?
👉 建议:
用 keyPressEvent()(90%情况)
用 event()(高级用法)
- 想拦截所有事件
- 想改分发逻辑
- 处理 Qt 没提供虚函数的事件类型
八、额外补充(你这个阶段很值得知道)
还有一个更底层的机制:
👉 事件过滤器(eventFilter)
调用顺序其实是:
1
| eventFilter → event() → keyPressEvent()
|
如果你后面继续深入 Qt,这个是关键。
最后总结(帮你压缩记忆)
👉 一句话版:
notify() 调用 event()
event() 调用 keyPressEvent()
- 不会重复调用,是链式调用
- 重写
event() 可能“拦截”掉后续事件
给我看一下Qt5.8中event的源码,然后我再疑惑的是操作系统 → Qt事件循环这部分是如何做到的,以及QCoreApplication::notify(receiver, event);这里面receiver是如何确定的这个函数是循环调用传入多个receiver吗
我会把源码拆成三段看:QCoreApplication::notify/notifyInternal2、QObject::event()/QWidget::event(),再解释 OS 消息如何进入 Qt,以及 receiver 是在哪里确定的。
已思考 21s
你可以把它理解成这条链:
1 2 3 4 5 6 7 8 9
| 操作系统消息 → QAbstractEventDispatcher::processEvents() → Qt 平台层把原生消息翻译成 QEvent → QCoreApplication::notifyInternal2(receiver, event) → QApplication/QCoreApplication::notify(receiver, event) → notify_helper(receiver, event) → receiver->event(event) → QWidget::event(event) → keyPressEvent(event)
|
1. Qt 5.8 里 QObject::event() 源码长这样
QObject::event() 本身只处理很基础的 QObject 事件,比如定时器、子对象变化、延迟删除、元调用等:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| bool QObject::event(QEvent *e) { switch (e->type()) { case QEvent::Timer: timerEvent((QTimerEvent*)e); break;
case QEvent::ChildAdded: case QEvent::ChildPolished: case QEvent::ChildRemoved: childEvent((QChildEvent*)e); break;
case QEvent::DeferredDelete: qDeleteInEventHandler(this); break;
case QEvent::MetaCall: // queued signal / invokeMethod 等会走这里 ... break;
... default: return false; }
return true; }
|
所以注意:
QObject::event() 里面没有 keyPressEvent()。
因为 keyPressEvent() 是 QWidget 的东西,不是普通 QObject 的东西。
真正分发键盘事件的是:
Qt 5.8 的 QWidget::event() 文档注释直接说:键盘事件比较特殊,event() 会先检查 Tab/Shift+Tab 是否用于焦点切换;如果不是,才调用 keyPressEvent()。源码附近就是:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| bool QWidget::event(QEvent *event) { ...
switch (event->type()) { case QEvent::MouseMove: mouseMoveEvent((QMouseEvent*)event); break;
case QEvent::MouseButtonPress: mousePressEvent((QMouseEvent*)event); break;
case QEvent::KeyPress: { QKeyEvent *k = (QKeyEvent *)event; bool res = false;
if (!(k->modifiers() & (Qt::ControlModifier | Qt::AltModifier))) { if (k->key() == Qt::Key_Backtab || (k->key() == Qt::Key_Tab && (k->modifiers() & Qt::ShiftModifier))) res = focusNextPrevChild(false); else if (k->key() == Qt::Key_Tab) res = focusNextPrevChild(true); }
if (!res) keyPressEvent(k);
break; }
... } }
|
所以你之前的问题答案是:
不会重复调用。
真实关系是:
1 2 3 4
| receiver->event(event); // 虚函数调用 // 如果 receiver 是 QWidget 子类,实际进入 QWidget::event() QWidget::event(event) → keyPressEvent(event)
|
不是:
1 2
| event(); keyPressEvent(); // Qt 又单独调用一次
|
3. event() 是谁调用的?
关键源码在 notify_helper():
1 2 3 4 5 6 7 8 9 10 11 12
| bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent *event) { // 先过 application event filters ...
// 再过 receiver 自己的 event filters ...
// 真正投递事件 consumed = receiver->event(event); return consumed; }
|
而 notify_helper() 是从 notify() / notifyInternal2() 过来的。Qt 5.8 里:
1 2 3 4 5 6 7 8 9 10 11 12 13
| bool QCoreApplication::notifyInternal2(QObject *receiver, QEvent *event) { ... return self->notify(receiver, event); }
bool QCoreApplication::notify(QObject *receiver, QEvent *event) { if (QCoreApplicationPrivate::is_app_closing) return true;
return doNotify(receiver, event); }
|
也就是说:
1 2 3 4 5
| notifyInternal2(receiver, event) → notify(receiver, event) → doNotify(receiver, event) → notify_helper(receiver, event) → receiver->event(event)
|
4. 操作系统 → Qt 事件循环,是怎么做到的?
app.exec() 不是一个魔法函数,它本质上进入一个循环:
1 2 3 4 5 6
| while (!退出) { 从操作系统取消息; 把原生消息翻译成 Qt 事件; 找到接收对象 receiver; notify(receiver, event); }
|
跨平台部分叫:
1
| QAbstractEventDispatcher
|
Qt 文档里说它负责从窗口系统和其他来源接收事件,再发送给 QCoreApplication / QApplication 处理。
不同系统有不同实现:
1 2 3 4
| Windows: 取 MSG,例如 WM_KEYDOWN / WM_MOUSEMOVE macOS: 取 NSEvent Linux/X11: 取 XCB event Wayland: 取 Wayland event
|
Qt 平台层再把它们转成:
1 2
| WM_KEYDOWN → QKeyEvent(QEvent::KeyPress) WM_MOUSEMOVE → QMouseEvent(QEvent::MouseMove)
|
5. receiver 是怎么确定的?
这要看事件类型。
键盘事件
键盘事件通常发给:
1
| QApplication::focusWidget()
|
也就是当前拥有键盘焦点的控件。
比如:
1 2
| QLineEdit *edit; edit->setFocus();
|
这时你按键,receiver 基本就是这个 QLineEdit。
鼠标事件
鼠标事件通常根据坐标命中测试:
1
| 鼠标位置 → 找到窗口 → 找到这个位置下面最里面的 QWidget
|
比如鼠标点在按钮上,receiver 就是那个 QPushButton。
定时器事件
你写:
这个 timer 属于哪个 QObject,receiver 就是哪个 QObject。
postEvent
如果你手动写:
1
| QCoreApplication::postEvent(obj, new QEvent(...));
|
那 receiver 就是你传进去的 obj。
6. notify(receiver, event) 是循环调用多个 receiver 吗?
不是一个 event 同时循环传给多个 receiver。
更准确是:
1 2 3
| 事件循环每次取出一个事件 → 这个事件有一个 receiver → notify(receiver, event)
|
伪代码:
1 2 3 4 5 6 7 8
| while (eventLoopRunning) { EventItem item = takeNextEvent();
QObject *receiver = item.receiver; QEvent *event = item.event;
QCoreApplication::notify(receiver, event); }
|
所以是:
1 2 3 4
| 第1个事件 → receiverA 第2个事件 → receiverB 第3个事件 → receiverA 第4个事件 → receiverC
|
不是:
1
| 一个事件 → receiverA → receiverB → receiverC
|
不过有例外:某些事件可能会传播,比如键盘/鼠标事件如果当前 widget 不接受,Qt 可能继续传给父 widget。Qt 的 notify() 注释里也提到,某些 mouse/key 事件会在 receiver 不感兴趣时传播到父对象。
最后压缩成一句话
event() 是 Qt 投递事件时调用的统一入口;receiver 在事件产生/翻译阶段就已经确定;notify() 每次处理一个 receiver + 一个 event;如果 receiver 是 QWidget,那么 QWidget::event() 会根据事件类型再调用 keyPressEvent()、mousePressEvent() 等具体函数。
一个事件只有一个receiver吗,那Qt是怎么确定这个receiver的
先把你这个问题纠正得更精确一点:
👉 “一个事件只有一个 receiver 吗?”
✔ 绝大多数情况下:是的(一个事件 → 一个目标对象)
❗ 但可能发生“传播”或“转发”,看起来像多个 receiver
一、Qt 是怎么确定 receiver 的?
核心一句话:
👉 receiver 在“事件产生/翻译阶段”就已经确定了,不是在 notify() 里算的
不同事件类型,确定方式完全不同 👇
二、最关键的三类事件
1️⃣ 键盘事件(KeyPress)
👉 receiver = 当前焦点对象(focus object)
1
| QWidget *w = QApplication::focusWidget();
|
举个例子:
1 2
| QLineEdit *edit; edit->setFocus();
|
你按键:
1 2 3 4
| OS: WM_KEYDOWN → Qt 转成 QKeyEvent → receiver = focusWidget() = edit → notify(edit, event)
|
✔ 所以键盘事件永远只发给“谁有焦点”
2️⃣ 鼠标事件(MousePress / Move)
👉 receiver = 鼠标坐标命中的最深层 QWidget
Qt 做了一件事叫:
🧠 hit test(命中测试)
流程:
1 2 3 4
| 鼠标点击 (x, y) → 找到窗口 → 找到这个点最里面的子控件 → 这个控件就是 receiver
|
举例:
1 2 3
| QWidget ├── QPushButton └── QLabel
|
你点在按钮上:
3️⃣ 定时器 / 自定义事件
👉 receiver = 你绑定的对象
1 2
| startTimer(1000); // 属于 this QCoreApplication::postEvent(obj, event);
|
✔ 谁创建的 / 你传给谁,receiver 就是谁
三、那事件“只会给一个对象”吗?
✔ 基本规则:一个事件只投递给一个 receiver
1
| notify(receiver, event);
|
四、但你感觉“像多个”的原因(重点)
1️⃣ 事件传播(propagation)
比如鼠标事件:
1 2 3
| void Child::mousePressEvent(QMouseEvent *e) { e->ignore(); // 我不要 }
|
👉 Qt 会把事件往父组件传:
1
| Child → Parent → Parent → ...
|
所以你会看到:
但本质是:
❗ 同一个 event 被“转交”,不是一开始就发给多个
2️⃣ eventFilter(事件过滤器)
调用链:
1 2 3 4
| eventFilter (A) eventFilter (B) event() keyPressEvent()
|
👉 看起来很多对象参与了,但:
✔ 真正的 receiver 还是一个
✔ 其他只是“拦截者”
3️⃣ Qt 内部转发(特殊情况)
比如:
- Tab 键 → 被 QWidget::event() 截获 → 转换为焦点移动
- Shortcut → 可能被 QApplication 处理
五、源码层面你要抓住的点
在 Qt 5.8 里:
1
| QCoreApplication::notifyInternal2(receiver, event)
|
👉 receiver 已经是确定好的
Qt 不会在这里“找谁来处理”,而是直接:
六、你可以建立这个“完整模型”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 【操作系统产生事件】
→ Qt 平台层(QPA) ✔ 转换为 QEvent ✔ 决定 receiver(关键!)
→ 放入事件系统 / 直接分发
→ QCoreApplication::notify(receiver, event)
→ receiver->event(event)
→ QWidget::event()
→ keyPressEvent / mousePressEvent
|
七、一句话总结(帮你建立直觉)
👉 receiver 不是 event() 里找的,而是在事件“诞生时”就决定好的
八、再补一个你这个阶段非常关键的理解
你现在其实已经踩到 Qt 设计的核心思想了:
👉 事件 = “谁应该处理这件事” + “发生了什么”
也就是:
不是:
如果你想再深入(强烈推荐)
下一步你可以问我这个:
👉 “Qt 是怎么通过源码做 hit test 找到鼠标点击的那个 QWidget?”
这个一旦搞懂,你对 Qt GUI 会直接上一个台阶。
我的疑问是如果父控件有子控件,子控件还有子控件,它们有重写了contextMenuEvent,那么它们的传递机制是怎么实现的,由谁做到的
事件传递机制的实现原理
这是一个非常深入的问题!让我详细解释整个传递链是如何实现的,以及由谁负责这个传递过程。
核心答案
传递机制由 Qt 事件系统 实现,具体来说有三个关键角色:
- QApplication - 事件分发总控
- QWidget::event() - 事件路由和传播判断
- 递归调用 - 向上查找父控件链
完整传递机制详解
1. QApplication 负责初始路由
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| bool QApplication::notify(QObject *receiver, QEvent *event) { if (event->type() == QEvent::ContextMenu) { QContextMenuEvent *ctxEvent = static_cast<QContextMenuEvent*>(event); QWidget *targetWidget = findDeepestChild(static_cast<QWidget*>(receiver), ctxEvent->pos()); if (targetWidget && targetWidget != receiver) { return sendEvent(targetWidget, event); } } return receiver->event(event); }
QWidget* findDeepestChild(QWidget *parent, const QPoint &pos) { QWidget *child = parent->childAt(pos); if (child && child != parent) { QPoint childPos = child->mapFromParent(pos); return findDeepestChild(child, childPos); } return parent; }
|
示例:
text
1 2 3 4 5 6 7 8 9
| GrandParent (0,0, 300x300) └─ Parent (50,50, 200x200) └─ Child (80,80, 100x100) ← 鼠标点击在 (150,150)
findDeepestChild 过程: 1. 检查 GrandParent → 找到 Parent 2. 检查 Parent → 找到 Child 3. 检查 Child → 没有更深子控件 4. 返回 Child
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| bool QWidget::event(QEvent *event) { switch (event->type()) { case QEvent::ContextMenu: { contextMenuEvent(static_cast<QContextMenuEvent*>(event)); if (!event->isAccepted()) { QWidget *parent = parentWidget(); if (parent && !isWindow()) { return QCoreApplication::sendEvent(parent, event); } return false; } return true; } } return QWidget::event(event); }
|
3. 完整的传递链递归过程
让我们追踪一个具体的三层嵌套示例:
cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class GrandParent : public QWidget { protected: void contextMenuEvent(QContextMenuEvent *event) override { qDebug() << "GrandParent: 收到事件"; event->ignore(); // 继续传递 } };
class Parent : public GrandParent { protected: void contextMenuEvent(QContextMenuEvent *event) override { qDebug() << "Parent: 收到事件"; event->ignore(); // 继续传递 } };
class Child : public Parent { protected: void contextMenuEvent(QContextMenuEvent *event) override { qDebug() << "Child: 收到事件"; event->ignore(); // 继续传递 } };
|
完整调用链:
text
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 用户右键点击 Child 区域 ↓ [步骤1] QApplication::notify() - 找到最深层的子控件 = Child - 调用 QApplication::sendEvent(Child, event) ↓ [步骤2] Child::event() 被调用 - 调用 Child::contextMenuEvent() - Child 设置 event->ignore() - Child::event() 检查到 !isAccepted() - 调用 sendEvent(Parent, event) ← 向上传递 ↓ [步骤3] Parent::event() 被调用 - 调用 Parent::contextMenuEvent() - Parent 设置 event->ignore() - Parent::event() 检查到 !isAccepted() - 调用 sendEvent(GrandParent, event) ← 继续向上 ↓ [步骤4] GrandParent::event() 被调用 - 调用 GrandParent::contextMenuEvent() - GrandParent 设置 event->ignore() - GrandParent::event() 检查到 !isAccepted() - GrandParent->parentWidget() 返回 nullptr(顶层窗口) - 事件被丢弃
|
4. 关键代码:sendEvent 的递归特性
注意 sendEvent 是同步调用,会立即执行目标控件的 event() 函数:
cpp
1 2 3 4 5 6 7 8 9
| // Child::event() 中的代码 if (!event->isAccepted()) { QWidget *parent = parentWidget(); if (parent) { // ⭐ 这是一个同步调用,会立即执行 Parent::event() // Parent::event() 执行完毕后,才返回这里 return QCoreApplication::sendEvent(parent, event); } }
|
调用栈示意图:
text
1 2 3 4 5 6 7 8 9 10 11 12 13
| QApplication::notify() └─ Child::event() └─ Child::contextMenuEvent() └─ sendEvent(Parent) // 同步调用 └─ Parent::event() └─ Parent::contextMenuEvent() └─ sendEvent(GrandParent) └─ GrandParent::event() └─ GrandParent::contextMenuEvent() └─ 返回 └─ 返回 └─ 返回 └─ 返回
|
5. 实际验证代码
cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| #include <QApplication> #include <QWidget> #include <QPushButton> #include <QDebug>
class DeepWidget : public QWidget { QString name; public: DeepWidget(const QString &n, QWidget *parent = nullptr) : QWidget(parent), name(n) { setStyleSheet(QString("background-color: %1; border: 1px solid black;") .arg(name == "Child" ? "red" : name == "Parent" ? "green" : "blue")); } protected: void contextMenuEvent(QContextMenuEvent *event) override { qDebug() << "[" << name << "] 收到事件"; // 打印调用栈深度 static int depth = 0; depth++; qDebug() << " 深度:" << depth; // 选择是否传递 event->ignore(); // 总是向上传递 qDebug() << "[" << name << "] 事件处理结束"; depth--; } bool event(QEvent *event) override { if (event->type() == QEvent::ContextMenu) { qDebug() << " ->" << name << "::event() 开始"; bool result = QWidget::event(event); qDebug() << " <-" << name << "::event() 结束, accepted:" << event->isAccepted(); return result; } return QWidget::event(event); } };
int main(int argc, char *argv[]) { QApplication app(argc, argv); auto *grandParent = new DeepWidget("GrandParent"); grandParent->resize(300, 300); auto *parent = new DeepWidget("Parent", grandParent); parent->resize(200, 200); parent->move(50, 50); auto *child = new DeepWidget("Child", parent); child->resize(100, 100); child->move(50, 50); grandParent->show(); return app.exec(); }
|
运行输出(右键点击 Child):
text
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| -> Child::event() 开始 [ Child ] 收到事件 深度: 1 [ Child ] 事件处理结束 <- Child::event() 结束, accepted: false
-> Parent::event() 开始 [ Parent ] 收到事件 深度: 2 [ Parent ] 事件处理结束 <- Parent::event() 结束, accepted: false
-> GrandParent::event() 开始 [ GrandParent ] 收到事件 深度: 3 [ GrandParent ] 事件处理结束 <- GrandParent::event() 结束, accepted: false
|
6. 如果中间某个控件 accept() 会怎样?
cpp
1 2 3 4 5 6 7
| class Parent : public GrandParent { protected: void contextMenuEvent(QContextMenuEvent *event) override { qDebug() << "Parent: 收到事件"; event->accept(); // ← 这里接受事件 } };
|
输出:
text
1 2 3 4 5 6 7 8 9
| -> Child::event() 开始 [ Child ] 收到事件,ignore() <- Child::event() 结束, accepted: false
-> Parent::event() 开始 [ Parent ] 收到事件,accept() <- Parent::event() 结束, accepted: true
// GrandParent 不会收到!
|
总结:谁做到了什么?
| 角色 |
职责 |
| QApplication::notify() |
找到最深层的子控件,作为事件初始接收者 |
| QWidget::event() |
调用具体的 contextMenuEvent,并判断是否需要向上传播 |
| QCoreApplication::sendEvent() |
同步发送事件到父控件,实现递归调用 |
| 父控件链 |
通过 parentWidget() 获取,形成传播路径 |
核心机制:
- 事件总是先发给最深层子控件
- 每个控件的
event() 函数检查 isAccepted()
- 如果
false,自动调用 sendEvent(parent, event)
- 这是一个同步递归调用,不是异步信号
- 直到某个控件
accept() 或到达顶层窗口
一句话总结:传递机制由 QWidget::event() 中的传播逻辑实现,它在调用完 contextMenuEvent() 后检查事件状态,如果未被接受就递归调用父控件的 event(),形成链式传播。