Qt 事件系统

Qt中,事件作为一个对象,继承自QEvent类,常见的有键盘事件QKeyEvent,鼠标事件QMouseEvent,定时器事件QTimerEvent等。

[TOC]

1 Qt中的事件

事件是对各种应用程序需要知道的由应用程序内部或者外部产生的事情或者动作的通称。

事件与信号并不相同。

eg.单一一下按钮,那么就会产生鼠标事件QMouseEvent(不是按钮产生的),而因为按钮被按下了,所以他会发射clicked()单击信号(按钮产生的)。一般需要考虑单击信号,如果要设计这个按钮被单击时候产生别的效果,那么需要考虑鼠标事件。

因此,事件与信号是两个不同层面的东西。

Qt中,任何QObject子类对象都可以接收和处理事件。

1.1 事件的处理

一个事件由某个QEventQEvent子类对象表示,每个事件对象都有一个事件类型,事件类型由 QEvent::Type 枚举值表示,其中有100多个枚举常量。

1.1.1 事件处理函数

Qt 的控件类里已经准备了很多虚函数,专门用来处理事件。

比如:

1
2
3
4
5
6
void mousePressEvent(QMouseEvent *event);
void mouseMoveEvent(QMouseEvent *event);
void keyPressEvent(QKeyEvent *event);
void closeEvent(QCloseEvent *event);
void resizeEvent(QResizeEvent *event);
void paintEvent(QPaintEvent *event);

自己写类时,可以重写这些函数。

比如:

1
2
3
4
5
class PetWidget : public QWidget
{
protected:
void mousePressEvent(QMouseEvent *event) override;
};

实现:

1
2
3
4
void PetWidget::mousePressEvent(QMouseEvent *event)
{
qDebug() << "鼠标按下了";
}

表示:当 PetWidget 收到鼠标按下事件时,执行这段代码。

1.1.2 事件的处理流程

以鼠标点击桌宠为例:

1
2
3
4
5
6
7
8
9
10
11
用户点击桌宠

操作系统收到鼠标消息

Qt 把系统消息转换成 QMouseEvent

Qt 判断鼠标点在哪个 QWidget 上

把事件发送给这个 QWidget

调用 mousePressEvent(event)

1.1.3 accept和ignore

很多事件对象都有:

1
2
event->accept();
event->ignore();

它们表示:

1
2
accept:表示当前对象已经接受/处理这个事件。
ignore:表示当前对象不处理这个事件,某些事件可能继续传递给父对象或由 Qt 做默认处理。

比如:

1
2
3
4
5
6
7
8
9
void MyWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
qDebug() << "处理左键";
event->accept();
} else {
event->ignore();
}
}

1.2 事件的传递

1.2.1 事件的传递

对 QWidget 来说,很多输入事件如果子控件不处理,可能会传给父控件。

比如:

1
2
PetWidget
└─ QLabel

如果点到了 QLabel,事件先到 QLabel

如果 QLabel 不处理,可能再传给父控件 PetWidget

1.2.2 event()函数

除了专门的事件函数,Qt 还有一个总入口:

1
bool event(QEvent *event) override;

所有事件进入某个对象时,通常会先经过这个总函数,然后再分发给具体处理函数。

比如:

1
2
3
4
5
6
event()
├─ mousePressEvent()
├─ keyPressEvent()
├─ paintEvent()
├─ resizeEvent()
└─ closeEvent()

可以重写 event()

1
2
3
4
5
6
7
8
9
bool MyWidget::event(QEvent *event)
{
if (event->type() == QEvent::MouseButtonPress) {
qDebug() << "在 event() 里捕获鼠标按下";
return true;
}

return QWidget::event(event);
}

这里:

1
return true;

表示事件已经处理。

2 鼠标事件和滚轮事件

