`
美丽的小岛
  • 浏览: 296984 次
  • 性别: 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事件系统

    主要是Qt时间系统的注意事项,附有代码注释,讲解。

    Qt事件系统相关练习代码

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

    Qt事件系统实例.zip

    软件开发设计:PHP、QT、应用软件开发、系统软件开发、移动应用开发、网站开发C++、Java、python、web、C#等语言的项目开发与学习资料 硬件与设备:单片机、EDA、proteus、RTOS、包括计算机硬件、服务器、网络设备、...

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

    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...

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

    这是一个Qt事件处理系统,目前只实现了鼠标事件的处理,后续将添加键盘事件,绘图事件,定时事件等等的处理过程。程序比较简单,适合初学者。程序开发环境:win7+qt5.7.0

    Qt后台监听键盘按键事件

    该实例程序使用Qt进行,windows的键盘后台监听,即使Qt桌面程序失去焦点在后台运行,也会捕捉到按键事件 这个程序使用windows的钩子(hook)实现的,Qt官方没有相关的实现 关于编译,我用的是Qt5.9.7 MinGW32位,我...

    QtDeskTop:一个让你体验 Windows 10 风格的 Qt 桌面系统

    一个使用 Qt 开发的类似于 Windows 10 的桌面系统。它具有以下特点和功能: 支持 UKEY 实名认证登录:用户可以通过插入 UKEY 来进行身份验证,提高安全性和便捷性。 软件中心和软件管理:用户可以通过软件中心来...

    Qt Windows 休眠唤醒信号

    Qt Windows 休眠唤醒信号

    QT的事件处理机制

    事件是窗口系统或者qt对不同情况的响应,绝大多数被产生的事件都是对用户行为的响应,但是也有一些,比如定时器事件,它们是被系统独立产生的。QWidget::event()虚函数是各种事件的一个大管家,负责把大多数常用类型...

    Qt事件分发的Demo

    在Qt中,事件分发是指当一个事件发生时,Qt系统决定应该把这个事件传递给哪个QWidget的过程。Qt中的事件分发主要通过`event(QEvent *event)`函数来实现。QWidget的所有子类都继承了`event()`函数,可以在该函数中...

    QT中文参考手册(QT help)

    Window系统特性注释 如何购买Qt 安装 如何学习Qt 教程一, 教程二 实例 循序渐进实例 白皮书 Qt 3.0的关键特征 修改历史 从Qt 2.x移植到Qt 3.x 简体中文汉化日志 Qt季刊 API参考 模块 概述 所有的...

    QT linux下获取键盘和鼠标事件

    QT linux下获取键盘和鼠标事件,可以运行。

    QT经典教程_详细一步一步例子

    九、Qt Creator中鼠标键盘事件的处理实现自定义鼠标指针 十、Qt Creator中实现定时器和产生随机数 十一、Qt 2D绘图(一)绘制简单图形 十二、Qt 2D绘图(二)渐变填充 十三、Qt 2D绘图(三)绘制文字 十四、Qt 2D...

    QT Linux系统捕获键盘事件,抓取键盘,无论焦点是否在此界面或进程上

    Linux系统捕获键盘事件,抓取键盘事件,无论焦点是否在此界面或进程上。类似,windows下hook的作用,可以捕获事件。可以区分按下,抬起按键,长按等。再加上定时器,可以判断组合键,不是简单的shift+A, 可以实现A+B...

    QT 知识点资料汇总(每个月更新一次)

    2\CHM已分类,包含:QT基础\QT容器类\QT事件系统\QT文件目录和输入输出\WINAPI等内容. 3\每个月会定期更新补充新得内容. 4\学习Qt碰到的问题,基本上在这里都可以找到答案,且是有效的(网络上好多QT4不能用).

    Qt5.10+GUI完全参考手册,qt5.15.2,QT

    8括,元对象系统,信号和槽, Qt 事件, Qt 主窗口,布局管理及焦点系统,对话框,模型/视图框架,拖放和剪贴板, Qt 文本系统, Qt 界面外观, Qt 2D 绘图和Qt 的输入输出。

Global site tag (gtag.js) - Google Analytics