目录
[一、事件(Event)vs 信号(Signal)](#一、事件(Event)vs 信号(Signal))
[1. 事件(QEvent)](#1. 事件(QEvent))
[2. 信号(Signal)](#2. 信号(Signal))
[二、Qt 事件循环是干嘛的?](#二、Qt 事件循环是干嘛的?)
[四、怎么"拦截 / 过滤"事件?](#四、怎么“拦截 / 过滤”事件?)
[方法二:事件过滤器(Event Filter)(更灵活)](#方法二:事件过滤器(Event Filter)(更灵活))
✅ 槽函数本身不是事件,鼠标点击这种才是事件(QEvent)。
✅ 事件由事件循环从队列里取出,发给控件处理;控件在事件处理函数里可能 emit 信号。
✅ 信号触发槽函数时:
-
同线程 + AutoConnection:直接调用槽函数;
-
跨线程 + Auto/QueuedConnection:把"调用槽函数"的请求丢到接收者线程的事件队列里,由那个线程执行槽。
一、事件(Event)vs 信号(Signal)
先记一句话:
事件是"系统给对象发消息",信号是"对象给别人发通知"。
1. 事件(QEvent)
-
来源:操作系统 / Qt 内部
比如:鼠标移动、按键、窗口大小改变......
-
走向:发给某个具体对象(一个控件、一个窗口)。
-
处理方式:覆盖虚函数:
bool MyWidget::event(QEvent *e) override; // 总入口 void MyWidget::mousePressEvent(QMouseEvent *e); // 专门处理鼠标按下
2. 信号(Signal)
-
来源:对象自己发
比如:按钮检测到点击后,发
clicked()。 -
走向:谁连我我就通知谁(广播)。
-
处理方式:connect 到槽函数:
connect(button, &QPushButton::clicked, this, &MyWindow::onButtonClicked);
可以这样类比:
-
事件:快递员把快递送到你家门口(发给"你"这个对象)。
-
信号:你在小区群里发一条消息"我到家啦"(谁关心就监听)。
二、Qt 事件循环是干嘛的?
Qt 程序本质上就是一个大循环:不停地拿事件、分发事件。
伪代码大概是:
int main() {
QApplication app(argc, argv);
MyWindow w;
w.show();
return app.exec(); // 事件循环在这里转圈
}
app.exec() 做的事可以理解为:
while (程序没退出) {
// 1. 从操作系统拿消息(鼠标、键盘等)
// 2. 转成对应的 QEvent(QMouseEvent、QKeyEvent......)
// 3. 找到应该接收的那个控件
// 4. 调用 QApplication::notify() 把事件发给它
}
三、一次"鼠标点击"大概流程
以你点一下按钮为例,简化成几步:
-
操作系统:窗口系统发现你在某个坐标点按下鼠标 → 产生原始消息。
-
Qt 平台插件 :把系统消息转成一个
QMouseEvent。 -
事件循环 (
app.exec()):从队列里取出这个QMouseEvent。 -
QApplication::notify() :
找到那个被点中的控件(比如
QPushButton),准备把事件发给它。 -
事件过滤器(可选):
-
如果别的对象给这个按钮装了
eventFilter,会先问一嘴:"这个事件你要拦下来吗?"
-
eventFilter()返回true→ 事件就到此为止,不再往下走。 -
返回
false→ 继续发给按钮本身。
-
-
控件接收事件:
-
button->event(e)被调用; -
event()里发现这是鼠标按下,就继续调用mousePressEvent(e); -
QPushButton::mousePressEvent()里判断:按下 + 抬起 → 触发clicked()信号。
-
-
信号发出:
-
button发出clicked(); -
所有
connect过的槽函数被依次调用,比如onButtonClicked()。
-
四、怎么"拦截 / 过滤"事件?
你想在事件"正常处理前"动手脚,有两个常见方式:
方法一:重写控件自己的事件函数(简单直接)
class MyButton : public QPushButton {
protected:
void mousePressEvent(QMouseEvent *e) override {
// 这里先做你自己的事
// ...
// 继续让父类处理(保持原有按钮行为)
QPushButton::mousePressEvent(e);
}
};
适合你能改这个控件的类的时候。
方法二:事件过滤器(Event Filter)(更灵活)
适合"我不想改控件的类,但想听它的所有事件"。
-
写一个过滤器类,重写
eventFilter:class MyFilter : public QObject { protected: bool eventFilter(QObject *watched, QEvent *event) override { if (watched == targetButton && event->type() == QEvent::KeyPress) { // 拦截:不让这个按钮响应键盘 return true; // true = 我已经处理,不要再往下发了 } return false; // false = 放行,照常处理 } }; -
在某处安装到目标对象上:
MyFilter *filter = new MyFilter(this); targetButton->installEventFilter(filter);
记住一句话:
eventFilter返回 true = 拦截;返回 false = 放行。
五、把面试要点压缩成几句话
你可以最后这样记:
-
事件 :系统 → 对象,
event()/xxxEvent()处理;
信号 :对象 → 其他对象,slot处理。 -
程序核心是
app.exec()事件循环:拿系统消息 → 转 QEvent →notify()分发。 -
一次鼠标点击:系统消息 →
QMouseEvent→eventFilter(可选) →event()→mousePressEvent()→(可能)发clicked()信号。 -
想拦截事件:
-
能改控件:重写
event()或mousePressEvent()等; -
改不了控件:写
eventFilter+installEventFilter(),true拦截,false放行。
-
如果你愿意,我下一步可以帮你把这些内容浓缩成一页"小抄",专门给你背八股用 😄
你这句话大体是对的,只是可以再精细一点点 👍
先回答你的问题:
事件循环是不是就是事件队列循环,队列就是先进先出?
可以这么理解:
-
Qt 里确实有一个事件队列;
-
app.exec()就是在不停地:-
从队列里取出一个事件
-
分发给对应对象处理
-
-
队列大致上可以按 FIFO 理解,虽然内部还有优先级之类的细节,但入门可以不用管。
再看你说的流程:
点击按钮,触发这一流程
系统 → 事件 → 通知 → 按钮事件 → 发送信号 → 处理槽函数
把 Qt 版"官话"稍微补全一下就是:
-
系统:鼠标点击消息 →
-
Qt 平台层 :转成
QMouseEvent,丢进 Qt 的事件队列 → -
事件循环 :
app.exec()从队列里取出这个QMouseEvent→ -
notify :
QApplication::notify()把事件发给对应的按钮对象- (期间可能先经过
eventFilter过滤器)
- (期间可能先经过
-
按钮事件处理 :按钮的
event()/mousePressEvent()里处理这个点击 -
按钮内部判断为"真正点了一下" →
emit clicked()发出信号 -
槽函数 :所有
connect了clicked()的槽函数被调用
再补一个小但重要的点:
-
默认情况下(在同一线程,
Qt::AutoConnection),
信号发出后,槽函数是立刻在当前调用栈里执行的 ,而不是再丢回事件队列里排队(除非你用的是
Qt::QueuedConnection)。
所以可以用一句"口语版总结":
Qt:事件循环负责从系统拿事儿 → 找到控件 → 调控件的事件函数 ;
控件:在事件函数里觉得该通知别人了,就发信号 → 调槽函数。