QMouseEvent类用来表示一个鼠标事件,在窗口部件中按下鼠标或者移动鼠标指针,都会产生鼠标事件。一般通过重定义部件的鼠标事件处理函数来进行一些自定义的操作。

最常用的几个函数是:

1
2
3
4
5
void mousePressEvent(QMouseEvent *event) override;    // 鼠标按下
void mouseMoveEvent(QMouseEvent *event) override; // 鼠标移动
void mouseReleaseEvent(QMouseEvent *event) override; // 鼠标释放
void mouseDoubleClickEvent(QMouseEvent *event) override; // 鼠标双击
void wheelEvent(QWheelEvent *event) override; // 鼠标滚轮

这些函数通常写在自己的 QWidget 子类里面,例如:

1
2
3
4
5
6
7
8
class PetWidget : public QWidget
{
protected:
void mousePressEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override;
void wheelEvent(QWheelEvent *event) override;
};

2.1 鼠标按下事件

1
void mousePressEvent(QMouseEvent *event) override;

鼠标按下时触发。常用来判断用户按的是左键、右键还是中键。

1
2
3
4
5
6
7
8
void PetWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
qDebug() << "左键按下";
} else if (event->button() == Qt::RightButton) {
qDebug() << "右键按下";
}
}

常用函数:

1
2
3
4
Qt::MouseButton button() const;   // 触发本次事件的按钮
Qt::MouseButtons buttons() const; // 当前处于按下状态的所有按钮
QPointF position() const; // 鼠标在当前控件内的位置,Qt 6 常用
QPointF globalPosition() const; // 鼠标在屏幕上的全局位置,Qt 6 常用

注意:

1
event->button()

表示“这一次事件是哪一个按钮触发的”。

1
event->buttons()

表示“当前有哪些鼠标按钮正处于按下状态”。

2.2 鼠标移动事件

1
void mouseMoveEvent(QMouseEvent *event) override;

鼠标移动时触发。

不过:默认情况下,只有鼠标按下后移动,控件才会收到 mouseMoveEvent。

如果希望鼠标不按下,只要经过控件就触发移动事件,需要开启:

1
setMouseTracking(true);

例如:

1
2
3
4
PetWidget::PetWidget(QWidget *parent) : QWidget(parent)
{
setMouseTracking(true);
}

然后:

1
2
3
4
void PetWidget::mouseMoveEvent(QMouseEvent *event)
{
qDebug() << "鼠标移动到:" << event->position();
}
  • 例题:用鼠标事件实现拖拽窗口

基本思路:

1
2
3
鼠标按下:记录鼠标相对窗口左上角的偏移
鼠标移动:用鼠标全局位置 - 偏移量,得到窗口新位置
鼠标释放:结束拖动

示例代码:

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
class PetWidget : public QWidget
{
public:
explicit PetWidget(QWidget *parent = nullptr) : QWidget(parent) {}

protected:
void mousePressEvent(QMouseEvent *event) override
{
if (event->button() == Qt::LeftButton) {
dragging = true;
dragOffset = event->globalPosition().toPoint() - frameGeometry().topLeft();
event->accept();
}
}

void mouseMoveEvent(QMouseEvent *event) override
{
if (dragging && (event->buttons() & Qt::LeftButton)) {
move(event->globalPosition().toPoint() - dragOffset);
event->accept();
}
}

void mouseReleaseEvent(QMouseEvent *event) override
{
if (event->button() == Qt::LeftButton) {
dragging = false;
event->accept();
}
}

private:
bool dragging = false;
QPoint dragOffset;
};

这里最关键的是:

1
dragOffset = event->globalPosition().toPoint() - frameGeometry().topLeft();

表示鼠标点下去的位置,距离窗口左上角有多远。

移动时:

1
move(event->globalPosition().toPoint() - dragOffset);

表示:

1
窗口新左上角 = 当前鼠标屏幕位置 - 当初记录的偏移

这样窗口就会跟着鼠标移动,而且不会突然跳一下。

