Qt 对于事件的自定义处理

1. 概述

  • 已知了事件传递的过程, 可针对每个环节来自定义事件的处理. 有五个层次来自定义处理事件.

1.1. 程序示例

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
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
class MyApplication : public QApplication
{
Q_OBJECT
public :
MyApplication(int &argc, char **argv): QApplication(argc, argv) { }
~MyApplication() {}
protected:
/*--- [1] application notify ---*/
virtual bool notify(QObject *object, QEvent *event) {
if (event->type() == QEvent::MouseButtonRelease) {
qDebug() << "[notify]" << "objName: " << object->objectName();
// return false; return true; 只要 return, 该 event 就不会继续传递
// 若需要继续分发该 event, 则需执行 QApplication::notify
}
return QApplication::notify(object, event);
}
};

class ClassFilter: public QObject
{
Q_OBJECT
public:
ClassFilter(QObject *parent = 0) : QObject(parent) {}
~ClassFilter() {}
protected:
virtual bool eventFilter(QObject *watched, QEvent *event) {
if (event->type() == QEvent::MouseButtonRelease) {
qDebug() << QString::fromUtf8("[eventFilter] [%1] ").arg(objectName()) << "objName: " << watched->objectName();
//return true; // 事件终止传递
//return false; // 事件继续传递
}
return QObject::eventFilter(watched, event);
}
};

class MyButton : public QPushButton
{
Q_OBJECT
public:
MyButton(QWidget *parent = 0): QPushButton(parent) {}
~MyButton() {}

protected:
/*--- [4] 事件接受者的 event ---*/
virtual bool event(QEvent *e) {
if (e->type() == QEvent::MouseButtonRelease) {
qDebug() << QString::fromUtf8("[event]");
//return true; // 事件终止传递
//return false; // 事件继续向父控件传递
}
return QPushButton::event(e);
}
/*--- [5] 事件接受者的 event Handle ---*/
virtual void mouseReleaseEvent(QMouseEvent *event) {
qDebug() << "[event Handle]";

// 根据需求判断是否需要调用父类 event Handler
QPushButton::mouseReleaseEvent(event);

//event->accept(); // 事件终止传递
//event->ignore(); // 事件继续向父控件传递
}
};

class MyUI: public QWidget
{
Q_OBJECT
public:
MyUI(QWidget *parent = 0): QWidget(parent) {
QPushButton *btn = new QPushButton(this);
btn->setObjectName("pushButton");

/*--- [3] 在组件上安装事件过滤器 ---*/
ClassFilter* filterWgt = new ClassFilter(this);
filterWgt->setObjectName("filterWgt");
btn->installEventFilter(filterWgt);
}
};

//main.cpp
int main(int argc, char *argv[])
{
MyApplication a(argc, argv);

MyUI w;
w.show();

/*--- [2] 在 application 安装事件过滤器 ---*/
ClassFilter filterApp;
filterApp.setObjectName("filterApp");
a.installEventFilter(&filterApp);

return a.exec();
}
  • 执行结果

    1
    2
    3
    4
    5
    6
    7
    [notify] objName:  "MyUIClassWindow"
    "[eventFilter] [filterApp] " objName: "MyUIClassWindow"
    [notify] objName: "pushButton"
    "[eventFilter] [filterApp] " objName: "pushButton"
    "[eventFilter] [filterWgt] " objName: "pushButton"
    "[event]"
    [event Handle]

2. 重写receiverEventHandler

  • 这是最普通简单的方法. 实现的功能也最简单, 且仅可用于有独立事件处理器的事件(很多不常用的事件就直接在event()中被处理了)
  • 注意: 需时刻注意是否需要通过调用父类的同名函数来确保原有实现仍能进行!不注意这个将可能产生很难调的 Bug.
    • eg: 有一个类 A, 继承自 QPushButton. 在类 A 中将他的 clicked() 信号关联到了槽 slot_a(). 又重写了该类的 mousePressEvent 回调函数. 在该函数中并没有调用父类的同名函数 QPushButton::mousePressEvent(). 因为 QPushButton 的 clicked 信号在 QPushButton::mousePressEvent() 中发出, 而该函数并未执行. 所以按钮的 clicked 信号将永远不会发出. slot_a() 也将永远不会被执行.

2.1. accept()ignore()

  • 调用该事件的accept(), 则这个事件就不会被继续传播给其父组件.
  • 调用该事件的ignore(), Qt 会从其父组件中寻找另外的接受者.

2.1.1. 使用

Tips: 当自己重写了事件处理函数时, 若想这个事件继续传播, 则调用父类的该事件处理函数. 若想事件就此打住, 则不用调用父类的事件处理函数.

  • 其实自己很少手动调用这两种方法, 当我们想忽略该事件时, 只需调用父类的相应事件处理函数即可. 因为我们不知道父类中的实现. 若我们在子类中直接忽略该事件, 则父类将接收不到该事件. 所以应尽量调用父类取实现.

  • 为此 Qt 有默认的设计:

    • 事件对象默认为accept()的.
    • 所有组件的父类QWidget默认的实现则是调用ignore()
  • 这样, 若在自己实现的事件处理函数中, 不调用 QWidget 的事件处理函数, 就相当于调用了accept(). 若想忽略信号, 则直接调用 QWidget 的事件处理函数.

  • 必须手动调用的特殊场景:

    • 对于窗口关闭事件QCloseEvent:
      • accept()的调用意味着 Qt 将停止事件的传播, 窗口关闭.
      • ignore()的调用意味着事件继续传播, 阻止窗口关闭.

