浅析Qt的事件机制

         Qt的事件和signal是不一样的。signal通常用来"使用"QWidget,而事件用来"实现" QWidget。比如我们在使用一个按钮时,我们只关心他clicked()的signal,然后响应对应的槽函数。而至于这个按钮如何接收处理鼠标事件,再发射这个信号,就是事件的问题,我们要改变这个响应事件的行为的时候,就需要面对event了。例如我们可以重载KeyPressEvent,使得鼠标按键按下(mousePressEvent) 的时候就触发clicked()的signal,而不是通常在释放( mouseReleaseEvent)时候。  

 1)事件的产生

        事件的两种来源:

        一种是系统产生的,通常是System把从系统得到的消息,比如鼠标按键,键盘按键等,放入系统的消息队列中。Qt事件循环的时候读取这些事件,转化为QEvent(),再依次处理。

        一种是由Qt应用程序程序自身产生的。程序产生事件有两种方式,一种是调用QApplication::postEvent()。例如QWidget::update()函数,当需要重新绘制屏幕时,程序调用update()函数,new出来一个paintEvent(),调用QApplication::postEvent(),将其放入Qt的消息队列中,等待依次被处理。 另一种方式是调用sendEvent()函数. 这时候事件不会放入队列, 而是直接被派发和处理, QWidget::repaint()函数用的就是这种方式。

2) 事件的调度

        两种调度方式,一种是同步,一种是异步。

       Qt的事件循环是异步的,当调用QApplication::exec()时,就进入了事件循环。该循环可以简化的描述为如下的代码:

   
   
  1. while ( !app_exit_loop ) {
  2. while( !postedEvents ) {
  3.    processPostedEvents();
  4. }
  5. while( !qwsEvnts ){
  6. qwsProcessEvents();
  7. }
  8. while( !postedEvents ) {
  9. processPostedEvents();
  10. }
  11. }
         先处理Qt事件队列中的事件, 直至为空。再处理系统消息队列中的消息, 直至为空,再处理系统消息的时候会产生新的Qt事件,需要对其再次进行处理。 调用QApplication::sendEvent的时候,,消息会立即被处理,是同步的。实际上QApplication::sendEvent()是通过调用QApplication::notify(),直接进入了事件的派发和处理环节。         

 3) 事件的派发和处理

         首先说明Qt中事件过滤器的概念。事件过滤器是Qt中一个独特的事件处理机制, 功能强大而且使用起来灵活方便,通过它可以让一个对象侦听拦截另外一个对象的事件。事件过滤器是这样实现的: 在所有Qt对象的基类: QObject中有一个类型为QObjectList的成员变量,名字为eventFilters,当某个QObjec (qobjA)给另一个QObject (qobjB)安装了事件过滤器之后,QObjectB会把QObjectA的指针保存在eventFilters中。在QObjectB处理事件之前,会先去检查eventFilters列表,如果非空,就先调用列表中对象的eventFilter()函数。 一个对象可以给多个对象安装过滤器。同样, 一个对象能同时被安装多个过滤器, 在事件到达之后, 这些过滤器以安装次序的反序被调用. 事件过滤器函数( eventFilter() ) 返回值是bool型, 如果返回true, 则表示该事件已经被处理完毕, Qt将直接返回, 进行下一事件的处理; 如果返回false, 事件将接着被送往剩下的事件过滤器或是目标对象进行处理。

         eventFilter()的object参数表示事件发生的来源物件,eventFilter()若返回false,则安装该事件过滤器的对象的event()会继续执行,若返回true,则安装事件过滤器的对象后event()方法就不会被执行,由此进行事件的拦截处理。给本对象安装事件过滤器:this->installEventFilter(this);         

        Qt中,事件的派发是从QApplication::notify() 开始的, 因为QAppliction也是继承自QObject, 所以先检查QAppliation对象, 如果有事件过滤器安装在qApp上, 先调用这些事件过滤器. 接下来QApplication::notify() 会过滤或合并一些事件(比如失效widget的鼠标事件会被过滤掉, 而同一区域重复的绘图事件会被合并). 之后,事件被送到reciver::event() 处理。 同样, 在reciver::event()中, 先检查有无事件过滤器安装在reciever上. 若有, 则调用之. 接下来,根据QEvent的类型, 调用相应的特定事件处理函数. 一些常见的事件都有特定事件处理函数, 比如:mousePressEvent(), focusOutEvent(),  resizeEvent(), paintEvent(), resizeEvent()等等. 在实际应用中, 经常需要重载这些特定事件处理函数在处理事件. 但对于那些不常见的事件, 是没有相对应的特定事件处理函数的. 如果要处理这些事件, 就需要使用别的办法, 比如重载event() 函数, 或是安装事件过滤器。

