事件(认识事件,重写event(),事件过滤器,定时器,事件总结)

来源:互联网 发布:淘宝二手苹果ipad 编辑:程序博客网 时间:2024/06/01 08:10

一、认识事件

    1、事件(event) 是由系统或者 Qt 本身在不同的时刻发出的。当用户按下鼠标、敲下键盘,或者是窗口需要重新绘制的时候,都会发出一个相应的事件。一些事件在对用户操作做出响应时发出,如键盘事件等;另一些事件则是由系统自动发出,如计时器事件。

    2、事件也就是我们通常说的“事件驱动(event drive) ”程序设计的基础概念。事件的出现,使得程序代码不会按照原始的线性顺序执行想想看,从最初的 C 语言开始,我们的程序就是以一种线性的顺序执行代码:这一条语句执行之后,开始执行下一条语句;这一个函数执行过后,开始执行下一个函数。这种类似“批处理”的程序设计风格显然不适合于处理复杂的用户交互。我们来想象一下用户交互的情景:我们设计了一堆功能放在界面上,用户点击了“打开文件”,于是开始执行打开文件的操作;用户点击了“保存文件”,于是开始执行保存文件的操作。我们不知道用户究竟想进行什么操作,因此也就不能预测接下来将会调用哪一个函数。如果我们设计了一个“文件另存为”的操作,如果用户不点击,这个操作将永远不会被调用。这就是所谓的“事件驱动”,我们的程序的执行顺序不再是线性的,而是由一个个事件驱动着程序继续执行。没有事件,程序将阻塞在那里,不执行任何代码。
    3、在 Qt 中,事件的概念似乎同信号槽类似。的确如此,一般来说,使用 Qt 组件时,我们并不会把主要精力放在事件上。因为在 Qt 中,我们关心的更多的是事件关联的一个信号。比如,对于 QPushButton 的鼠标点击,我们不需要关心这个鼠标点击事件,而是关心它的clicked()信号的发出。这与其他的一些 GUI 框架不同:在 Swing 中,你所要关心的是JButton 的 ActionListener 这个点击事件。由此看出,相比于其他 GUI 框架, Qt 给了我们额外的选择:信号槽。

    4、但是, Qt 中的事件和信号槽却并不是可以相互替代的信号由具体的对象发出,然后会马上交给由 connect()函数连接的槽进行处理;而对于事件, Qt 使用一个事件队列对所有发出的事件进行维护,当新的事件产生时,会被追加到事件队列的尾部。前一个事件完成后,取出后面的事件进行处理。但是,必要的时候, Qt 的事件也可以不进入事件队列,而是直接处理。信号一旦发出,对应的槽函数一定会被执行。但是,事件则可以使用“事件过滤器”进行过滤,对于有些事件进行额外的处理,另外的事件则不关心。总的来说,如果我们使用组件,我们关心的是信号槽;如果我们自定义组件,我们关心的是事件。因为我们可以通过事件来改变组件的默认操作。比如,如果我们要自定义一个能够响应鼠标事件的 EventLabel,我们就需要重写 QLabel 的鼠标事件,做出我们希望的操作,有可能还得在恰当的时候发出一个类似按钮的 clicked()信号(如果我们希望让这个 EventLabel 能够被其它组件使用)或者其它的信号。

