qt-重写event(),事件过滤器,定时器,事件总结

一、认识事件

    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_OBJECT
 
public:
    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_OBJECT
 
public:
    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_OBJECT
private:
    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_OBJECT
private 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
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Qt 中,QApplication 类是整个应用程序的核心类,它管理着应用程序的主事件循环,同时负责分发和处理各种事件。其中,QApplication 的 notify 函数是用于处理应用程序接收到事件后的通知的函数。 当应用程序接收到一个事件时,Qt 会自动调用 QApplication 的 notify 函数。该函数会传递一个 QObject 对象和一个 QEvent 对象,分别表示事件的接收者和事件本身。在 notify 函数中,可以根据事件类型和接收者对象进行处理,并进行一些额外的操作,比如更新应用程序的状态或者发送信号。 除了重写 QApplication 的 notify 函数,Qt 还提供了事件过滤器事件监听机制,可以方便地对应用程序中的事件进行处理。 事件过滤器是通过 QObject::installEventFilter() 函数设置的。当事件到达对象时,会先调用事件过滤器,并将事件对象传递给过滤器进行处理。如果过滤器返回 true,则表示事件已经被过滤,不会再传递给接收者;如果过滤器返回 false,则事件将继续传递给接收者。事件过滤器一般用于在不改变接收者的情况下,对事件进行一些预处理或者后处理。 事件监听机制是通过重写 QObject 的事件处理函数来实现的。当一个对象接收到一个事件时,Qt 会自动调用该对象的事件处理函数。如果需要处理其他对象的事件,则可以使用事件过滤器。在事件处理函数中,可以根据事件类型进行处理,并进行一些额外的操作,比如更新对象的状态或者发送信号。 需要注意的是,在使用事件过滤器事件监听机制时,一定要注意不要阻止事件传递,否则可能会导致应用程序无法正常工作。同时,如果同时使用事件过滤器事件监听机制,一定要注意它们之间的优先级关系,以避免出现不必要的问题。 总的来说,Qt事件处理机制非常灵活,可以通过多种方式对应用程序中的事件进行处理。在实际开发中,需要根据具体的需求选择合适的方式来处理事件

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值