当前位置:   article > 正文

Qt中的事件系统(什么是事件,事件是如何分发的)_qt 事件怎么传播的

qt 事件怎么传播的

本文结构如下:


概述

首先要明白的是:“在 Qt 里,一个事件就是一个对象,所有事件的祖先都来自于 QEvent”。意思就是说,只要有一个事件发生(如鼠标单击事件),此时就会有一个 QEvent 对象被创建出来,然后开始各种传送。由于 Qt 事件系统是依托于元对象系统的,所以所有的 QObject 类都可以接收/处理 QEvent 事件。

说起事件,其实无非就是围绕着“产生-发送-处理”这个基本流程来说的。

如何产生一个事件?

这是最简单的知识点了,当然是创建一个对象啦。这问题和“如何创建一个整数”一样简单。我创建一个整数是“int i”,那我创建一个事件就是“MyEvent event(1, 2, 3)”啦。其中 MyEvent 是我自定义的一个事件类,123是我往这个事件传入的参数。当你不知道怎么创建一个事件对象时,更多的是不知道一个事件对象长啥样。所以我们先看下事件是什么样的一个类。

事件类长什么样?

这是我自定义的一个事件类:

  1. class MyEvent : public QEvent
  2. {
  3. public:
  4. MyEvent();
  5. MyEvent(int x, int y, int z);
  6. static const Type type;
  7. int x;
  8. int y;
  9. int z;
  10. };

别忘了本文最开始的那句话“一个事件就是一个类对象”。你看我写的这个事件类,它里面可以存储三个 int 整数。假如我程序中触发了某某槽函数,产生了一个三维坐标事件,要把坐标发送出去,那我上面写的这个事件类就派上用场了。我创建一个事件对象,并把坐标参数传到这个事件对象里(MyEvent event(1, 2, 3)),这样这个 event 对象就产生了,它承载着一个三维坐标。

Qt 自带的事件类功能更多

Qt 自带的事件类有很多很多,有些事件类还附带了简单的函数,例如 QResizeEvent 这个事件类就有 size() 和 oldSize() 函数;有些事件类还支持多种实际操作,比如 QMouseEvent 支持鼠标单击、双击、移动等,也就是说当鼠标进行这些操作的时候,都会产生一个 QMouseEvent 事件对象。我上面写的那个例子是很简单的,只能存储 xyz 三个整数,我也可以定义多点变量或者函数。

每一个事件类都有个唯一身份值:type值

这个 type 值的作用就是我们在处理事件时用于识别事件类的代号。请看下面实际中的例子:

  1. bool FilterObject::eventFilter(QObject *object, QEvent *event)
  2. {
  3. if (object == target && event->type() == QEvent::KeyPress) {
  4. QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
  5. if (keyEvent->key() == Qt::Key_Tab) {
  6. ......
  7. return true;
  8. } else {
  9. return false;
  10. }
  11. }
  12. return false;
  13. }
  • 不能和系统值冲突

我们在官方文档可以看到,Qt 自带的事件类的 type 值都已经在 QEvent::type 中有了,数值范围在 0 - 999 之间。

  • 自定义的不能冲突

而我自己定义的事件类也有个 type 值,如上文的代码“static const Type type”。为了保证我的这个值不和 Qt 的冲突,所以数值要大于 999。Qt 给我们规定了两个边界值:QEvent::User 和 QEvent::MaxUser,即 1000 - 65535。

但这也太难记了,怎么办?Qt 提供了一个函数 registerEventType() 专门用于自定义事件的注册。如下:

const QEvent::Type MyEvent::type = (QEvent::Type)QEvent::registerEventType()

好了,现在我们已经认识了 Qt 事件类长什么样了。接下来就是怎么把它发送出去了。

如何发送一个事件?