2.3 鼠标释放事件

1
void mouseReleaseEvent(QMouseEvent *event) override;

鼠标松开时触发。常用于结束拖动、结束选择、结束绘制等。

1
2
3
4
5
6
7
void PetWidget::mouseReleaseEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
dragging = false;
qDebug() << "左键释放";
}
}

2.4 鼠标双击事件

1
void mouseDoubleClickEvent(QMouseEvent *event) override;

双击时触发,例如桌宠双击打开聊天窗口:

1
2
3
4
5
6
7
void PetWidget::mouseDoubleClickEvent(QMouseEvent *event)
{
if (event->button() == Qt::LeftButton) {
openChatWindow();
event->accept();
}
}

2.5 右键菜单

右键菜单可以直接在 mousePressEvent() 里判断右键,也可以重写专门的:

1
void contextMenuEvent(QContextMenuEvent *event) override;

简单写法:

1
2
3
4
5
6
7
8
9
10
11
12
13
void PetWidget::mousePressEvent(QMouseEvent *event)
{
if (event->button() == Qt::RightButton) {
QMenu menu(this);
menu.addAction("打开设置");
menu.addAction("退出");
menu.exec(event->globalPosition().toPoint());
event->accept();
return;
}

QWidget::mousePressEvent(event);
}

不过更标准的是:

1
2
3
4
5
6
7
void PetWidget::contextMenuEvent(QContextMenuEvent *event)
{
QMenu menu(this);
menu.addAction("打开设置");
menu.addAction("退出");
menu.exec(event->globalPos());
}

2.6 滚轮事件

滚轮事件函数是:

1
void wheelEvent(QWheelEvent *event) override;

常用来做缩放、调节透明度、调节音量等。

Qt 6 常用:

1
QPoint angleDelta() const;

这个函数返回一个 QPoint,里面有两个方向的滚动量:

1
2
event->angleDelta().x();  // 水平方向滚动量
event->angleDelta().y(); // 垂直方向滚动量

例如:

1
2
3
4
5
6
7
8
9
10
void PetWidget::wheelEvent(QWheelEvent *event)
{
int delta = event->angleDelta().y();

if (delta > 0) {
qDebug() << "滚轮向上";
} else if (delta < 0) {
qDebug() << "滚轮向下";
}
}

通常:

1
2
angleDelta().y() > 0   向上滚
angleDelta().y() < 0 向下滚

Q: button()buttons() 的区别?

A:

1
event->button()

表示本次事件由哪个按钮触发。

常用于:

1
2
3
mousePressEvent()
mouseReleaseEvent()
mouseDoubleClickEvent()

比如:

1
if (event->button() == Qt::LeftButton)
1
event->buttons()

表示当前哪些按钮正在被按住。

常用于:

1
mouseMoveEvent()

比如拖动时:

1
if (event->buttons() & Qt::LeftButton)

2.7 常用枚举

1
2
3
4
Qt::LeftButton;    // 左键
Qt::RightButton; // 右键
Qt::MiddleButton; // 中键
Qt::NoButton; // 没有按钮

判断单个按钮:

1
if (event->button() == Qt::LeftButton) {}

判断当前是否按着某个按钮:

1
if (event->buttons() & Qt::LeftButton) {}

3 键盘事件

QKeyEvent类用来描述一个键盘事件。当键盘按键被按下或者被释放时,键盘事件就会被发送给有键盘输入焦点的部件。

常用的两个函数是:

1
2
void keyPressEvent(QKeyEvent *event) override;    // 键盘按下
void keyReleaseEvent(QKeyEvent *event) override; // 键盘释放

它们一般写在自己的 QWidget 子类里。

3.1 示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <QKeyEvent>
#include <QDebug>

void MyWidget::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_Escape) {
qDebug() << "按下了 Esc";
close();
event->accept();
return;
}

QWidget::keyPressEvent(event);
}

