• Qt事件机制

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

    Qt事件的发生和处理成为程序运行的主线,存在于程序整个生命周期。

    Qt事件的类型很多, 常见的qt的事件如下:

    键盘事件: 按键按下和松开.

    鼠标事件: 鼠标移动,鼠标按键的按下和松开.

    拖放事件: 用鼠标进行拖放.

    滚轮事件: 鼠标滚轮滚动.

    绘屏事件: 重绘屏幕的某些部分.

    定时事件: 定时器到时.

    焦点事件: 键盘焦点移动.

    进入和离开事件: 鼠标移入widget之内,或是移出.

    移动事件: widget的位置改变.

    大小改变事件: widget的大小改变.

    显示和隐藏事件: widget显示和隐藏.

    窗口事件: 窗口是否为当前窗口.

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

    交流会围绕以下三个问题展开:

    一、什么是事件?

    二、事件是怎样被处理的?

    三、事件与信号的区别?

    一、什么是事件?

    事件:某个“动作”的完成后,需让某个对象知道而发送的消息。(个人观点)

    解释:此时的“动作”并非通常意义所指的动作,而是广义的“动作”,是主动和被动的总和。

    例:两个窗体A和B,当A为最小化状态时,我们使它最大化,这就会让A主动产生一个重绘事件;当A和B非最小化状态,且B位于A窗体之上时,我们让B最小化,那么刚才被B遮挡的A窗体就会被动地产生一个重绘事件。

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

    我们按产生来源把事件分为两类:

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

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

    二、事件是怎样被处理的?

    (一)两种调度方式,一种是同步的, 一种是异步.

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

    while ( !app_exit_loop )

    {

    while( !postedEvents ) { processPostedEvents() }

    while( !qwsEvnts ){ qwsProcessEvents();   }

    while( !postedEvents ) { processPostedEvents() }

    }

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

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

    (二) 事件的派发和处理

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

    事件过滤器的代码实现:
    这个代码实现了点击图片进行放大的功能(属于事件过滤器的操作实例)

    #ifndef EVENTFILTER_H
    #define EVENTFILTER_H #include <QDialog>
    #include <QLabel>
    #include <QImage>
    #include <QEvent> class EventFilter : public QDialog
    {
    Q_OBJECT public:
    EventFilter(QWidget *parent = ,Qt::WindowFlags f=);
    ~EventFilter();
    public slots:
    bool eventFilter(QObject *, QEvent *);
    private:
    QLabel *label1;
    QLabel *label2;
    QLabel *label3; QLabel *stateLabel; QImage Image1;
    QImage Image2;
    QImage Image3;
    }; #endif // EVENTFILTER_H
    #include "eventfilter.h"
    #include <QHBoxLayout>
    #include <QVBoxLayout>
    #include <QMouseEvent>
    #include <QMatrix>
    #pragma execution_character_set("utf-8") EventFilter::EventFilter(QWidget *parent,Qt::WindowFlags f)
    : QDialog(parent,f)
    {
    setWindowTitle(tr("事件过滤")); label1 = new QLabel;
    Image1.load("../image/1.png");
    label1->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
    label1->setPixmap(QPixmap::fromImage(Image1)); label2 = new QLabel;
    Image2.load("../image/2.png");
    label2->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
    label2->setPixmap(QPixmap::fromImage(Image2)); label3 = new QLabel;
    Image3.load("../image/3.png");
    label3->setAlignment(Qt::AlignHCenter|Qt::AlignVCenter);
    label3->setPixmap(QPixmap::fromImage(Image3)); stateLabel = new QLabel(tr("鼠标按下标志"));
    stateLabel->setAlignment(Qt::AlignHCenter);
    QHBoxLayout *layout=new QHBoxLayout;
    layout->addWidget(label1);
    layout->addWidget(label2);
    layout->addWidget(label3); QVBoxLayout *mainLayout = new QVBoxLayout(this);
    mainLayout->addLayout(layout);
    mainLayout->addWidget(stateLabel); //三个标签安装事件监听器, 指定整个窗体为监视的对象
    label1->installEventFilter(this);
    label2->installEventFilter(this);
    label3->installEventFilter(this);
    } EventFilter::~EventFilter()
    { } bool EventFilter::eventFilter(QObject *watched, QEvent *event)
    {
    if(watched==label1)
    {
    //判断时间类型:鼠标按压事件(左中右)
    if(event->type()==QEvent::MouseButtonPress)
    {
    QMouseEvent *mouseEvent=(QMouseEvent *)event;
    if(mouseEvent->buttons()&Qt::LeftButton)
    {
    stateLabel->setText(tr("左键按下左边图片"));
    }
    else if(mouseEvent->buttons()&Qt::MidButton)
    {
    stateLabel->setText(tr("中键按下左边图片"));
    }
    else if(mouseEvent->buttons()&Qt::RightButton)
    {
    stateLabel->setText(tr("右键按下左边图片"));
    } //放大选中图片
    QMatrix matrix;
    matrix.scale(1.8,1.8);
    //重新载入缩放图片
    QImage tmpImg=Image1.transformed(matrix);
    label1->setPixmap(QPixmap::fromImage(tmpImg));
    }
    //释放就还原
    if(event->type()==QEvent::MouseButtonRelease)
    {
    stateLabel->setText(tr("鼠标释放左边图片"));
    label1->setPixmap(QPixmap::fromImage(Image1));
    }
    }
    else if(watched==label2)
    {
    if(event->type()==QEvent::MouseButtonPress)
    {
    QMouseEvent *mouseEvent=(QMouseEvent *)event;
    if(mouseEvent->buttons()&Qt::LeftButton)
    {
    stateLabel->setText(tr("左键按下中间图片"));
    }
    else if(mouseEvent->buttons()&Qt::MidButton)
    {
    stateLabel->setText(tr("中键按下中间图片"));
    }
    else if(mouseEvent->buttons()&Qt::RightButton)
    {
    stateLabel->setText(tr("右键按下中间图片"));
    } QMatrix matrix;
    matrix.scale(1.8,1.8);
    QImage tmpImg=Image2.transformed(matrix);
    label2->setPixmap(QPixmap::fromImage(tmpImg));
    }
    if(event->type()==QEvent::MouseButtonRelease)
    {
    stateLabel->setText(tr("鼠标释放中间图片"));
    label2->setPixmap(QPixmap::fromImage(Image2));
    }
    }
    else if(watched==label3)
    {
    if(event->type()==QEvent::MouseButtonPress)
    {
    QMouseEvent *mouseEvent=(QMouseEvent *)event;
    if(mouseEvent->buttons()&Qt::LeftButton)
    {
    stateLabel->setText(tr("左键按下右边图片"));
    }
    else if(mouseEvent->buttons()&Qt::MidButton)
    {
    stateLabel->setText(tr("中键按下右边图片"));
    }
    else if(mouseEvent->buttons()&Qt::RightButton)
    {
    stateLabel->setText(tr("右键按下右边图片"));
    } QMatrix matrix;
    matrix.scale(1.8,1.8);
    QImage tmpImg=Image3.transformed(matrix);
    label3->setPixmap(QPixmap::fromImage(tmpImg));
    }
    if(event->type()==QEvent::MouseButtonRelease)
    {
    stateLabel->setText(tr("鼠标释放右边图片"));
    label3->setPixmap(QPixmap::fromImage(Image3));
    }
    }
    return QDialog::eventFilter(watched,event);
    }

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

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

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

    (三) 事件的转发

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

    (四)实际运用

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

    (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);

    }

    }

    (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) 在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;

    }

    (4) 给QAppliction对象安装事件过滤器.

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

    (5) 继承QApplication类,并重载notify()函数.

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

    三、事件与信号的区别?

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

    信号通过事件实现,事件可以过滤,事件更底层,事件是基础,信号是扩展。