Qt 提供了三个 static 事件发送函数:sendEvent、postEvent、sendPostEvents。函数原型如下:

  1. bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
  2. void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority)
  3. void QCoreApplication::sendPostedEvents(QObject *receiver = Q_NULLPTR, int event_type = 0)

直接发送:sendEvent

这是最好理解的,两个参数中一个是要发给谁,另一个是发送什么事件。这种方式的发送我们要等待对方处理,所以返回值是一个 bool 量来判断。对于许多事件类,有一个名为 isAccepted() 来告诉你是否被处理。因为事件被发送的时候,event 对象并不会被销毁,因此我们要在栈上创建 event 对象

举个例子比较好理解:

  1. int main(int argc, char *argv[])
  2. {
  3. QCoreApplication a(argc, argv);
  4. MainWindow w;
  5. w.show();
  6. MyEvent event(1, 2, 3);
  7. QCoreApplication::sendEvent(&w, &event);
  8. return a.exec();
  9. };

发到队列:postEvent

我们创建一个 Qt 程序的时候,一般在 main 下面会看到 QCoreApplication a(argc, argv) 以及 return a.exec() 的字样。这其实就是开启了 Qt 事件循环来维护一个事件队列,exec 的本质就是不停的调用 processEvent() 函数从队列中获取事件来处理。而 postEvent() 的作用就是把事件发送到这个队列中去。

这种方式不需要等待处理结果,只要把事件发到队列中就可以了,所以返回值是 void。由于事件队列会持有发送的事件对象,在 post 的时候会将事件 delete 掉,所以我们必须在堆上创建 event 对象

在队列中立即处理:sendPostedEvents

看参数我们就可以知道,这个函数的作用就是立刻、马上将队列中的 event_type 类型的事件立马交给 receiver 进行处理。需要注意的是,来自窗口系统的事件并不由这个函数进行处理,而是 processEvent()。

如何处理一个事件?

创建了事件,发送了事件,接下来就是怎么接收处理事件了。

发送的事件在哪里捕获?

我们发送的事件传给了 QObject 对象,更具体点是传给了 QObject 对象的 event() 函数。根据官方文档的说明,所有的事件都会进到这个函数里面,那么我们处理事件就要重写这个 event() 函数。

在 event() 中怎么处理事件?

event() 函数本身不会去处理事件,而是根据事件类型(type值)调用不同的事件处理器函数。大家都是这样的一个流程,所以我们也尽量保持统一。请看下图,其中的 timerEvent()、childEvent() 都是处理事件的函数。

再举个简单的例子:鼠标点击一下 -> 产生鼠标点击事件 -> 接收者的 event() 函数判断类型 -> 发给 mousePressEvent() 函数处理。写成代码就是这样的:

  1. void MyCheckBox :: mousePressEvent( QMouseEvent *event )
  2. {
  3. if(event->button() == Qt::LeftButton)
  4. {
  5. ....//点击左键后实现的功能
  6. } else {
  7. QCheckBox::mousePressEvent(event);
  8. }
  9. }

需要注意的是,重写事件处理器函数时,如果不实现任何功能,最好调用基类的实现。就像上面的那段代码,Qt 本身就已经写了一大堆的实现了,你要是不写上 QCheckBox::mousePressEvent(event) 这个代码,那你写的这个继承于 QCheckBox 类的 MyCheckBox 就不会对鼠标点击产生任何反应。正所谓“你要不会干这事,叫你爸爸来做吧”。

至此,一个完整的事件处理过程已经说完了。此时的你应该不仅了解了 Qt 自带的类是如何处理事件的,而且写个自定义事件也是应该是能下手了。接下来我们对事件处理再说说其他方便的功能:过滤、接收/忽略。

技巧一:接收/忽略事件