这里:

1
event->key()

表示用户按下的是哪个键。

常见按键枚举:

1
2
3
4
5
6
7
8
Qt::Key_Escape;  // Esc
Qt::Key_Return; // 回车,主键盘 Enter
Qt::Key_Enter; // 小键盘 Enter
Qt::Key_Space; // 空格
Qt::Key_A; // A
Qt::Key_Z; // Z
Qt::Key_0; // 数字 0
Qt::Key_F1; // F1

3.2 key()text()

QKeyEvent 里常用两个信息:

1
2
int key() const;
QString text() const;

区别是:

1
2
key()  :表示物理/逻辑按键,比如 Qt::Key_A、Qt::Key_Escape
text() :表示这个按键产生的文本,比如 "a"、"A"、"中"

例如按下 A

1
2
event->key()  可能是 Qt::Key_A
event->text() 可能是 "a"

如果同时按 Shift + A

1
2
event->key()  仍然是 Qt::Key_A
event->text() 可能是 "A"

如果按 Esc

1
2
event->key()  是 Qt::Key_Escape
event->text() 通常为空

所以:

  • 判断快捷键/功能键:用 key()
  • 获取用户输入字符:用 text()

3.3 判断组合键

比如判断 Ctrl + S

1
2
3
4
5
6
7
8
9
10
11
void MyWidget::keyPressEvent(QKeyEvent *event)
{
if (event->key() == Qt::Key_S &&
(event->modifiers() & Qt::ControlModifier)) {
qDebug() << "Ctrl + S";
event->accept();
return;
}

QWidget::keyPressEvent(event);
}

这里:

1
event->modifiers()

返回当前按下的修饰键集合。

常见修饰键:

1
2
3
4
Qt::ControlModifier;  // Ctrl
Qt::ShiftModifier; // Shift
Qt::AltModifier; // Alt
Qt::MetaModifier; // Windows键 / Command键

3.4 让 QWidget 接收键盘事件

有些控件默认不主动接受键盘焦点。可以设置焦点策略:

1
setFocusPolicy(Qt::StrongFocus);

例如:

1
2
3
4
MyWidget::MyWidget(QWidget *parent) : QWidget(parent)
{
setFocusPolicy(Qt::StrongFocus);
}

常见焦点策略:

1
2
3
4
5
Qt::NoFocus;          // 不接受键盘焦点
Qt::TabFocus; // 可以通过 Tab 获得焦点
Qt::ClickFocus; // 点击后获得焦点
Qt::StrongFocus; // Tab 和点击都可以获得焦点
Qt::WheelFocus; // StrongFocus + 鼠标滚轮可获得焦点

3.5 按键自动重复

长按一个键时,系统会不断生成键盘事件。这叫 auto repeat。

可以用:

1
bool isAutoRepeat() const;

例如:

1
2
3
4
5
6
void MyWidget::keyPressEvent(QKeyEvent *event)
{
if (event->isAutoRepeat()) {
qDebug() << "这是长按产生的重复事件";
}
}

如果只想处理第一次按下,可以这样:

1
2
3
if (event->isAutoRepeat()) {
return;
}

因为:

1
2
3
4
5
第1次:真正按下 A        isAutoRepeat() == false
第2次:长按自动重复 A isAutoRepeat() == true
第3次:长按自动重复 A isAutoRepeat() == true
第4次:长按自动重复 A isAutoRepeat() == true
...

3.6 keyPressEvent() 和快捷键系统的区别

可以用键盘事件判断快捷键:

1
2
3
4
if (event->key() == Qt::Key_S &&
(event->modifiers() & Qt::ControlModifier)) {
save();
}

但更标准的快捷键做法通常是用:

1
2
QShortcut
QAction

例如:

1
2
QShortcut *shortcut = new QShortcut(QKeySequence("Ctrl+S"), this);
connect(shortcut, &QShortcut::activated, this, &MyWidget::save);