QT学习之事件处理的更多相关文章

  1. QT学习之第一个程序

    QT学习之第一个程序 目录 手动创建主窗口 居中显示 添加窗口图标 显示提示文本 Message Box的应用 手动连接信号与槽 手动创建主窗口 窗口类型 QMainWindow: 可以包含菜单栏.工 ...

  2. Qt学习之信号与槽(一)

    Qt学习之信号与槽(一) 目录 QT的信号与槽机制 在窗口的UI设计中操作添加信号和槽     QT的信号与槽机制   QT的两种机制 在Qt和PyQt中有两种通信机制: 低级事件处理机制(low-l ...

  3. Qt 学习之路 2(74):线程和 QObject

    Home / Qt 学习之路 2 / Qt 学习之路 2(74):线程和 QObject Qt 学习之路 2(74):线程和 QObject  豆子  2013年12月3日  Qt 学习之路 2  2 ...

  4. Qt 学习之路 2(72):线程和事件循环

    Qt 学习之路 2(72):线程和事件循环 <理解不清晰,不透彻>  --  有需求的话还需要进行专题学习  豆子  2013年11月24日  Qt 学习之路 2  34条评论 前面一章我 ...

  5. Qt 学习之路 2(52):使用拖放

    Qt 学习之路 2(52):使用拖放 豆子 2013年5月21日 Qt 学习之路 2 17条评论 拖放(Drag and Drop),通常会简称为 DnD,是现代软件开发中必不可少的一项技术.它提供了 ...

  6. Qt 学习之路 2(23):自定义事件

    Qt 学习之路 2(23):自定义事件  豆子  2012年10月23日  Qt 学习之路 2  21条评论 尽管 Qt 已经提供了很多事件,但对于更加千变万化的需求来说,有限的事件都是不够的.例如, ...

  7. Qt 学习之路 2(22):事件总结

    Qt 学习之路 2(22):事件总结 豆子 2012年10月16日 Qt 学习之路 2 47条评论 Qt 的事件是整个 Qt 框架的核心机制之一,也比较复杂.说它复杂,更多是因为它涉及到的函数众多,而 ...

  8. Qt 学习之路 2(20):event()

    Qt 学习之路 2(20):event() 豆子 2012年10月10日 Qt 学习之路 2 43条评论 前面的章节中我们曾经提到event()函数.事件对象创建完毕后,Qt 将这个事件对象传递给QO ...

  9. Qt 学习之路 2(19):事件的接受与忽略

    Home / Qt 学习之路 2 / Qt 学习之路 2(19):事件的接受与忽略 Qt 学习之路 2(19):事件的接受与忽略  豆子  2012年9月29日  Qt 学习之路 2  140条评论 ...

随机推荐

  1. 第六章 通过Service访问Pod(中)

    6.2 Cluster IP 底层实现 Cluster IP 是一个虚拟IP,是由K8s节点上的iptables规则管理的. 使用类似轮询的方法访问Pod. 6.3 DNS 访问Service 在Cl ...

  2. 非对称加密与OpenSSL

    随着个人隐私越来越受重视, HTTPS也渐渐的流行起来, 甚至有许多网站都做到了全站HTTPS, 然而这种加密和信任机制也不断遭遇挑战,比如戴尔根证书携带私钥,Xboxlive证书私钥泻露, 还有前一 ...

  3. Mybatis内部原理与插件原理

    Mybatis的运行分为两大问题,第一部分是读取配置文件保存在Configuration对象中,用以创建SqlSessionFactory,第二部分是SqlSession的执行过程.相对而言SqlSe ...

  4. form中的input的redonly和disable区别

    Readonly和Disabled是用在表单中的两个属性,它们都能够做到使用户不能够更改表单域中的内容.但是它们之间有着微小的差别,总结如下: Readonly只针对input(text / pass ...

  5. thymeleaf switch在表格中的使用,遇到的空行问题

    switch在表格中的使用时 如果把<td>写在<div th:switch="${data.isShow}"> 里面导致外面出现很多空的<div&g ...

  6. 【转】C#父类与子类的静态成员变量、实例成员变量、构造函数的执行顺序

    原文地址:http://www.xuebuyuan.com/1092603.html Win7+VS2010测试的结果如下: ①子类静态成员变量②子类静态构造函数③子类实例成员变量④父类静态成员变量⑤ ...

  7. Linux运维跳槽必备的40道面试精华题

    过一次年,结婚.存款.父母养老,一系列向钱看的事都在碾压我们本来还挺简单的神经,但难过没有出路,唯有找到好的方法和事业方向,才能实现一步一个脚印的逆袭. 下面是一名资深Linux运维求职数十家公司总结 ...

  8. Balls(poj 3783)

    The classic Two Glass Balls brain-teaser is often posed as: “Given two identical glass spheres, you ...

  9. 字符编码 ASCII,Unicode和UTF-8的关系

    转自:http://www.liaoxuefeng.com/wiki/0014316089557264a6b348958f449949df42a6d3a2e542c000/00143166410626 ...

  10. 创建excel,合并单元格,设置单元格样式

    package com.huawei.excel; import java.io.File;import java.io.FileOutputStream;import java.util.Date; ...