4)事件的转发

          对于某些类别的事件, 如果在整个事件的派发过程结束后还没有被处理, 那么这个事件将会向上转发给它的父widget, 直到最顶层窗口。事件最先发送给QCheckBox, 如果QCheckBox没有处理, 那么由QGroupBox接着处理, 如果QGroupBox没有处理, 再送到QDialog,因为QDialog已经是最顶层QWidget, 所以如果QDialog不处理, QEvent将停止转发.如何判断一个事件是否被处理了呢? Qt中和事件相关的函数通过两种方式相互通信。QApplication::notify(), QObject::eventFilter(), QObject::event() 通过返回bool值来表示是否已处理. “真”表示已经处理, “假”表示事件需要继续传递. 另一种是调用QEvent::ignore() 或 QEvent::accept() 对事件进行标识. 这种方式只用于event() 函数和特定事件处理函数之间的沟通. 而且只有用在某些类别事件上是有意义的, 这些事件就是上面提到的那些会被转发的事件, 包括: 鼠标, 滚轮, 按键等事件。

         QT将事件封装为QEvent实例以后,会呼叫QObject的event()方法,并且将QEvent实例传送给它,在某些情况下,希望在执行event()之前,先对一些事件进行处理或过滤,然后再决定是否呼叫event()方法,这时候可以使用事件过滤器。

  可以重新定义一个继承自QObject(或其子类)的类的eventFilter()方法,

   
   
  1. bool FilterObject::eventFilter(QObject *object, QEvent *event)
  2. {
  3.   if(event->type() == QEvent::KeyPress)
  4.   {
  5.     QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
  6.     if (keyEvent->key() == Qt::Key_Tab)
  7.     {
  8.       // Process the Tab Key
  9.       return true;
  10.     }
  11.   }
  12.   return false;
  13. }

5) Qt事件的实际应用

Qt程序是事件驱动的, 程序的每个动作都是由幕后某个事件所触发。

Qt提供5个级别的事件处理和过滤:  
1,重新实现特定事件 事件函数 qt的event是通过QObject通知另一个QObject,event用一个QEvent类表示,它是所有event事件的基类, 特殊event如鼠标event用QE vent的子类, 如QMouseEvent类表示。其他的还如 mousePressEvent(), keyPress-Event(),paintEvent() 。 这是最常规的事件处理方法。  

以按键事件为例, 一个典型的处理函数如下:

    
    
  1. void imageView::keyPressEvent(QKeyEvent * event)
  2. {
  3. switch (event->key()) {
  4. case Key_Plus:
  5. zoomIn();
  6. break;
  7. case Key_Minus:
  8. zoomOut();
  9. break;
  10. case Key_Left:
  11. // …
  12. default:
  13. QWidget::keyPressEvent(event);
  14. }
  15. }
 

2,重新实现QObject::event()。 这一般用在Qt没有提供该事件的处理函数时。也就是,我们增加新的事件时,如 QWidget重载了event()函数,并在这里把所有的event转发给相应的event处理函数,如mousePressEvent(),mouseReleaseEvent()等 通过重载event()函数,我们可以在事件被特定的事件处理函数处理之前(象keyPressEvent())处理它. 比如, 当我们想改变tab键的默认动作时,一般要重载这个函数. 在处理一些不常见的事件(比如:LayoutDirectionChange)时,evnet()也很有用,因为这些函数没有相应的特定事件处理函数. 当我们重载event()函数时, 需要调用父类的event()函数来处理我们不需要处理或是不清楚如何处理的事件。

下面这个例子演示了如何重载event()函数, 改变Tab键的默认动作: (默认的是键盘焦点移动到下一个控件上. )

    
    
  1. bool CodeEditor::event(QEvent * event)
  2. {
  3. if (event->type() == QEvent::KeyPress) {
  4. QKeyEvent *keyEvent = (QKeyEvent *) event;
  5. if (keyEvent->key() == Key_Tab) {
  6. insertAtCurrentPosition('\t');
  7. return true;
  8. }
  9. }
  10. return QWidget::event(event);
  11. }
3,给Qt对象增加事件过滤器。Q t还可以设置一个QObject去监视另一个QObject的event,这个功能通过eventfilter()实现的(installEventFilter()函数)。

安装事件过滤器有两个步骤: (假设要用A来监视过滤B的事件)

首先调用B的installEventFilter( const QOject *obj ), 以A的指针作为参数. 这样所有发往B的事件都将先由A的eventFilter()处理。

 然后, A要重载QObject::eventFilter()函数, 在eventFilter() 中书写对事件进行处理的代码.

用这种方法改写上面的例子: (假设我们将CodeEditor 放在MainWidget中)

    
    
  1. MainWidget::MainWidget()
  2. {
  3. // …
  4. CodeEditor * ce = new CodeEditor( this, code editor”);
  5. ce->installEventFilter( this );
  6. // …
  7. }
  8. bool MainWidget::eventFilter( QOject * target , QEvent * event )
  9. {
  10. if( target == ce ){
  11. if( event->type() == QEvent::KeyPress ) {
  12. QKeyEvent *ke = (QKeyEvent *) event;
  13. if( ke->key() == Key_Tab ){
  14. ce->insertAtCurrentPosition('\t');
  15. return true;
  16. }
  17. }
  18. }
  19. return false;
  20. }
返回true通知Qt,我们已经处理了该事件。 如果返回false的话,Qt继续将该事件发送给目标控件,或者 将事件传递给基类的eventFilter()函数。
  4,在 QApplication 上安装事件过滤器   QApplication 上的事件过滤器将捕获应用程序的所有事件,而且第一个获得该事件。也就是说事件在发送给其它任何一个event filter之前发送给QApplication的event filter。 一旦我们给 qApp( 每个程序中唯一的 QApplication 对象 ) 装上过滤器 , 那么所有的事件在发往任何其他的过滤器时 , 都要先经过当前这个 eventFilter(). 
5,重新实现QApplication 的 notify()方法。 QApplication::notify()  是先调用 qApp 的过滤器 再对事件进行分析 以决定是否合并或丢弃。 想要在任何事件过滤器查看任何事件之前先得到这些事件 , 重载这个函数notify()是唯一的办法 通常来说事件过滤器更好用一些 因为不需要去继承 QApplication 而且可以给 QApplication 对象安装任意个数的事件过滤器。 例如, debug 的时候, 处理失效了的 widget 的鼠标事件 , 通常这些事件会被 QApplication::notify() 丢掉


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值