#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QMainWindow>#include <QMouseEvent>#include <QLabel>class MainWindow : public QMainWindow{    Q_OBJECTpublic:    MainWindow(QWidget *parent = 0);    ~MainWindow();};/* * EventLabel 继承了 QLabel,覆盖了 mousePressEvent()、 mouseMoveEvent() * 和MouseReleaseEvent() 三个函数。*/class EventLabel:public QLabel{protected:    void mouseMoveEvent(QMouseEvent *event);    void mousePressEvent(QMouseEvent *event);    void mouseReleaseEvent(QMouseEvent *event);};#endif // MAINWINDOW_H
#include "mainwindow.h"#include <QString>MainWindow::MainWindow(QWidget *parent)    : QMainWindow(parent){}MainWindow::~MainWindow(){}/* *QLabel支持HTML代码,<center>设置文本为水平居中,<h1>设置字体为黑体,</h1></center>是结束符。 * QString 的 arg() 函数可以自动替换掉 QString 中出现的占位符。其占位符以 % 开始,后面是占位 * 符的位置,例如 %1, %2 这种。*/void EventLabel::mouseMoveEvent(QMouseEvent *event){    this->setText(QString("<center><h1>Move:(%1,%2)</h1></center>").arg(QString::number(event->x()),QString::number(event->y())));}void EventLabel::mousePressEvent(QMouseEvent *event){    this->setText(QString("<center><h1>Press:(%1,%2)</h1></center>").arg(QString::number(event->x()),QString::number(event->y())));}void EventLabel::mouseReleaseEvent(QMouseEvent *event){    QString msg;    /*     * 使用另外一种 QString 的构造方法。类似C风格的格式化函数sprintf()来构造 QString。    */    msg.sprintf("<center><h1>Release:(%d,%d)</h1></center>",event->x(),event->y());    this->setText(msg);}
#include "mainwindow.h"#include <QApplication>int main(int argc, char *argv[]){    QApplication a(argc, argv);    EventLabel *la=new EventLabel;    la->setWindowTitle("鼠标事件");    la->resize(300,200);//设置窗口大小    /*     * QWidget 中有一个 mouseTracking 属性,该属性用于设置是否追踪鼠标。只有鼠标被追踪时,     * mouseMoveEvent() 才会发出。如果 mouseTracking 是 false(默认即是),组件在至少一次     * 鼠标点击之后, 才能够被追踪, 也就是能够发出 mouseMoveEvent() 事件。如果 mouseTracking     * 为 true,则 mouseMoveEvent() 直接可以被发出。    */    la->setMouseTracking(true);    la->show();    return a.exec();}


二、重写event()
     event() 函数主要用于事件的分发。所以,如果你希望在事件分发之前做一些操作,就可以重写这个 event() 函数了。我们可以通过使用QEvent::type()函数可以检查事件的实际类型,其返回值是 QEvent::Type类型的枚举。我们处理过自己感兴趣的事件之后,可以直接返回 true,表示我们已经对此事件进行了处理;对于其它我们不关心的事件,则需要调用父类的 event() 函数继续转发,否则这个组件就只能处理我们定义的事件了。

#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QMainWindow>#include <QWidget>#include <QEvent>class MainWindow : public QMainWindow{    Q_OBJECTpublic:    MainWindow(QWidget *parent = 0);    ~MainWindow();};class CustomWidget:public QWidget{protected:    bool event(QEvent *e);};#endif // MAINWINDOW_H
#include "mainwindow.h"#include <QKeyEvent>#include <QDebug>#include <Qt>MainWindow::MainWindow(QWidget *parent)    : QMainWindow(parent){}MainWindow::~MainWindow(){}/* * CustomWidget 是一个普通的 QWidget 子类。我们重写了它的 event()函数,这个函数有 * 一个 QEvent 对象作为参数,也就是需要转发的事件对象。函数返回值是 bool 类型。如果 * 传入的事件已被识别并且处理,则需要返回 true,否则返回 false。如果返回值是 true,并 * 且,该事件对象设置了 accept(),那么 Qt 会认为这个事件已经处理完毕,不会再将这个事 * 件发送给其它对象,而是会继续处理事件队列中的下一事件。注意,在 event()函数中,调 * 用事件对象的 accept()和 ignore()函数是没有作用的,不会影响到事件的传播。*/bool CustomWidget::event(QEvent *e){    if(e->type()==QEvent::KeyPress){        QKeyEvent *keyEvent=static_cast<QKeyEvent *>(e);        if(keyEvent->key()==Qt::Key_Tab){            qDebug()<<"tab键被按下";            return true;        }    }    return QWidget::event(e);}
三、事件过滤器

    有时候,对象需要查看、甚至要拦截发送到另外对象的事件。例如,对话框可能想要拦截按键事件,不让别的组件接收到;或者要修改回车键的默认处理。通过前面的章节,我们已经知道, Qt 创建了 QEvent 事件对象之后,会调用 QObject 的event()函数处理事件的分发。显然,我们可以在event()函数中实现拦截的操作。由于event()函数是protected 的,因此,需要继承已有类。如果组件很多,就需要重写很多个 event()函数。这当然相当麻烦,更不用说重写 event()函数还得小心一堆问题。好在 Qt 提供了另外一种机制来达到这一目的:事件过滤器

    QObject 有一个 eventFilter()函数,用于建立事件过滤器。这个函数的签名如下:
    virtual bool QObject::eventFilter ( QObject * watched, QEvent * event );
    这个函数正如其名字显示的那样,是一个“事件过滤器”。所谓事件过滤器,可以理解成一种过滤代码。想想做化学实验时用到的过滤器,可以将杂质留到滤纸上,让过滤后的液体溜走。事件过滤器也是如此:它会检查接收到的事件。如果这个事件是我们感兴趣的类型,就进行我们自己的处理;如果不是,就继续转发。这个函数返回一个 bool 类型,如果你想将参数event 过滤出来,比如,不想让它继续转发,就返回 true,否则返回 false。事件过滤器的调用时间是目标对象(也就是参数里面的 watched 对象)接收到事件对象之前。也就是说,如果你在事件过滤器中停止了某个事件,那么, watched 对象以及以后所有的事件过滤器根本不会知道这么一个事件。