所有的事件的祖先都是 QEvent 类,而该类里有个 isAccepted() 函数来判断这个事件是否被接受了。看到这里我们就能推测出 QEvent 类身上有一个私有变量,或者称为标签吧,用来指示 QEvent 是不是被人处理过了。我一个小小的事件处理没处理有啥关系?很有关系!顺序是这样的:当收到一个事件对象时,我先拿到这个事件。如果处理它,那么事情也就到此为止了;如果不去处理,它会传到我爸爸(基类)那儿去;他如果也不处理,就继续传给我的爷爷。

事实上,我们很少会用到 accept()、ignore() 函数的,如果忽略事件(即自己不想去处理这个事件),只要调用父类的响应函数就可以了。

一个特殊的例子:QWidget

Qt 中大部分的事件处理函数都是 accept() 接收的事件对象,而 QWidget 却是 ignore() 的。这样的话如果某个 QWidget 子类想忽略某个事件,既可以调用 ignore() 函数,也可以调用基类 QWidget 的默认实现。另外还有一点需要知道,在图形编辑中,如果指定了父类(注意是父类,不是基类),事件的传播是在组件层面的,而不是靠类继承机制。

用一个小例子加深事件忽略/接收

看代码:

  1. class A : public QPushButton
  2. {
  3. void mousePressEvent(QMouseEvent *e)
  4. { qDebug() << "A";}
  5. };
  6. class B : public A
  7. {
  8. void mousePressEvent(QMouseEvent *e)
  9. { qDebug() << "B";}
  10. };
  11. class MyWidget : public QWidget
  12. {
  13. void mousePressEvent(QMouseEvent *e)
  14. { qDebug() << "MyWidget"; }
  15. };
  16. MainWindow::MainWindow(QWidget *parent) :
  17. QMainWindow(parent)
  18. {
  19. MyWidget *myWidget = new MyWidget();
  20. myWidget->setParent(this);
  21. A *a = new A();
  22. a->setParent(myWidget);
  23. a->setText("A_Button");
  24. B *b = new B();
  25. b->setParent(myWidget);
  26. b->setText("B_Button");
  27. }
  28. void MainWindow::mousePressEvent(QMouseEvent *e)
  29. {
  30. qDebug() << "MainWindow";
  31. }

实验1:点击 B 按钮,运行结果是“B”。因为我们重写了鼠标按下事件处理函数,并没有调用基类函数;

实验2:将 B 的 mousePressEvent() 第一行添加“event->accept()”。运行结果不变,正如上文所述 QEvent 默认是 accept 的,调用这个函数并没有什么区别。

实验3:将 B 的 mousePressEvent() 改成 event->ignore()。运行结果是“B MyWidget”。ignore 是作用是让事件继续传播,因此父组件 MyWidget 也收到了这个事件。而 MyWidget 没有调用基类的事件处理函数,所以事件传播到此就结束了。

必须调用 accept()、ignore() 函数的场景

就是窗口关闭事件!对于 QCloseEvent 来说,接收意味着事件到此结束不再传播,窗口关闭;忽略意味着事件继续传播,窗口仍然开着。

技巧二:过滤事件

其实上面已经就可以对事件搞点事情了,在 event() 函数中,调用处理函数前可以做些事儿。Qt 还设计了 eventFilter() 函数,所以总的来说过滤事件有两种方法,后者更为灵活。

事件接收者是 QObject 类型的,而 QObject 类有个 eventFilter() 函数,有什么用呢?比如在主界面上有一个按钮叫“A”,有一个按钮叫“B”。当 B->installEventFilter(MainWindow) 的时候,B 产生的事件就不会进入接收者的 event() 函数中了,而是进入接收者的 eventFilter() 函数中。如下图。

注意:如果你在事件过滤器中 delete 了某个接收组件,务必将函数返回值设为 true。否则,Qt 还是会将事件分发给这个接收组件,从而导致程序崩溃。事件过滤器和被安装过滤器的组件必须在同一线程,否则,过滤器将不起作用。另外,如果在安装过滤器之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Guff_9hys/article/detail/949565
推荐阅读
相关标签
  

闽ICP备14008679号