0%

事件机制

QWidget::installEventFilter

当 obj 收到任何事件时,这些事件将首先被传递给 this(MyWidget)的 eventFilter 方法。

例如 btn->installEventFilter(this);

返回值:

if you want to filter the event out, i.e. stop it being handled further, return true; otherwise return false.

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
class MyWidget : public QWidget
{
public:
explicit MyWidget() { this->installEventFilter(this); } // 注册事件过滤器

protected:
bool eventFilter(QObject *obj, QEvent *event) override
{
if (event->type()
== QEvent::MouseButtonPress) { // 每次鼠标按下就会触发(left,mid,right都会触发)
qDebug() << "eventfilter";
return true; // 阻止事件继续传播
}
return QWidget::eventFilter(obj, event); // 其他事件正常处理
}
bool event(QEvent *eve) override //filter happen before event
{
if (eve->type() == QEvent::MouseButtonPress)
qDebug() << "Event()";
return QWidget::event(eve);
}
};

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
auto *w = new MyWidget();
w->setGeometry(200, 200, 1000, 800);
w->show();
return a.exec();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
QPushButton  {
min-height: 20px;
max-height: 60px;
margin: 5px;
padding: 20px;
margin:20px;
font-size: 20px;
color: white;
background-color: black;
border: 1px solid black;
border-radius:10px;
}

QPushButton:pressed{
color:green;
padding:20px;
margin:15px;
border-radius:10px;
}

事件传播

当MainWindow中的Gbutton发生mousePressEvent事件后,QPushButton默认实现不会传播到MainWindow的mousePressEvent,只有调用event->ignore();并且不能再调用QPushButton::mousePressEvent();否则仍然会accept();

那么我认为一切event先考虑设置成accept也就是不会传播,再考虑交给其他组件处理,这样保证不会出太大问题

才会传播到父组件,默认也就是event->accept();等价于 setAccepted(true)

通过event()函数编写处理代码是要通过返回值告诉调用者是否被接收,accept() or ignore() 没有意义

Calling accept() or ignore() on an event from event() is pointless

void GButton::mousePressEvent(QMouseEvent *e) {
e->ignore();
INFO(“[button]clicked”);
QPushButton::mousePressEvent(e); 如果调用基类的处理函数,那么会默认accept
}

Qt is designed in such a way that you normally never need to call them. The default value is “accept”, and the default event handler implementations in QWidget call ignore()

Qt 默认是accept也就是不会传播,但是QWidget的实现是ignore(),也就是会传播

Another Look at Events

qevent.html#ignore

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
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
namespace Ui {
class MainWindow;
}

QT_END_NAMESPACE

class MainWindow : public QMainWindow {
Q_OBJECT

public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();

protected:
void mouseMoveEvent(QMouseEvent *event) override;
bool event(QEvent *e) override;
void mousePressEvent(QMouseEvent *) override;

private:
bool isMenuVisible;
void initMenu();
Ui::MainWindow *ui;
};
#include "mainwindow.h"
#include "gbutton.h"
#include "logger.h"
#include "ui_mainwindow.h"
#include <QMetaEnum>
#include <QMetaObject>
#include <QMouseEvent>
#include <QTimer>


MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) {
ui->setupUi(this);
auto btn = new GButton();

btn->setParent(this);
btn->move(geometry().center() - btn->geometry().center());
INFO(__PRETTY_FUNCTION__);
initMenu();
setMouseTracking(true);
}

MainWindow::~MainWindow() {
delete ui;
}

void MainWindow::mouseMoveEvent(QMouseEvent *event) {
// INFO(__PRETTY_FUNCTION__);
// INFO(rand());
QMainWindow::mouseMoveEvent(event);
}

bool MainWindow::event(QEvent *e) {
// QMetaObject meta = QEvent::staticMetaObject;
// QMetaEnum metaEnum = meta.enumerator(meta.indexOfEnumerator("Type"));

// // 使用QMetaEnum将枚举值转换为字符串
// QString eventType = metaEnum.valueToKey(e->type());

// // 输出事件类型字符串
// qDebug() << "[MainWindow]Event type:" << eventType;
// e->accept();
return QMainWindow::event(e);
}

void MainWindow::mousePressEvent(QMouseEvent *event) {
if (event->button() == Qt::LeftButton) {
// 这里处理鼠标左键单击事件
qDebug() << "[mainwindow] left clicked";
}

return QMainWindow::mousePressEvent(event);
}

void MainWindow::initMenu() {
connect(ui->file_open_action, &QAction::triggered, this, [](bool check) {
INFO("file_open_action triggered " << (check ? "checked" : "unchecked"));
});
connect(ui->backup_action, &QAction::triggered, this, [](bool check) {
INFO("backup_action triggered" << (check ? "checked" : "unchecked"));
});
// connect(ui->menubar, &QMenuBar::hovered, this, [this](QAction *action) {
// INFO(action->text());
// auto menu = action->menu();

// menu->setIcon(QIcon(R"(C:\Project_repository\save\image\cat_640_640.jpg)"));
// });
}

#include <QPushButton>

class GButton : public QPushButton {
public:
explicit GButton();

protected:
bool event(QEvent *e) override;
void mousePressEvent(QMouseEvent *e) override;
};

#include "gbutton.h"
#include "logger.h"
#include <QMouseEvent>

GButton::GButton() {
setFixedSize({300, 100});
setText("button clicked");
}

bool GButton::event(QEvent *e) {
// INFO(__FUNCTION__);
return QPushButton::event(e);
}

void GButton::mousePressEvent(QMouseEvent *e) {
e->ignore();
INFO("[button]clicked");
}

