Qt 事件循环机制 & 事件过滤器


Qt中,事件被封装成一个个对象,所有的事件均继承自抽象类QEvent.  接下来依次谈谈Qt中有谁来产生、分发、接受和处理事件

1、谁来产生事件: 最容易想到的是我们的输入设备,比如键盘、鼠标产生的

keyPressEvent,keyReleaseEvent,mousePressEvent,mouseReleaseEvent事件(他们被封装成QMouseEvent和QKeyEvent),这些事件来自于底层的操作系统,它们以异步的形式通知Qt事件处理系统,后文会仔细道来。当然Qt自己也会产生很多事件,比如QObject::startTimer()会触发QTimerEvent. 用户的程序可还以自己定制事件。

2、谁来接受和处理事件:答案是QObject。在Qt的内省机制剖析一文已经介绍QObject 类是整个Qt对象模型的心脏,事件处理机制是QObject三大职责(内存管理、内省(intropection)与事件处理制)之一。任何一个想要接受并处理事件的对象均须继承自QObject,可以选择重载QObject::event()函数或事件的处理权转给父类。

3、谁来负责分发事件:对于non-GUI的Qt程序,是由QCoreApplication负责将QEvent分发给QObject的子类-Receiver. 对于Qt GUI程序,由QApplication来负责。

接下来,将通过对代码的解析来看看QT是利用event loop从事件队列中获取用户输入事件,又是如何将事件转义成QEvents,并分发给相应的QObject处理。



  1. int main(int argc, char *argv[])     
  2. {     
  3.     QApplication app(argc, argv);     
  4.     Widget window;  // Widget 继承自QWidget     
  5.     window.show();     
  6.     return app.exec(); // 进入Qpplication事件循环   

以下是Qpplication事件循环的函数调用过程:

main(int, char **)   
QApplication::exec()   
QCoreApplication::exec()   
QEventLoop::exec(ProcessEventsFlags )   //进入事件循环分发
QEventLoop::processEvents(ProcessEventsFlags )   //事件处理,该函数内区分调用平台相关的processEvents()函数
QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags)                //拿到用户的输入事件,调用windows API处理事件


以上总结为,Qt进入QApplication的event loop,经过层层委任,最终QEventloop的processEvent将通过与平台相关的QAbstractEventDispatcher的子类QEventDispatcherWin32获得用户的用户输入事件,并将其打包成message后,通过标准Windows API ,把消息传递给了Windows OS,Windows OS得到通知后回调QtWndProc,  至此事件的分发与处理完成了一半的路程;


事件经OS处理完毕,回调到widget过程:


QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) //拿到Widget指针和事件,用于回调


bool QETWidget::translateMouseEvent(const MSG &msg)   //该函数所在与Windows平台相关,主要职责就是把已windows格式打包的鼠标事件解包、翻译成QApplication可识别的QMouseEvent,QWidget,call QApplicationPrivate::sendMouseEvent(...)  


//至此与平台相关代码处理完毕     
//MouseEvent默认的发送方式是spontaneous, 所以将执行sendSpontaneousEvent。 sendSpontaneousEvent() 与 sendEvent的代码实现几乎相同,
除了将QEvent的属性spontaneous标记不同。 这里是解释什么spontaneous事件:如果事件由应用程序之外产生的,比如一个系统事件。 
显然MousePress事件是由视窗系统产生的一个的事件,因此它是   spontaneous事件

bool QApplicationPrivate::sendMouseEvent(...) //  判断出事件类型来决定调用QApplication::sendSpontaneousEvent()或是QApplication::sendEvent()


inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event) //调用  notifyInternal()


bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event)   //call notify()


// QCoreApplication::notify和它的重载函数QApplication::notify在Qt的派发过程中起到核心的作用,Qt的官方文档时这样说的:
任何线程的任何对象的所有事件在发送时都会调用notify函数.

bool QApplication::notify(QObject *receiver, QEvent *e)   //里面主要是一个switch case结构来处理不同的事件,call notify_helper()进一步处理具体事件


bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)   //主要先将事件交给事件过滤器来处理(如果安装有时间过滤器的话),最后call event()


bool QWidget::event(QEvent *event)   //通过switch case 将事件交给具体的事件handler,如mousePressEvent()完成整个事件循环过程。




事件过滤器机制