#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QMainWindow>#include <QTextEdit>#include <QObject>#include <QEvent>class MainWindow : public QMainWindow{    Q_OBJECTprivate:    QTextEdit *textedit;protected:   bool eventFilter(QObject *obj, QEvent *event);public:    MainWindow();    ~MainWindow();};#endif // MAINWINDOW_H
#include "mainwindow.h"#include <QEvent>#include <QDebug>#include <QKeyEvent>MainWindow::MainWindow(){    textedit=new QTextEdit;    setCentralWidget(textedit);    /*     * eventFilter() 函数相当于创建了过滤器,然后我们需要安装这个过滤器。安装过滤器需要调用     * QObject::installEventFilter() 函数。我们可以向一个对象上面安装多个事件处理器,只要调     * 用多次 installEventFilter() 函数。如果一个对象存在多个事件过滤器,那么,最后一个安装     * 的会第一个执行,也就是后进先执行的顺序。    */    textedit->installEventFilter(this);}/* * MainWindow 是我们定义的一个类。我们重写了它的 eventFilter() 函数。为了过滤特定组件上的事件, * 首先需要判断这个对象是不是我们感兴趣的组件,然后判断这个事件的类型。在上面的代码中,我们不想 * 让 textEdit 组件处理键盘按下的事件。所以,首先我们找到这个组件,如果这个事件是键盘事件,则直 * 接返回 true,也就是过滤掉了这个事件,其他事件还是要继续处理,所以返回 false。对于其它的组件, * 我们并不保证是不是还有过滤器,于是最保险的办法是调用父类的函数。*/bool MainWindow::eventFilter(QObject *obj, QEvent *event){    if(obj==textedit){        if(event->type()==QEvent::KeyPress){            QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);            qDebug() << "Ate key press" << keyEvent->key();            return true;        }        else{            return false;        }    }    else{        return QMainWindow::eventFilter(obj,event);    }}/* * 注意,如果你在事件过滤器中 delete 了某个接收组件,务必将函数返回值设为 true。否则, * Qt 还是会将事件分发给这个接收组件,从而导致程序崩溃。事件过滤器和被安装过滤器的组件 * 必须在同一线程,否则,过滤器将不起作用。另外,如果在安装过滤器之后,这两个组件到了 * 不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。*/MainWindow::~MainWindow(){}
四、定时器

    Qt中有两种方法来使用定时器,一种是定时器事件,另一种是使用信号和槽。一般使用了多个定时器时最好使用定时器事件来处理。

