Qt 事件系统
事件 vs 信号槽
| 事件 (Event) | 信号槽 (Signal/Slot) | |
|---|---|---|
| 层级 | 底层(操作系统 → Qt) | 上层(QObject 之间) |
| 方向 | 自顶向下传播 | 任意对象间通信 |
| 处理 | event() / eventFilter() | connect() |
| 典型 | 鼠标点击、键盘按键、重绘 | 按钮点击、数据到达 |
信号槽底层由事件系统驱动,但日常开发中大多数场景用信号槽就够了。
事件循环
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
// ...
return app.exec(); // 进入事件循环
}
// 事件循环伪代码:
while (!shouldQuit) {
QEvent event = getNextEvent(); // 从操作系统取事件
sendEvent(widget, &event); // 发给目标控件
}
QWidget 事件处理五层
// 层 1: 专用事件处理器(最常用)
void MyWidget::mousePressEvent(QMouseEvent *event) override {
if (event->button() == Qt::LeftButton) {
// 处理左键点击
}
QWidget::mousePressEvent(event); // ⚠️ 调用父类,否则无法继续传播
}
// 层 2: event() — 事件总入口(少用)
bool MyWidget::event(QEvent *event) override {
if (event->type() == QEvent::KeyPress) {
auto *ke = static_cast<QKeyEvent *>(event);
// 拦截所有按键
}
return QWidget::event(event); // 分发到专用处理器
}
// 层 3: 事件过滤器 — 拦截子控件事件
class Monitor : public QObject {
bool eventFilter(QObject *obj, QEvent *event) override {
if (event->type() == QEvent::MouseButtonPress) {
qDebug() << "clicked on" << obj;
}
return QObject::eventFilter(obj, event); // false = 继续传播
}
};
// 安装:child->installEventFilter(monitor);
// 层 4: 全局事件过滤器(应用程序级)
// qApp->installEventFilter(globalMonitor);
// 层 5: notify() — QApplication 级(极少用)
// 重写 QApplication::notify()
常用事件类型
// 鼠标事件
void mousePressEvent(QMouseEvent *e) override;
void mouseReleaseEvent(QMouseEvent *e) override;
void mouseMoveEvent(QMouseEvent *e) override; // 需 setMouseTracking(true)
void wheelEvent(QWheelEvent *e) override;
// 键盘事件
void keyPressEvent(QKeyEvent *e) override;
void keyReleaseEvent(QKeyEvent *e) override;
// 窗口事件
void resizeEvent(QResizeEvent *e) override;
void closeEvent(QCloseEvent *e) override; // 拦截关闭
void showEvent(QShowEvent *e) override; // 首次显示前
// 重绘
void paintEvent(QPaintEvent *e) override;
// 拖放
void dragEnterEvent(QDragEnterEvent *e) override;
void dropEvent(QDropEvent *e) override;
自定义事件
// 1. 定义事件类型
const QEvent::Type MyCustomEvent = static_cast<QEvent::Type>(
QEvent::registerEventType()
);
// 2. 自定义事件类(可携带数据)
class DataEvent : public QEvent {
public:
static const QEvent::Type Type;
QString message;
DataEvent(const QString &msg)
: QEvent(Type), message(msg) {}
};
const QEvent::Type DataEvent::Type =
static_cast<QEvent::Type>(QEvent::registerEventType());
// 3. 发送事件
// sendEvent — 同步(等处理完才返回)
QApplication::sendEvent(receiver, new DataEvent("hello"));
// postEvent — 异步(加入事件队列,立即返回)
QApplication::postEvent(receiver, new DataEvent("hello"));
// 4. 接收事件
bool Receiver::event(QEvent *event) override {
if (event->type() == DataEvent::Type) {
auto *de = static_cast<DataEvent *>(event);
qDebug() << de->message;
return true; // 已处理
}
return QWidget::event(event);
}
事件过滤器实战
// 场景:让 LineEdit 响应回车键
class EnterFilter : public QObject {
bool eventFilter(QObject *obj, QEvent *event) override {
if (event->type() == QEvent::KeyPress) {
auto *ke = static_cast<QKeyEvent *>(event);
if (ke->key() == Qt::Key_Return) {
emit enterPressed(); // 自定义信号
return true; // 拦截事件
}
}
return false; // 继续传播
}
signals:
void enterPressed();
};
auto *filter = new EnterFilter(edit);
edit->installEventFilter(filter);
connect(filter, &EnterFilter::enterPressed, [=]() {
// 处理回车
});
常见陷阱
// 陷阱 1: 忘记调用父类事件处理器
void MyWidget::mousePressEvent(QMouseEvent *e) override {
// 处理...但没调 QWidget::mousePressEvent(e)
// → setFocus、右键菜单等默认行为丢失
}
// 陷阱 2: eventFilter 返回 true 会吞掉事件
bool eventFilter(QObject *obj, QEvent *event) override {
if (event->type() == QEvent::KeyPress)
return true; // ❌ 按键被吃了,子控件永远收不到
return false;
}
// 陷阱 3: postEvent 的内存管理
QApplication::postEvent(obj, new MyEvent); // ✅ Qt 自动 delete
// sendEvent 也一样 — 不要手动 delete 事件对象