Qt提供5个级别的事件处理和过滤:


1,重新实现事件函数。 比如: mousePressEvent(), keyPress-Event(), paintEvent() 。


这是最常规的事件处理方法。


2,重新实现QObject::event().


这一般用在Qt没有提供该事件的处理函数时。也就是,我们增加新的事件时。


3,安装事件过滤器


4,在 QApplication 上安装事件过滤器。


这之所以被单独列出来是因为: QApplication 上的事件过滤器将捕获应用程序的所有事件,而且第一个获得该事件。也就是说事件在发送给其它任何一个event filter之前发送给QApplication的event filter。


5,重新实现QApplication 的 notify()方法.


Qt使用 notify()来分发事件。要想在任何事件处理器捕获事件之前捕获事件,唯一的方法就是重新实现QApplication 的 notify()方法。


/****************************************************************************/


Qt创建了QEvent事件对象之后,会调用QObject的event()函数做事件的分发。有时候,你可能需要在调用event()函数之前做一些另外的操作,比如,对话框上某些组件可能并不需要响应回车按下的事件,此时,你就需要重新定义组件的event()函数。如果组件很多,就需要重写很多次event()函数,这显然没有效率。为此,你可以使用一个事件过滤器,来判断是否需要调用event()函数。


  QOjbect有一个eventFilter()函数,用于建立事件过滤器。这个函数的签名如下:


virtual bool QObject::eventFilter ( QObject * watched, QEvent * event )


  如果watched对象安装了事件过滤器,这个函数会被调用并进行事件过滤,然后才轮到组件进行事件处理。在重写这个函数时,如果你需要过滤掉某个事件,例如停止对这个事件的响应,需要返回true。


  bool MainWindow::eventFilter(QObject *obj, QEvent *event)


   {


   if (obj == textEdit) {


   if (event->type() == QEvent::KeyPress) {


   QKeyEvent *keyEvent = static_cast(event);


   qDebug() << "Ate key press" << keyEvent->key();


   return true;


   } else {


   return false;


   }


   } else {


   // pass the event on to the parent class


   return QMainWindow::eventFilter(obj, event);


   }


   }


  上面的例子中为MainWindow建立了一个事件过滤器。为了过滤某个组件上的事件,首先需要判断这个对象是哪个组件,然后判断这个事件的类型。例如,我不想让textEdit组件处理键盘事件,于是就首先找到这个组件,如果这个事件是键盘事件,则直接返回true,也就是过滤掉了这个事件,其他事件还是要继续处理,所以返回false。对于其他组件,我们并不保证是不是还有过滤器,于是最保险的办法是调用父类的函数。


  在创建了过滤器之后,下面要做的是安装这个过滤器。安装过滤器需要调用installEventFilter()函数。这个函数的签名如下:


 


 void QObject::installEventFilter ( QObject * filterObj )


  这个函数是QObject的一个函数,因此可以安装到任何QObject的子类,并不仅仅是UI组件。这个函数接收一个QObject对象,调用了这个函数安装事件过滤器的组件会调用filterObj定义的eventFilter()函数。例如,textField.installEventFilter(obj),则如果有事件发送到textField组件是,会先调用obj->eventFilter()函数,然后才会调用textField.event()。


  当然,你也可以把事件过滤器安装到QApplication上面,这样就可以过滤所有的事件,已获得更大的控制权。不过,这样做的后果就是会降低事件分发的效率。


  如果一个组件安装了多个过滤器,则最后一个安装的会最先调用,类似于堆栈的行为。


  注意,如果你在事件过滤器中delete了某个接收组件,务必将返回值设为true。否则,Qt还是会将事件分发给这个接收组件,从而导致程序崩溃。


  事件过滤器和被安装的组件必须在同一线程,否则,过滤器不起作用。另外,如果在install之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。


  事件的调用最终都会调用QCoreApplication的notify()函数,因此,最大的控制权实际上是重写QCoreApplication的notify()函数。由此可以看出,Qt的事件处理实际上是分层五个层次:重定义事件处理函数,重定义event()函数,为单个组件安装事件过滤器,为QApplication安装事件过滤器,重定义QCoreApplication的notify()函数。这几个层次的控制权是逐层增大的。




以上出处:http://mobile.51cto.com/symbian-272812.htm

http://download.csdn.net/detail/foolish1212/5330380


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值