#ifndef WIDGET_H#define WIDGET_H#include <QWidget>#include <QTimerEvent>#include <QLabel>#include <QLineEdit>#include <QTimer>#include <QDateTime>class Widget : public QWidget{    Q_OBJECTprivate slots:    void timerUpdate();private:    int id1,id2,id3;    QLabel *label1;    QLabel *label2;    QLineEdit *lineEdit;protected:    void timerEvent(QTimerEvent *event);public:    Widget(QWidget *parent = 0);    ~Widget();};#endif // WIDGET_H
#include "widget.h"#include <QString>Widget::Widget(QWidget *parent)    : QWidget(parent){    resize(600,600);    setWindowTitle(tr("定时器和随机数"));    label1=new QLabel(this);    label1->move(100,100);    label2=new QLabel(this);    label2->move(200,100);    label2->setFixedSize(40,30);    /*     * 这里开启了三个定时器,分别返回了它们的id,这个id用来区分不同的定时器。     * 定时器的时间单位是毫秒。每当一个定时器溢出时,都会调用定时器事件处理函数,     * 我们可以在该函数中进行相应的处理。    */    id1=startTimer(1000);    id2=startTimer(2000);    id3=startTimer(10000);    /*     * 如果只是想开启少量的定时器,也可以使用信号和槽来实现。     * 这里创建了一个定时器,并将其溢出信号和更新槽关联起来,最后使用start()函数来开启定时器。    */    QTimer *timer=new QTimer(this);    connect(timer,&QTimer::timeout,this,&Widget::timerUpdate);    timer->start(1000);    lineEdit=new QLineEdit(this);    lineEdit->move(100,300);    lineEdit->setFixedSize(200,50);    /*     *关于随机数,在Qt中是使用qrand()和qsrand()两个函数实现的。在前面的程序中已经看到了qrand()     * 函数的使用,其可以产生随机数,qrand()%10可以产生0-9之间的随机数。要想产生100以内的随机数     * 就是%100,以此类推。在使用qrand()函数产生随机数之前,一般要使用qsrand()函数为其设置初值,     * 如果不设置初值,那么每次运行程序,qrand()都会产生相同的一组随机数。为了每次运行程序时,     * 都可以产生不同的随机数,我们要使用qsrand()设置一个不同的初值。这里使用了QTime类的secsTo()     * 函数,它表示两个时间点之间所包含的秒数,比如代码中就是指从零点整到当前时间所经过的秒数。    */    qsrand(QTime(0,0,0).secsTo(QTime::currentTime()));}Widget::~Widget(){}/* * 这里先使用timerId()函数返回了溢出的定时器的id,然后根据该id来判断是哪个定时器溢出了, * 并进行相应的处理。每当第一个定时器溢出时都产生一个小于10的随机数;当第二个定时器溢出时, * 就更改标签的文本。*/void Widget::timerEvent(QTimerEvent *event){    if(event->timerId()==id1){        label1->setText(tr("%1").arg(qrand()%10));    }    else if(event->timerId()==id2){        label2->setText(tr("呵呵"));    }    else{        return QWidget::timerEvent(event);    }}void Widget::timerUpdate(){    QDateTime time=QDateTime::currentDateTime();//获取系统现在的时间    QString str=time.toString("yyyy-MM-dd hh:mm:ss ddd");//设置系统时间显示格式    lineEdit->setText(str);//在标签上显示时间    int rand=qrand()%300;//坐标随机数    int rand2=qrand()%200;    lineEdit->move(rand,rand2);}

五、事件总结
    Qt 中有很多种事件:鼠标事件、键盘事件、大小改变的事件、位置移动的事件等等。如何处理这些事件,实际有两种选择:
    1. 所有事件对应一个事件处理函数,在这个事件处理函数中用一个很大的分支语句进行选择。
    2. 每一种事件对应一个事件处理函数
    Qt 具有这么多种事件处理函数,肯定有一个地方对其进行分发,否则, Qt 怎么知道哪一种事件调用哪一个事件处理函数呢?这个分发的函数,就是 event()。显然,当 QMouseEvent产生之后, event()函数将其分发mouseEvent()事件处理器进行处理。

    event()函数会有两个问题:
    1. event()函数是一个 protected 的函数,这意味着我们要想重写 event(),必须继承一个已有的类。试想,我的程序根本不想要鼠标事件,程序中所有组件都不允许处理鼠标事件,是不是我得继承所有组件,一一重写其 event()函数? protected 函数带来的另外一个问题是,如果我基于第三方库进行开发,而对方没有提供源代码,只有一个链接库,其它都是封装好的。我怎么去继承这种库中的组件呢?
    2. event()函数的确有一定的控制,不过有时候我的需求更严格一些:我希望那些组件根本看不到这种事件。event()函数虽然可以拦截,但其实也是接收到了 QMouseEvent对象。我连让它收都收不到。这样做的好处是,模拟一种系统根本没有那个事件的效果,所以其它组件根本不会收到这个事件,也就无需修改自己的事件处理函数。这种
需求怎么办呢?这两个问题是 event()函数无法处理的。于是, Qt 提供了另外一种解决方案:事件过滤器。
    事件过滤器给我们一种能力,让我们能够完全移除某种事件。事件过滤器可以安装到任意QObject 类型上面,并且可以安装多个。如果要实现全局的事件过滤器,则可以安装到QApplication 或者 QCoreApplication 上面。这里需要注意的是,如果使用installEventFilter()函数给一个对象安装事件过滤器,那么该事件过滤器只对该对象有效,