区别:

1
2
3
4
5
keyPressEvent:
适合处理当前控件内部的键盘行为。

QShortcut / QAction:
适合处理窗口或应用中的功能快捷键,比如保存、复制、关闭、打开设置。

注:

  • QKeySequence 表示一个按键组合

例如:

1
QKeySequence("Ctrl+S")
  • QShortcut 是真正的快捷键对象

    它负责把某个快捷键注册到某个控件或窗口上。

    例如:

    1
    QShortcut *shortcut = new QShortcut(QKeySequence("Ctrl+S"), this);

    意思是:在 this 这个窗口/控件中注册 Ctrl+S 快捷键

    但是此时只是创建了快捷键,还没说触发后要干什么。

    所以还要:

    1
    connect(shortcut, &QShortcut::activated, this, &MyWidget::save);

    意思是:当 Ctrl+S 被按下时,调用 save()

4 定时器事件与随机数

QTimerEvent类用来描述一个定时器事件。

QRandomGenerator类用来实现随机数的生成。

4.1 timerEvent函数

  • QObject的子类都可以启动定时器。

核心函数:

1
2
3
int startTimer(int interval);
void killTimer(int id);
void timerEvent(QTimerEvent *event) override;

其中:

1
2
interval 是时间间隔,单位是毫秒
1000 ms = 1 秒

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyWidget : public QWidget
{
public:
explicit MyWidget(QWidget *parent = nullptr) : QWidget(parent)
{
timerId = startTimer(1000);
}

protected:
void timerEvent(QTimerEvent *event) override
{
if (event->timerId() == timerId) {
qDebug() << "每秒执行一次";
}
}

private:
int timerId = 0;
};

含义是:

1
2
构造函数里启动一个 1000ms 的定时器;
之后每隔 1 秒,Qt 会自动调用 timerEvent()。

Q: 为什么有timerId?

A: 因为一个对象可以启动多个定时器。

startTimer() 返回 id
killTimer(id) 停止对应定时器

4.2 QTimer

实际项目里更多用QTimer类。它是一个 QObject 子类,可以发出 timeout() 信号。

例子:

1
2
3
4
5
6
7
QTimer *timer = new QTimer(this);

connect(timer, &QTimer::timeout, this, [=]() {
qDebug() << "每秒执行一次";
});

timer->start(1000);

含义:

1
2
每隔 1000ms,timer 发出 timeout() 信号;
然后执行 lambda 里的代码。

这个写法比 timerEvent() 更直观。

4.3 单次定时器

如果只想延迟执行一次,可以用singleShot

1
2
3
QTimer::singleShot(3000, this, [=]() {
qDebug() << "3秒后执行一次";
});

例如:

1
2
3
弹出提示后 3 秒自动隐藏
保存成功提示延迟消失
等待动画播放完再切换状态

4.4 随机数

Qt 里常用QRandomGenerator类。

包含头文件:

1
#include <QRandomGenerator>

生成一个随机整数:

1
int value = QRandomGenerator::global()->bounded(100);

含义:

1
2
生成 [0, 100) 之间的整数
也就是 0 到 99

生成指定范围:

1
int value = QRandomGenerator::global()->bounded(10, 21);

含义:

1
2
生成 [10, 21) 之间的整数
也就是 10 到 20

5 拖放事件

Qt提供了强大的拖放机制Drag and Drop

拖放事件的核心是:

1
2
3
4
用户把一个东西拖到窗口上
Qt 产生拖放相关事件
窗口决定“接不接收”
如果接收,就在 dropEvent() 里处理数据

比如你把一个图片文件拖到桌宠窗口上,可以实现:

1
2
3
4
拖入图片 → 导入宠物资源
拖入 zip → 导入角色包
拖入 txt → 读取文本
拖入文件到聊天框 → 作为附件/路径处理

最常用的事件函数是:

1
2
3
4
void dragEnterEvent(QDragEnterEvent *event) override; // 拖拽进入窗口
void dragMoveEvent(QDragMoveEvent *event) override; // 拖拽在窗口内移动
void dragLeaveEvent(QDragLeaveEvent *event) override; // 拖拽离开窗口
void dropEvent(QDropEvent *event) override; // 松手放下

不过在使用前,必须先开启接收拖放:

1
setAcceptDrops(true);

否则窗口不会收到拖放事件。

5.1 MIME类型

拖放数据通常通过MIME格式描述。Qt 使用QMimeData保存拖放数据,例如文本、URL、图片等。

一个最简单的“拖入文件并输出路径”的例子:

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
#include <QWidget>
#include <QDragEnterEvent>
#include <QDropEvent>
#include <QMimeData>
#include <QUrl>
#include <QDebug>

class DropWidget : public QWidget
{
public:
explicit DropWidget(QWidget *parent = nullptr) : QWidget(parent)
{
setAcceptDrops(true);
}

protected:
void dragEnterEvent(QDragEnterEvent *event) override
{
if (event->mimeData()->hasUrls()) {
event->acceptProposedAction();
} else {
event->ignore();
}
}

void dropEvent(QDropEvent *event) override
{
const QList<QUrl> urls = event->mimeData()->urls();

for (const QUrl &url : urls) {
QString path = url.toLocalFile();
qDebug() << "拖入文件:" << path;
}

event->acceptProposedAction();
}
};

这里最关键的是:

1
event->mimeData()

拖进来的数据都放在 QMimeData 里面。它可能包含:

1
2
3
event->mimeData()->hasUrls();   // 文件路径/URL
event->mimeData()->hasText(); // 文本
event->mimeData()->hasImage(); // 图片数据

5.2 拖放事件的流程

1
2
3
4
5
6
7
8
9
10
11
拖着文件进入窗口

dragEnterEvent()

如果 accept,Qt 才允许继续拖放

用户松手

dropEvent()

读取 mimeData() 并处理
  • 所以 dragEnterEvent() 的作用不是处理文件,而是告诉 Qt:这个东西我能接收。

  • 真正处理数据一般在:dropEvent()里面。

例题:拖入文件导入资源。比如只接受图片:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void PetWidget::dragEnterEvent(QDragEnterEvent *event)
{
if (!event->mimeData()->hasUrls()) {
event->ignore();
return;
}

const QList<QUrl> urls = event->mimeData()->urls();
if (urls.isEmpty()) {
event->ignore();
return;
}

QString path = urls.first().toLocalFile();

if (path.endsWith(".png", Qt::CaseInsensitive) ||
path.endsWith(".jpg", Qt::CaseInsensitive) ||
path.endsWith(".gif", Qt::CaseInsensitive)) {
event->acceptProposedAction();
} else {
event->ignore();
}
}

Q: 这个event被ignore了会怎么样?

A: 在 dragEnterEvent() 里调用

1
2
event->ignore();
return;

就是告诉 Qt:这个拖进来的东西我不接收。

于是通常表现为:

1
2
3
鼠标光标会变成禁止拖放的样子
用户松开鼠标时不会触发有效 drop
你的 dropEvent() 通常也不会被调用

然后在 dropEvent() 里真正导入:

1
2
3
4
5
6
7
8
9
10
11
void PetWidget::dropEvent(QDropEvent *event)
{
const QList<QUrl> urls = event->mimeData()->urls();

for (const QUrl &url : urls) {
QString path = url.toLocalFile();
qDebug() << "准备导入图片:" << path;
}

event->acceptProposedAction();
}

6 事件过滤器与事件的发送

Qt中提供了事件过滤器,来实现在一个部件中监控其它多个部件的事件。

  • 事件过滤器是什么

正常情况下,一个事件会直接送到目标对象。

比如用户点击 QLineEdit

