一文弄懂QT事件机制

1. 事件来源

  在做开发的时候经常会听到事件,消息队列等名词。甚至日常开发中经常用到。但是为什么会有消息机制呢?
  来看一个问题,假设在一个与世隔绝的小村庄有个五个用户要安装电话线进行相互通信。线路会怎么设计呢?很容易会想到两两相连如图1。

在这里插入图片描述

  虽然这样可以实现相互通信,但是用户量变多了就会比较复杂。可以考虑用一个模块管理所有的消息,每个用户将消息交给这个模块,然后这个模块复制消息分发。无论用户变多少,复杂度变化不大。如图2

在这里插入图片描述

  在电信里面这就是人们常说的交互机。现在的电话,网络通信都是这种这种模式。这中模式最大的优势就是当新的任意两个网络群想实现通信很方便。如果某天这个与世隔绝的村庄想和其他村庄通信,只需要实现两个村庄消息管理模块通信即可。即实现上图消息队列通信,就可以实现两个村庄的通信。

  上面的通信机制是windows操作系统运行的基础。整个windows系统就是不停的接收各种事件,转发执行事件。常见的有鼠标点击事件,键盘点击事件等。windows中有系统的消息队列。它负责接收事件,转发给各个软件。软件负责执行。

2. qt事件类型:

  1. Spontaneous事件:系统中消息队列中的消息,如鼠标键盘按键等,qt通过读取这些事件,再转成QEvent事件加入到qt的消息队列中。

  2. posted事件: qt内部产生的事件 ,被加入 消息队列中,异步调用。

  3. send事件: 由qt产生直接发送给目标对象。同步执行。

3. qt事件执行流程