只有这个对象的事件需要先传递给事件过滤器的 eventFilter()函数进行过滤,其它对象不受影响。如果QApplication 对象安装事件过滤器,那么该过滤器对程序中的每一个对象都有效,任何对象的事件都是先传给 eventFilter()函数。
事件过滤器可以解决刚刚我们提出的 event()函数的两点不足:首先,事件过滤器不是protected 的,因此我们可以向任何 QObject 子类安装事件过滤器;其次,事件过滤器在目标对象接收到事件之前进行处理,如果我们将事件过滤掉,目标对象根本不会见到这个事件。

    Qt 的事件处理,实际上是有五个层次:
    1. 重写 paintEvent()、 mousePressEvent() 等事件处理函数。这是最普通、最简单的形式,同时功能也最简单。
    2. 重写 event() 函数。 event() 函数是所有对象的事件入口, QObject 和 QWidget中的实现,默认是把事件传递给特定的事件处理函数。
    3. 在特定对象上面安装事件过滤器。该过滤器仅过滤该对象接收到的事件。
    4. 在 QCoreApplication::instance() 上面安装事件过滤器。该过滤器将过滤所有对象的所有事件,因此和 notify() 函数一样强大,但是它更灵活,因为可以安装多个过滤器。全局的事件过滤器可以看到 disabled 组件上面发出的鼠标事件。全局过滤器有一个问题:只能用在主线程。
    5. 重写 QCoreApplication::notify() 函数。这是最强大的,和全局事件过滤器一样提供完全控制,并且不受线程的限制。但是全局范围内只能有一个被使用(因为QCoreApplication 是单例的)。

#ifndef MAINWINDOW_H#define MAINWINDOW_H#include <QWidget>#include <QMouseEvent>#include <QEvent>#include <QObject>/* * 事件过滤器可以解决 event() 函数的两点不足:首先,事件过滤器不是 * protected 的,因此我们可以向任何 QObject 子类安装事件过滤器;其次,事件过滤器在目标 * 对象接收到事件之前进行处理,如果我们将时间过滤掉,目标对象根本不会见到这个事件。*/class Label:public QWidget{public:    Label(){        installEventFilter(this);    }    bool eventFilter(QObject *watched, QEvent *event);    ~Label(){}protected:    void mousePressEvent(QMouseEvent *);    bool event(QEvent *e);};class EventFilter:public QObject{private:    QObject *m_watched;public:    EventFilter(QObject *watched,QObject *parent=0):QObject(parent),m_watched(watched){}    bool eventFilter(QObject *watched, QEvent *event);    ~EventFilter(){}};#endif // MAINWINDOW_H
#include "mainwindow.h"#include <QDebug>/* * 第二是全局对象上面的事件过滤器*/bool Label::eventFilter(QObject *watched, QEvent *event){    if(watched==this){        if(event->type()==QEvent::MouseButtonPress){            qDebug()<<"eventFilter";        }    }    return false;}/* * 最后才是特定的事件处理函数*/void Label::mousePressEvent(QMouseEvent *){    qDebug()<<"mousePressEvent";}/* * 第三是重写的event函数*/bool Label::event(QEvent *e){    if(e->type()==QEvent::MouseButtonPress){        qDebug()<<"event";    }    return QWidget::event(e);}/* * 全局事件过滤器最先被调用*/bool EventFilter::eventFilter(QObject *watched, QEvent *event){    if(watched==m_watched){        if(event->type()==QEvent::MouseButtonPress){            qDebug()<<"Application::eventFilter";        }    }    return false;}
#include "mainwindow.h"#include <QApplication>int main(int argc, char *argv[]){    QApplication a(argc, argv);    Label label;    a.installEventFilter(new EventFilter(&label,&label));//安装过滤器    label.show();    return a.exec();}

0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 联通充错了怎么办 话费存多了怎么办 微信充错话费了怎么办 联通充错话费怎么办 qq充话费充值充到空号上怎么办 微信电费没到账怎么办 余额宝等待付款怎么办 微信交固话费交错了怎么办 银行卡给校园卡充值没到账怎么办 充话费没收到怎么办 手机充不了话费怎么办 数据流量网不好怎么办 手机卡变成空号怎么办 手机已暂停服务怎么办 网上话费交错了怎么办 手机话费充错怎么办 打开微信总跳出广告怎么办 qq充错话费怎么办 微信反应速度慢怎么办 淘宝qb充错号了怎么办 快手充错了怎么办 每日优鲜怎么办会员 每日优鲜怎么办会员卡 微信收付款没到怎么办 交宽带费还连不上网怎么办 失业金忘记签到怎么办 失业金未到账怎么办 atm转账未到账怎么办 失业金这个月没到账怎么办 失业金停发了怎么办? 跨行转账没到账怎么办 医疗报销不到账怎么办 转账正在处理中怎么办 被花椒刺了怎么办 小米8来电黑屏怎么办 发语音聊天黑屏怎么办 店倒闭了会员卡怎么办 淘宝退货不理人怎么办 平安银行车主信用卡怎么办 支付宝信用差怎么办 建行储蓄卡限额怎么办