3. 重写接收者的event()函数

  • event() 函数负责将到来的所有 event 按照 event->type() 不同, 分发给不同的 event Handler.
  • 可达到的效果: 自定义事件的分发.

3.1. 作用

  • 重写该组件的event()后, 在分发到各 event Handler 之前, 完成一些自定义的事情. 如下:
    1. 直接在该函数中写入对关系的事件的自定义处理. 不用一个一个重写各种事件处理器了. (但若在父类的 event() 调用问题上出错, 将有很大的影响. )
    2. 直接屏蔽掉一些不关心的事件. ( QEvent::type() 判断之后, 在不关心的 case 分支下什么都不写. )

3.2. 缺点

  • 虽然可以在event()中屏蔽掉不关心的事件. 但是当需要屏蔽事件的组件很多时, 工程量巨大. (eg: 所有组件都不响应鼠标事件, 则需要在所有组件中都重写 event(), 在 event() 中不分发鼠标事件).
  • 所以 Qt 提供了 eventFilter (事件过滤器). 只需将主窗体中为你每个部件都installEventFilter(主窗体)之后, 重写主窗体的eventFilter(), 在其中屏蔽掉鼠标事件即可.

3.3. 代码实例

只处理 tab 按键事件, 其他都不处理(因为所有信号都未分发)

1
2
3
4
5
6
7
8
9
10
11
bool CustomTextEdit::event(QEvent *e)
{
if (e->type() == QEvent::KeyPress) { // 事件
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
if (keyEvent->key() == Qt::Key_Tab) { // 只接受 tab
qDebug() << "You press tab.";
return true;
}
}
return false;
}

只对 tab 事件做了特殊处理(处理后屏蔽). 其他事件不受影响(其他信号分发)

1
2
3
4
5
6
7
8
9
10
{
if (e->type() == QEvent::KeyPress) { // 事件
QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
if (keyEvent->key() == Qt::Key_Tab) { // 只接受 tab
qDebug() << "You press tab.";
return true; // 不会再传递
}
}
return 父类: :event(); // 其他事件, 不受影响
}

4. 事件过滤器

  • 每个 QObject 对象都维护一个 QObjectList eventFilters; 成员变量.

    1
    2
    3
    4
    5
    6
    // 示例
    QObject *a = new QObject;
    QObject *b = new QObject;
    // 在 a 上安装事件过滤器 b.
    // b 被存储在 a 的成员变量 eventFilters 中.
    a->installEventFilter(b);
  • 当事件到达对象 A 的event()之后, 会根据事件类型传递给 A 的各事件处理函数. 但可以在 A 上安装事件过滤器 A->installEventFilter(B); , 使得 A 的这些事件在到达事件处理函数之前, 先被对象 B 的eventFilter()进行处理.
  • 根据被安装事件过滤器的对象, 可分为以下两种:
    • QCoreApplication::instance(), 也就是qApp上面安装事件过滤器. 这样该应用程序的所有事件将经过该事件过滤函数的过滤.
    • 在其他对象上安装事件过滤器, 只有发送给该对象的事件会被该事件过滤函数过滤.

4.1. 优缺点

  • 优点:
    • 不用继承该控件, 就能自定义处理其事件. 因为各事件处理函数为 protected, 外部无法直接访问. 而安装事件过滤器就能在 事件处理函数 之前, 处理事件.
  • 缺点:
    • 只能用于单线程. 事件过滤器和被安装过滤器的组件必须在同一线程, 否则, 过滤器将不起作用. 另外, 如果在安装过滤器之后, 这两个组件到了不同的线程, 那么, 只有等到二者重新回到同一线程的时候过滤器才会有效.

4.2. 方法

1
2
3
4
5
6
7
8
9
10
// 安装
void QObject::installEventFilter(QObject * filterObj);

// 卸载
void QObject::removeEventFilter(QObject * obj);

// 事件过滤函数
// 返回值: 不想该事件被继续传递下去(包括发送给 receiver), 返回 true. 否则返回 false.
// 注意: 若在事件过滤函数中 delete 了某个组件, 则必须返回`true`. 否则 Qt 还将事件分发给这个组件, 程序崩溃
bool QObject::eventFilter(QObject * watched, QEvent * event) [virtual];

4.3. 特别注意

  • QAbstractScollArea 及其子类上安装事件过滤器时, 要安装在其视口上.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    QTextEdit edit;
    edit.installEventFilter(this);
    edit.viewPort()->installEventFilter(this); // 在视口上安装事件过滤器

    bool eventFilter(QObject *watcher, QEvent *event)
    {
    if (watcher == edit) {
    if (event->type == QEvent::KeyPress) {
    //...
    return true;
    }
    }
    return QWidgert::eventFilter(watcher, event);
    }
  • 在一个控件或 qApp 安装了多个事件过滤器

1
2
3
4
5
6
7
8
9
btn->installEventFilter(A);
btn->installEventFilter(B);

// B::eventFilter 先收到, 不做处理时, A::eventFilter 收到

qApp->installEventFilter(A);
qApp->installEventFilter(B);

// B::eventFilter 先收到, 不做处理时, A::eventFilter 收到

5. 重写notify()函数

  • 这种方法是影响最大的方法, 因为该函数负责将事件发送给接收者.
  • 该方法不受线程限制, 其可用于多线程. 但是全局范围内只能有一个被使用(因为 QCoreApplication 是单例的).
  • QApplication 重写了 QCoreApplication 的 notify(). 具体可查看上一节的源码分析部分.
  • 根据本程序是 QCoreApplication 还是 QAPplication, 选择子类化哪个类.

— 道理越辩越明, 欢迎留言讨论. —

0%