协程(Coroutine)既是函数也是协程

协程:允许暂停的函数(函数的泛化)

普通函数的工作原理

普通函数包含两个操作 : call && return

  • call: 暂停函数执行,创建 activation frame 转到被调用函数的执行
  • return: 将返回值保存,传给调用者,销毁 activation frame,在调用处恢复调用者的执行

Activation Frames

the block of memory that holds the current state of a particular invocation of a function

保存了函数调用状态的内存,包括参数的值和局部变量的值

对于普通函数, activation frame 保存返回地址(通常由 call 指令保存),

以及调用者的 activation frame 的地址,以便恢复该函数的执行状态

普通函数的 activation frame 具有严格的 lifetimes 嵌套,这允许高效的数据结构来分配和释放,比如 stack

所以 activation frame 也通常称为 stack frame

Coroutines

协程通过将 Call 和 Return 操作中执行的一些步骤分离为三个额外操作来概括函数的操作:

SuspendResumeDestroy

  • Suspend 暂停当前协程执行,并将执行转移回调用方或恢复方,协程执行暂停后,暂停时作用域内的任何对象仍保持存活状态。

  • Resume 在挂起的协程的挂起点处恢复执行。

  • Destroy 销毁 Coroutine activation frame 不再恢复执行。挂起点范围内的任何对象都将被销毁。

Coroutine activation frames

  • 由于协程可以在不销毁激活帧的情况下挂起,因此我们不能再保证激活帧的生存期将严格嵌套。激活帧可能需要存储在堆上。

  • C++ 协程规定,如果编译器可以证明协程的生命周期确实严格嵌套在调用方的生命周期内,则允许从调用方的激活帧分配协程帧的内存。

  • 对于协程,激活帧的某些部分需要在协程挂起期间保留,而有些部分只需要在协程执行时保留。例如一个 scope 不跨越挂起点的变量

  • Coroutine 逻辑上分为:(coroutine frame)协程帧和(stack frame)栈帧

  • “协程帧”保存协程激活帧的部分,该部分在挂起时持续存在,“栈帧”部分仅在协程执行时存在,并在协程挂起并转移回调用者或恢复者时释放

The Suspend operation

  • 挂起操作使得协程在函数中间暂停执行,并将执行转移回协程的调用者或恢复者。

  • 挂起点通过使用 co_awaitco_yield 关键字来标识。

  • 到达挂起点时,要准备恢复的数据,准备工作:寄存器中保存的值写入协程帧,写入暂存点。准备好了,即可视为suspended

  • 协程可以在执行转移回调用者/恢复者之前执行一些附加逻辑。该附加逻辑可以访问协程帧的句柄,该句柄可用于稍后恢复或销毁

  • 挂起后执行逻辑的能力允许恢复协程,或者转移到调用者或恢复者

  • 如果执行转移到调用者/恢复者,则协程激活帧的栈帧部分将被释放并从栈中弹出。

The Resume operation

  • 恢复操作可以在处于suspended状态的协程上执行。

  • 当函数想要恢复协程时,是通过调用被挂起的协程帧句柄上的 void resume() 方法。

  • 像普通的函数调用一样,对 resume() 的调用分配一个新的栈帧,并在将执行转移之前将调用者的返回地址存储在栈帧中。

  • 但是,它不会将执行转移到函数的开头,而是将执行转移到函数中上次挂起的位置。

  • 当协程下次挂起或运行对 resume()调用完成时,将返回并恢复调用函数的执行。

The Destroy operation

  • Destroy 操作会销毁协程帧,此操作只能在挂起的协程上执行。
  • 但是,它不是将执行转移到一个代码块,该代码块在挂起点调用作用域内所有局部变量的析构函数,然后释放协程帧使用的内存。
  • Destroy 操作通过调用相应的 Suspend状态的协程帧句柄上的 void destroy() 方法来销毁的特定激活帧。

The Call operation of a coroutine

  • 协程的调用与普通函数的调用相似,从调用者的角度来看没有什么区别

  • 然而,当函数运行完成时,执行不会仅返回到调用者,而对于协程,调用操作将在协程到达其第一个挂起点时恢复调用者的执行。

  • 协程执行 Call 时,调用者分配一个新的栈帧,保存参数和返回地址到栈帧,并专业将执行转移到协程。

  • 协程的第一件事是在堆上分配一个协程帧,并复制/移动参数从栈帧到协程帧中

The Return operation of a coroutine

  • 协程的 Return 与普通函数的 Return 有所不同。
  • 当协程执行 co_return 时,它保存返回值(具体由由协程定义),然后销毁范围内的局部变量(除了参数)
  • 在将执行转移回调用者/恢复者之前执行一些附加逻辑。
  • 此附加逻辑可能会执行某些操作来发布返回值,或者可能会恢复另一个正在等待结果的协程。
  • 然后,协程执行Suspend/Destroy
  • 然后根据**Suspend/Destroy ** 的语义将执行转移回调用者/恢复者,将激活帧的堆栈帧组件从堆栈中弹出。
  • 注意:传递给 co_return的返回值与从 Call 的返回值不同,因为返回操作可能会从初始 Call 恢复之后很长时间才执行。

Coroutine summery

将协程描述为函数的概括,除了“正常”函数提供的‘Call’ and ‘Return’之外,该函数还具有三个附加操作——‘Suspend’, ‘Resume’和‘Destroy

创建文字

1
$ hexo new "文章名字"

运行服务器

1
2
$ hexo server 
$ hexo s

生成文件(没屁用)

1
$ hexo generate

部署到github(最有用)

1
$ hexo clean && hexo deploy