​ 1. 整个系统事件流转机制综述

  所有事件首先由操作系统捕获,并加入到自己的消息队列中,排队等待被执行。若是qt的事件会被分发给qt程序,qt程序将其封装为qt事件加入到qt程序自己的消息队列中。需要注意的是所有的qt事件都继承QEvent。最后将消息队列中的事件分发给具体控件,在具控件中执行事件。如图3(该图搬运https://www.cnblogs.com/zhaobinyouth/p/7688954.html

在这里插入图片描述

  1. qt内部事件流转机制:

  执行机制如下图4: Qt程序的main()函数会创建一个QApplication对象并执行其exec()函数,这个函数实际上是开始qt的事件循环。用来监听应用程序事件。当监听到事件,会调用notify函数,将事件转发给不同的事件接收对象。每个Qobject对象都有event()函数,但是该函数只做事件分发,不做事件处理。分化即调用相应的事件处理函数 ,一些常见的特定事件有特定的数据处理函数如 :mousePressEvent(), focusOutEvent(), resizeEvent(), paintEvent(), resizeEvent()等等. 也可自己重载event()实现自己的事件处理函数。如按钮点击事件,event()函数对事件进行分派,调用 mouseClickEvent(QMouseEvent*) 事件处理器,里面调用click()成员函数 ,并触发信号SIGNAL(clicked())
在这里插入图片描述

4. post事件流转机制:

  再次回到故事场景。由于小村庄用户间经常聊天,渐渐的开始表达一下不友好的词语。这肯定不利于小村庄的和谐发展。于是乎,网关想对一些不友好的词语进行和谐。在当前的消息传递结构中该怎么办呢?很快大家就想到,既然小村庄所有的消息会在消息队列中汇总,那么只需要在消息队列中设置一个监控程序就可以监控所有消息。便对不利于村庄发展的消息进行和谐。

  同样在qt日常开发中,也会遇到一些需要和谐事件的场景。那么该怎么做。前面我们了解了qt的事件流转机制,那么便可以在事件流转里面解决问题。

4.1 事件流转机制

  首先所有消息会进入notify函数。意味着 ,只需要重写notify()函数便可以监测和谐消息。接着消息进入每个组件对象的event()函数中,也可以重写event函数事件检测。最后消息会 被event转发调用各个事件处理函数。也可以重写对应的事件处理函数。下面对各个重写做详细介绍:

  1. 重写notify函数
bool MyApplication::notify(QObject* obj, QEvent* event){
    if (event->type() == QEvent::MouseButtonPress){
        qDebug() << "\n xxxxxxxxxxxxxxxxxxx notify: " << obj->objectName() << " " << event->type() << endl;
        return false;
    }
    return QApplication::notify(obj, event);
}

  如上实现一个继承了ApplicationMyApplication类,并重写了notify函数。该函数监听了鼠标点击事件,当鼠标点击事件进入notify函数会被捕获并返回false。返回false事件不会向下传递,从而达到事件的监控处理。否者事件依然详细传递。记住最后要调用父类中的notify方法。因为notify里面还有其他处理逻辑,重写函数中没有实现这些逻辑。

  1. 重写event函数:
bool MyButton::event(QEvent* e){
	if (QEvent::MouseButtonPress == e->type()){
		qDebug() << "\n xxxxxxxxxxxxxxxxxxx  MyButton event: " <<  e->type() << endl;
		return true;
	}
	return QWidget::event(e);
}

  上面函数是一个继承了QPushButtonMyButton类。该类重写了event方法。同样该类不活了鼠标按下事件并返回true。所以事件不会详细传递。

  1. 重写具体的事件
void MyButton::mousePressEvent(QMouseEvent* e){
	qDebug() << "\n xxxxxxxxxxxxxxxxxxx  MyButton mousePressEvent : mouse press" << endl;
    return QWidget::mousePressEvent(e);
}

  上面实例依然是MyButton类中,只是重写了mousePressEvent方法。在主键处理事件的时候监控和谐该事件。

4.2 事件过滤机制:

  上面的事件过滤机制是基于事件流转的,意味着在小村庄的通信机制中,每次要增加过滤规则,需要重新换新的设备(即实现新的类)。这种玩法用户可能不能接收。那么有没有可能在每个部分事件接收前预留出接口,基于这个接口做各种过滤事件。这种机制,设备不用更新,每次有新的过滤机制,只需要基于这个接口修改过滤事件即可。

  qt事件处理中就有这种实现,叫事件过滤器 eventFilter ()。 qt在每次调用event()会调用eventFilter()函数。

​ 事件过滤器特性:

  1. 任意的QObject对象都可以作为事件过滤器使用。
  2. 事件过滤器要重写eventFilter()函数
  3. 事件过滤器需要安装。installEvetnFilter()
bool QtWidgetsApplication2::eventFilter(QObject* watched, QEvent* event){
    if (watched == m_btn) {
        if (event->type() == QEvent::MouseButtonPress){
            qDebug() << "\n xxxxxxxxxxxxxxxxxxx  QtWidgetsApplication2 eventFilter: " << watched->objectName() << " " << event->type() << endl;
            return true;
        }
    }
    return false;
}

  如上实现一个继承了QMainWindowQtWidgetsApplication2类,并重写了eventFilter函数。并在该窗口中添加一个MyButton的按钮m_btn。并给该按钮注册QtWidgetsApplication2的事件过滤器。

​ 事件过滤器返回true,事件将被过滤掉。不会传递到event()函数进行分发处理。

m_btn->installEventFilter(this);

  事件过滤器调用流程图如下图5:(该图搬运:https://blog.csdn.net/m0_60259116/article/details/127813131

在这里插入图片描述

  上面介绍的是给每个组件安装事件过滤器。实际上我们还可以给Application安装事件过滤器。而且经过实测,在Application 安装过滤器,事件流转顺序是先notify接着eventFilter

5. send事件流转机制:

  回到小村庄的例子,当前的消息传递模式已经足够用户使用了。但是虽然用户增多,大家渐渐发现消息传输变慢了。日常生活中的消息慢点不影响,但是遇到突发事件,比如急救,救火等。耽误一秒钟都关乎生死。所以大家希望能快点。这时候人们就想出来另外一套方案。比喻每家直接和医院火警相连。一旦发生紧急情况,直接将消息发送到医院和火警,而不经过消息队列。

  前面讲的都是post事件,基于消息队列的流转机制。这种事件传递机制虽然清楚明了。但是有个问题,如果事件很多,消息队列中的时间多,会导致事件处理很慢。对于紧急事件,直接发送消息的机制就很有用了。这个机制在qt中也有实现。

bool QApplication::sendEvent ( QObject * receiver, QEvent * event ) ; 
bool QApplication::postEvent ( QObject * receiver, QEvent * event ) ; // 异步

  该函数是阻塞式的。sendEventQApplicationnotify中加入一条事件,该事件直接发送到接收者。待接收者处理完成返回。整个过程会阻塞发送事件线程。

 bool QtWidgetsApplication2::event(QEvent* e)
      {
          if (QEvent::MouseButtonPress == e->type())
          {
           QMouseEvent* me = dynamic_cast<QMouseEvent*>(e);
              QString str = QString(u8"X坐标: %1 Y坐标: %2").arg(me->x()).arg(me->y());
           QStringEvent* event = new QStringEvent(str);
              qDebug() << u8"\n xxxxxxxxxxxxxxxxxxx 发送消息 : " << str << endl;
              QApplication::sendEvent(m_label, event); //发送自定义事件
              // QApplication::postEvent(m_label, event);
              qDebug() << u8"\n xxxxxxxxxxxxxxxxxxx 消息发完成 : " << str << endl;
          }
          return QWidget::event(e);
      }

   bool QtWidgetsApplication2::eventFilter(QObject* watched, QEvent* event)
   {
       if (event->type() == QStringEvent::TYPE)
       {
           QStringEvent* str = dynamic_cast<QStringEvent*>(event);
           m_label->setText(str->str());
           m_label->adjustSize();
           qDebug() << u8"\n xxxxxxxxxxxxxxxxxxx 过滤器处理消息 : " << str << endl;
           return true;
       }
       return false;
   }

  如上实例,在主窗口的event函数鼠标点击中执行事件发送,然后注册事件监听,监听m_lable事件。会发现发送事件的线程被阻塞

在这里插入图片描述

  注意:一般情况下推荐使用post异步的方式发送事件。QT中对post发送事件有优化 ,当同一个事件被发送多次的时候,会将多个消息合并为一个消息。而send同步的方法会执行每一个消息。

  如果传入的事件已被识别并且处理,则需要返回 true,否则返回 false。如果返回值是 true,那么 Qt 会认为这个事件已经处理完毕,不会再将这个事件发送给其它对象,而是会继续处理事件队列中的下一事件。
在event()函数中,调用事件对象的accept()和ignore()函数是没有作用的,不会影响到事件的传播。

6. 子父级别接收:

  在QT的事件传递机制中,当事件在子组件中没有被处理,会上传到父组件中。

使用事件的函数控制event->ignore() event->accept()

// 主窗口重写鼠标点击事件
void QtWidgetsApplication2::mousePressEvent(QMouseEvent* e)
{
    QString str = u8"消息传到了上级";
    m_label->setText(str);
    m_label->adjustSize();
}

// 主窗口某个按钮重写鼠标点击事件
void MyButton::mousePressEvent(QMouseEvent* e)
{
	 e->accept();
     // e->ignore();
	return;
}

  在上面实例中重写了主窗口和以主窗口为父组件的按钮的鼠标点击事件,当采用’e->accept()',点击按钮,主窗口鼠标点击事件不会被触发。而采用e->ignore(),主窗口鼠标点击事件会被触发。

猜你喜欢

转载自blog.csdn.net/qq_41546984/article/details/134689612