1
2
3
鼠标点击

QLineEdit 收到 mousePressEvent()

但如果安装了事件过滤器,流程会变成:

1
2
3
4
5
6
7
鼠标点击

事件过滤器先看到这个事件

过滤器决定:放行 / 拦截

如果放行,QLineEdit 再处理

6.1 事件过滤器的基本写法

事件过滤器函数是:

1
bool eventFilter(QObject *watched, QEvent *event) override;

通常写在父窗口类里。

例如:

1
2
3
4
5
6
7
8
9
10
11
class MyWidget : public QWidget
{
public:
explicit MyWidget(QWidget *parent = nullptr);

protected:
bool eventFilter(QObject *watched, QEvent *event) override;

private:
QLineEdit *edit = nullptr;
};

构造函数里安装过滤器:

1
2
3
4
5
MyWidget::MyWidget(QWidget *parent) : QWidget(parent)
{
edit = new QLineEdit(this);
edit->installEventFilter(this);
}

这句:

1
edit->installEventFilter(this);

意思是:让 this 帮 edit 过滤事件。

之后,edit 收到事件前,this->eventFilter(...) 会先被调用。

例子:拦截 QLineEdit 的 Enter 键。

1
2
3
4
5
6
7
8
9
10
11
12
13
bool MyWidget::eventFilter(QObject *watched, QEvent *event)
{
if (watched == edit && event->type() == QEvent::KeyPress) {
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);

if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) {
qDebug() << "在事件过滤器中拦截 Enter";
return true;
}
}

return QWidget::eventFilter(watched, event);
}

这里有几个关键点。

1
watched == edit

表示:这个事件是不是来自 edit

1
event->type() == QEvent::KeyPress

表示:这个事件是不是键盘按下事件。

1
static_cast<QKeyEvent *>(event)

表示:既然已经确认它是 KeyPress,就把通用的 QEvent * 转成更具体的 QKeyEvent *,这样才能调用 key()

6.2 return true 和 return false 的区别

这是事件过滤器最重要的地方。

1
return true;

表示:事件被拦截了,不再交给原来的对象处理。

也就是说,edit 不会再收到这个 Enter 键事件。

1
return false;

或者:

1
return QWidget::eventFilter(watched, event);

表示:不拦截,继续交给原来的对象处理。

Q: 事件过滤器的典型用途?

A: 它适合这种场景:

1
2
3
4
不想继承某个控件,但想监听它的事件。
想统一监听多个控件的事件。
想在事件到达控件之前提前处理。
想让父窗口管理子控件的一些特殊行为。

比如:

1
2
3
4
监听多个输入框的 Enter 键。
鼠标进入某个控件时改变样式。
拦截 Esc 键关闭弹窗。
在 QTextEdit 中实现 Enter 发送、Shift+Enter 换行。

6.3 事件的发送

事件发送指的是:程序主动创建一个事件,然后发给某个对象。

常见函数有两个:

1
2
static bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event);
static void QCoreApplication::postEvent(QObject *receiver, QEvent *event);

它们区别很重要。

6.3.1 sendEvent

1
2
QKeyEvent event(QEvent::KeyPress, Qt::Key_A, Qt::NoModifier, "a");
QCoreApplication::sendEvent(edit, &event);

意思是:

1
2
立刻把这个键盘事件发给 edit。
sendEvent 会同步执行,等事件处理完才返回。

可以理解为:现在就送,现在就处理。

6.3.2 postEvent

1
2
QKeyEvent *event = new QKeyEvent(QEvent::KeyPress, Qt::Key_A, Qt::NoModifier, "a");
QCoreApplication::postEvent(edit, event);

意思是:

1
2
把事件放进 Qt 事件队列。
等事件循环有空时再处理。

可以理解为:先排队,稍后处理。

注意:postEvent() 传进去的事件一般要用 new 创建,因为 Qt 之后会接管并删除它。