`
美丽的小岛
  • 浏览: 309230 次
  • 性别: Icon_minigender_1
  • 来自: 大连
社区版块
存档分类
最新评论

Qt 事件系统

    博客分类:
  • QT
 
阅读更多

Qt是事件驱动的, 程序每个动作都是由某个事件所触发。 Qt事件的类型很多,我们可以通过查看Qt的 manual中的Event System 和 QEvent 来获得各个事件的详细信息。

为了完整起见,一份Qt4.6的事件列表附在本文后面。

事件来源

  • Spontaneous events(自发事件)
    • 从系统得到的消息,比如鼠标按键,键盘按键等。Qt事件循环的时候读取这些事件,转化为QEvent后依次处理
  • Posted events
    • 有Qt或应用程序产生,放入消息队列
    • QCoreApplication::postEvent()
  • Sent events
    • 由Qt或应用程序产生,不放入队列,直接被派发和处理
    • QCoreApplication::sendEvent()

比如考虑重绘事件处理函数 paintEvent(),3种事件都能使得该函数被调用:

  • 当窗口被其他窗口覆盖后,再次重新显示时,系统将产生 spontaneous 事件来请求重绘
  • 当我们调用 update() 时,产生的是 Posted 事件
  • 当我们调用 repaint() 时,产生的是 Sent 事件

事件派发

事件循环

    while (!exit_was_called) {
        while (!posted_event_queue_is_empty) {
            process_next_posted_event();
        }
        while (!spontaneous_event_queue_is_empty) {
            process_next_spontaneous_event();
        }
        while (!posted_event_queue_is_empty) {
            process_next_posted_event();
        }
    }
  • 先处理Qt事件队列中的事件,直至为空
  • 再处理系统消息队列中的消息,直至为空
  • 在处理系统消息的时候会产生新的Qt事件,需要对其再次进行处理

不通过事件循环

sendEvent的事件派发不通过事件循环。QApplication::sendEvent()是通过调用QApplication::notify(),直接进入了事件的派发和处理环节,是同步的。

sendEvent与postEvent的使用

  • 两个函数都是接受一个 QObject * 和一个 QEvent * 作为参数。
  • postEvent 的 event 必须分配在 heep 上。用完后会被Qt自动删除
  • sendEvent 的 event 必须分配在 stack 上。

例子(发送X按键事件到mainWin):

QApplication::postEvent(mainWin, new QKeyEvent(QEvent::KeyPress, Key_X, 'X', 0));

 

QKeyEvent event(QEvent::KeyPress, Key_X, 'X', 0);
QApplication::sendEvent(mainWin, &event);

 

notify

所有的事件都最终通过 notify 派发到相应的对象中。

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

事件过滤

看看notify()调用的内部函数notify_helper()的源码部分:

  • 先通过 Applicaton 安装的过滤器
  • 如果未被过滤,再通过 receiver 安装的过滤器
  • 如果仍未被过滤,才调用 receiver->event() 函数进行派发

/*!\internal

  Helper function called by notify()
 */
bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
{
    // send to all application event filters
    if (sendThroughApplicationEventFilters(receiver, event))
        return true;
    // send to all receiver event filters
    if (sendThroughObjectEventFilters(receiver, event))
        return true;
    // deliver the event
    return receiver->event(event);
}

事件在传递到对象之前(调用obj->event()函数之前),要先能通过 Applicaton 和 obj 安装的过滤器,那么过滤器是怎么安装的:

  • 首先QObject中有一个类型为QObjectList的成员变量,名字为eventFilters
  • 当某个QObject安装了事件过滤器之后, 它会将filterObj的指针保存在eventFilters中

 

monitoredObj->installEventFilter(filterObj);
  • 在事件到达QObject::event()函数之前,会先查看该对象的eventFilters列表, 如果非空, 就先调用列表中对象的eventFilter()函数.

 

bool QObject::eventFilter ( QObject * watched, QEvent * event )
  • 事件过滤器函数eventFilter()返回值是bool型
    • 如果返回true, 则表示该事件已经被处理完毕, Qt将直接返回, 进行下一事件的处理
    • 如果返回false, 事件将接着被送往剩下的事件过滤器或是目标对象进行处理

对于 QCoreApplication ,由于也是QObject 派生类,安装过滤器方式与前述相同。

事件转发

对于某些类别的事件, 如果在整个事件的派发过程结束后还没有被处理, 那么这个事件将会向上转发给它的父widget, 直到最顶层窗口.

如何判断一个事件是否被处理了呢? (有两个层次)

  • QApplication::notify(), QObject::eventFilter(), QObject::event() 通过返回bool值来表示是否已处理. “真”表示已经处理, “假”表示事件需要继续传递
  • 另一种是调用QEvent::ignore() 或 QEvent::accept() 对事件进行标识,accept表示事件被处理

为清楚起见,贴一点Qt的源码(来自 QApplication::notify()):

    case QEvent::ToolTip:
    case QEvent::WhatsThis:
    case QEvent::QueryWhatsThis:
        {
            QWidget* w = static_cast<QWidget *>(receiver);
            QHelpEvent *help = static_cast<QHelpEvent*>(e);
            QPoint relpos = help->pos();
            bool eventAccepted = help->isAccepted();
            while (w) {
                QHelpEvent he(help->type(), relpos, help->globalPos());
                he.spont = e->spontaneous();
                res = d->notify_helper(w, w == receiver ? help : &he);
                e->spont = false;
                eventAccepted = (w == receiver ? help : &he)->isAccepted();
                if ((res && eventAccepted) || w->isWindow())
                    break;

                relpos += w->pos();
                w = w->parentWidget();
            }
            help->setAccepted(eventAccepted);
        }
        break;

这儿显示了对 WhatsThis 事件的处理:先派发给 w,如果事件被accepted 或已经是顶级窗口,则停止;否则获取w的父对象,继续派发。

事件处理

  • 重新实现一个特定的事件handler

QObject与QWidget提供了许多特定的事件handlers,分别对应于不同的事件类型。(如paintEvent()对应paint事件)

  • 重新实现QObject::event()

event()函数是所有对象事件的入口,QObject和QWidget中缺省的实现是简单地把事件推入特定的事件handlers。

  • 在QObject安装上事件过滤器

事件过滤器是一个对象,它接收别的对象的事件,在这些事件到达指定目标之间。

  • 在aApp上安装一个事件过滤器,它会监视程序中发送到所有对象的所有事件
  • 重新实现QApplication:notify(),Qt的事件循环与sendEvent()调用这个函数来分发事件,通过重写它,你可以在别人之前看到事件。

事件列表

Qt4.6的事件列表:

  • QAccessibleEvent
  • QActionEvent
  • QChildEvent
  • QCloseEvent
  • QCustomEvent
  • QDragLeaveEvent
  • QDropEvent
    • QDragMoveEvent
      • QDragEnterEvent
  • QDynamicPropertyChangeEvent
  • QFileOpenEvent
  • QFocusEvent
  • QGestureEvent
  • QGraphicsSceneEvent
    • QGraphicsSceneContextMenuEvent
    • QGraphicsSceneDragDropEvent
    • QGraphicsSceneHelpEvent
    • QGraphicsSceneHoverEvent
    • QGraphicsSceneMouseEvent
    • QGraphicsSceneMoveEvent
    • QGraphicsSceneResizeEvent
    • QGraphicsSceneWheelEvent.
  • QHelpEvent
  • QHideEvent
  • QHoverEvent
  • QIconDragEvent
  • QInputEvent
    • QContextMenuEvent
    • QKeyEvent
    • QMouseEvent
    • QTabletEvent
    • QTouchEvent
    • QWheelEvent
  • QInputMethodEvent
  • QMoveEvent
  • QPaintEvent
  • QResizeEvent
  • QShortcutEvent
  • QShowEvent
  • QStatusTipEvent
  • QTimerEvent
  • QWhatsThisClickedEvent
  • QWindowStateChangeEvent

http://hi.baidu.com/cyclone/blog/item/fe6ab3de0e9f2155ccbf1aea.html

 

*******************************另一个博客***********************************************************************

Qt事件 
Qt程序是事件驱动的, 程序的每个动作都是由幕后某个事件所触发. Qt事件的类型很多, 常见的qt的事件如下:
键盘事件: 按键按下和松开.
鼠标事件: 鼠标移动,鼠标按键的按下和松开.
拖放事件: 用鼠标进行拖放.
滚轮事件: 鼠标滚轮滚动.
绘屏事件: 重绘屏幕的某些部分.
定时事件: 定时器到时.
焦点事件: 键盘焦点移动.
进入和离开事件: 鼠标移入widget之内,或是移出.
移动事件: widget的位置改变.
大小改变事件: widget的大小改变.
显示和隐藏事件: widget显示和隐藏.
窗口事件: 窗口是否为当前窗口.

还有一些非常见的qt事件,比如socket事件,剪贴板事件,字体改变,布局改变等等.

Qt 的事件和Qt中的signal不一样. 后者通常用来"使用"widget, 而前者用来"实现" widget. 比如一个按钮, 我们使用这个按钮的时候, 我们只关心他clicked()的signal, 至于这个按钮如何接收处理鼠标事件,再发射这个信号,我们是不用关心的. 但是如果我们要重载一个按钮的时候,我们就要面对event了. 比如我们可以改变它的行为,在鼠标按键按下的时候(mouse press event) 就触发clicked()的signal而不是通常在释放的( mouse release event)时候.

2. 事件产生和处理流程

2.1 事件的产生
事件的两种来源:

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

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

// 自定义事件的时候讲述: 需要注意的时, 这两个函数的使用方法不大一样, 一个是new, 一个是....

2.2 事件的调度
两种调度方式,一种是同步的, 一种是异步.

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

while ( !app_exit_loop )
{
   while( !postedEvents ) { processPostedEvents() }
   while( !qwsEvnts ){ qwsProcessEvents();   }
   while( !postedEvents ) { processPostedEvents() }

}

先处理Qt事件队列中的事件, 直至为空. 再处理系统消息队列中的消息, 直至为空, 在处理系统消息的时候会产生新的Qt事件, 需要对其再次进行处理.

调用QApplication::sendEvent的时候, 消息会立即被处理,是同步的. 实际上QApplication::sendEvent()是通过调用QApplication::notify(), 直接进入了事件的派发和处理环节.

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

Qt中,事件的派发是从 QApplication::notify() 开始的, 因为QAppliction也是继承自QObject, 所以先检查QAppliation对象, 如果有事件过滤器安装在qApp上, 先调用这些事件过滤器. 接下来QApplication::notify() 会过滤或合并一些事件(比如失效widget的鼠标事件会被过滤掉, 而同一区域重复的绘图事件会被合并). 之后,事件被送到reciver::event() 处理.

同样, 在reciver::event()中, 先检查有无事件过滤器安装在reciever上. 若有, 则调用之. 接下来,根据QEvent的类型, 调用相应的特定事件处理函数. 一些常见的事件都有特定事件处理函数, 比如:mousePressEvent(), focusOutEvent(),  resizeEvent(), paintEvent(), resizeEvent()等等. 在实际应用中, 经常需要重载这些特定事件处理函数在处理事件. 但对于那些不常见的事件, 是没有相对应的特定事件处理函数的. 如果要处理这些事件, 就需要使用别的办法, 比如重载event() 函数, 或是安装事件过滤器.

事件派发和处理的流程图如下:

 2.4 事件的转发

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


3. 实际运用

根据对Qt事件机制的分析, 我们可以得到5种级别的事件过滤,处理办法. 以功能从弱到强, 排列如下:

3.1 重载特定事件处理函数.
最常见的事件处理办法就是重载象mousePressEvent(), keyPressEvent(), paintEvent() 这样的特定事件处理函数. 以按键事件为例, 一个典型的处理函数如下:

void imageView::keyPressEvent(QKeyEvent * event)
{
    switch (event->key()) {
    case Key_Plus:
        zoomIn();
        break;
    case Key_Minus:
        zoomOut();
        break;
    case Key_Left:
        // … 
    default:
        QWidget::keyPressEvent(event);
    }
}

3.2重载event()函数.
通 过重载event()函数,我们可以在事件被特定的事件处理函数处理之前(象keyPressEvent())处理它. 比如, 当我们想改变tab键的默认动作时,一般要重载这个函数. 在处理一些不常见的事件(比如:LayoutDirectionChange)时,evnet()也很有用,因为这些函数没有相应的特定事件处理函数. 当我们重载event()函数时, 需要调用父类的event()函数来处理我们不需要处理或是不清楚如何处理的事件.

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

bool CodeEditor::event(QEvent * event)
{
    if (event->type() == QEvent::KeyPress)
    {
        QKeyEvent *keyEvent = (QKeyEvent *) event;
        if (keyEvent->key() == Key_Tab)
        {
            insertAtCurrentPosition('/t');
            return true;
        }
    }
    return QWidget::event(event);
}

3.3 在Qt对象上安装事件过滤器.

安装事件过滤器有两个步骤: (假设要用A来监视过滤B的事件) 
首先调用B的installEventFilter( const QOject *obj ), 以A的指针作为参数. 这样所有发往B的事件都将先由A的eventFilter()处理.
然后, A要重载QObject::eventFilter()函数, 在eventFilter() 中书写对事件进行处理的代码. 
用这种方法改写上面的例子: (假设我们将CodeEditor 放在MainWidget中)

MainWidget::MainWidget()
{
    CodeEditor * ce = new CodeEditor( this, “code editor”);
    ce->installEventFilter( this );
}

bool MainWidget::eventFilter( QOject * target , QEvent * event )
{
   if( target == ce )
   {
       if( event->type() == QEvent::KeyPress )
       {
             QKeyEvent *ke = (QKeyEvent *) event;
             if( ke->key() == Key_Tab )
             {
                ce->insertAtCurrentPosition('/t');
                return true;
             }
      }
   }
   return false;
}

3.4 给QAppliction对象安装事件过滤器.
一 旦我们给qApp(每个程序中唯一的QApplication对象)装上过滤器,那么所有的事件在发往任何其他的过滤器时,都要先经过当前这个 eventFilter(). 在debug的时候,这个办法就非常有用, 也常常被用来处理失效了的widget的鼠标事件,通常这些事件会被QApplication::notify()丢掉. ( 在QApplication::notify() 中, 是先调用qApp的过滤器, 再对事件进行分析, 以决定是否合并或丢弃)

4.5 继承QApplication类,并重载notify()函数.
Qt 是用QApplication::notify()函数来分发事件的.想要在任何事件过滤器查看任何事件之前先得到这些事件,重载这个函数是唯一的办法. 通常来说事件过滤器更好用一些, 因为不需要去继承QApplication类. 而且可以给QApplication对象安装任意个数的事

 

 

分享到:
评论

相关推荐

    Qt事件系统

    Qt事件系统是Qt框架的核心部分,它使得GUI应用程序能够响应用户操作和其他系统级事件。本文将深入探讨Qt事件系统的工作原理、重要概念以及如何在实际编程中应用。 首先,我们来理解事件的基本概念。在Qt中,事件是...

    QT事件系统

    QT事件系统

    QT事件系统一:父子组件之间的事件传播机制

    QT事件系统是Qt库中的核心特性之一,它使得在图形用户界面编程中处理用户交互变得简单而高效。本文将深入探讨“QT事件系统一:父子组件之间的事件传播机制”,揭示Qt如何优雅地处理事件流。 首先,理解Qt事件系统的...

    Qt事件系统相关练习代码

    Qt 系统事件 1. Qt中的事件 1.1 事件的处理 1.2 事件的传递 1.2.1 程序练习 2. 鼠标和滚轮事件 2.1 程序练习代码 3. 键盘事件 3.1 程序练习代码 4. 定时器事件和随机数 4.1 程序练习代码 5. 事件过滤器与事件的发送 ...

    Qt事件系统实例.zip

    QT,C++使用技巧,实战应用开发小系统参考资料,源码参考。 详细介绍了一些Qt框架的各种功能和模块,以及如何使用Qt进行GUI开发、网络编程和跨平台应用开发等。 适用于初学者和有经验的开发者,能够帮助你快速上手Qt...

    Qt for android触摸手势事件QGestureEvent

    QGestureEvent是Qt事件系统的一部分,它包含了在触摸屏上发生的所有手势信息。当用户执行手势时,Qt会生成一个QGestureEvent,并将其发送给相应部件的`gestureEvent()`方法。这个事件包含了所有已识别的手势对象,...

    qt自定义事件

    总结一下,Qt自定义事件是Qt事件系统的一个强大特性,它允许我们扩展标准事件机制,以处理特定的应用场景。理解并熟练运用自定义事件,对于编写高效、灵活的Qt应用程序至关重要。在实际项目中,结合事件队列、事件...

    qt 系统钩子获取鼠标键盘事件

    在Qt框架中,系统钩子(System Hooks)是一种机制,允许开发者监听并处理系统的底层事件,如鼠标和键盘输入。这种技术对于开发需要全局监控或响应用户输入的应用程序尤其有用。下面将详细介绍如何使用Qt来实现系统...

    Qt事件机制浅析

    - 在处理系统消息的过程中可能会产生新的Qt事件,这些新事件会被加入到Qt事件队列中等待处理。 ##### 2. 同步与异步调度 - **异步调度**:通过事件循环处理事件,这种方式适用于大部分情况。 - **同步调度**:通过...

    QT事件学习

    QT 的事件循环是异步的,当调用 QApplication::exec() 时,就进入了事件循环,先处理 Qt 事件队列中的事件,再处理系统消息队列中的消息。 事件的派发和处理是通过事件过滤器实现的。事件过滤器可以让一个对象侦听...

    Qt事件处理之鼠标处理事件

    在【压缩包子文件的文件名称列表】"QtEventSystem"中,可能包含了一个简单的Qt事件处理系统示例代码。这个系统可能已经实现了鼠标事件的处理,为初学者提供了一个很好的起点。你可以通过查看和运行这个例子来更深入...

    第9章 Qt事件机制与原理

    9.1.1 什么是Qt事件驱动?  我们在写Qt工程类项目的时候都会发现,主程序里面都有这么一段代码: int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); return a.exec...

    QtEvent.7zQtEvent.7z

    本文将详细解析Qt事件系统的核心概念、流程及实际应用。 首先,我们要明白什么是Qt事件。在Qt中,事件是应用程序之间传递信息的一种方式,通常由系统或应用程序自身触发。事件包含了触发事件的原因和相关信息,例如...

    Qt核心机制、Qt元对象系统、Qt信号槽原理

    这就引出了 Qt 的元对象系统。元对象系统是 Qt 为了解决 C++ 静态类型的限制而设计的一种机制,它允许在运行时动态地添加和修改对象的属性,以及实现对象之间的通信。 1. **元对象系统**: - Qt 的元对象系统基于 ...

    qt-buttons.tar.gz_QT events

    QT事件(QT_events)是Qt库中的核心特性之一,它在GUI编程中起着至关重要的作用。Qt事件系统使得应用程序能够响应用户的各种交互,如点击按钮、...同时,了解并掌握Qt事件系统对于任何Qt应用程序的开发都是必不可少的。

    Qt5.10检测系统休眠

    首先,Qt提供了一个名为`QSystemTrayIcon`的类,它可以监听系统事件,包括系统进入和退出休眠状态。在Windows和Linux系统中,可以通过注册信号槽来监听`QSystemTrayIcon::activated`信号,当系统状态发生变化时,这...

    QT 自助点餐系统

    5. **事件处理**:QT事件驱动的编程模型使得用户在界面上的每一个操作,如点击按钮或滑动屏幕,都能被正确响应并执行相应的功能。 6. **多线程**:为了确保系统的流畅性,可能会使用多线程技术,如QThread,将耗时...

    QT编程----事件

    事件循环的处理流程是:先处理 QT 事件队列中的 posted 事件,直至为空,然后再处理系统消息队列中的 spontaneous 消息,直至为空。在处理系统消息的时候会产生新的 QT posted 事件,需要对其再次进行处理。 ...

    qt帮助文档和案例教程.zip

    "事件分发器以及事件过滤器解析图.png"揭示了QT事件系统的内部工作,包括事件如何从窗口系统传递到应用程序对象,以及事件过滤器如何拦截和处理事件。这对于理解复杂的用户交互和自定义行为至关重要。 最后,"Unity...

    Qt功能代码收藏 Qt功能代码收藏

    11. **事件处理**:Qt事件系统允许程序对键盘、鼠标和其他输入设备事件进行响应,通过重载`QObject::event`或使用`QEvent`子类来定制事件处理。 12. **定时器**:`QTimer`类提供定时触发事件的功能,常用于延时操作...

Global site tag (gtag.js) - Google Analytics