Qt事件处理汇总

一、事件简介

QT将系统产生的消息转化为QT事件,QT事件被封装为对象,所有的QT事件均继承抽象类QEvent,用于描述程序内部或外部发生的动作,任意的QObject对象都具备处理QT事件的能力。
在这里插入图片描述

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

事件名称描述
键盘事件按键按下和松开
鼠标事件鼠标移动,鼠标按键的按下和松开
拖放事件用鼠标进行拖放
滚轮事件鼠标滚轮滚动
绘屏事件重绘屏幕的某些部分
定时事件定时器到时
焦点事件键盘焦点移动
进入和离开事件鼠标移入widget之内,或是移出
移动事件widget的位置改变
大小改变事件widget的大小改变
显示和隐藏事件widget显示和隐藏
窗口事件窗口是否为当前窗口

通常,如果使用组件,关心的是信号槽;如果自定义组件,关心的是事件。因为可以通过事件来改变组件的默认操作。比如,如果要自定义一个能够响应鼠标事件的EventLabel,就需要重写QLabel的鼠标事件,做出期望的操作,有可能还得在恰当的时候发出一个类似按钮的clicked()信号(如果期望让EventLabel能够被其它组件使用)或者其它的信号。

二、事件处理

2.1 自定义类的事件处理

处理顺序:

  1. QT事件产生后会被立即发送到相应的QWidget对象
  2. 相应的QWidget中的event(QEvent *)进行事件处理
  3. event(QEvent *)根据事件类型调用不同的事件处理函数
  4. 在事件处理函数中发送QT预定义的信号
  5. 调用信号关联的槽函数

示例代码:
CustomButton.h

#ifndef CUSTOMBUTTON_H
#define CUSTOMBUTTON_H
 
#include <QPushButton>
#include <QDebug>
 
class CustomButton : public QPushButton
{
public:
    CustomButton(QWidget* parent = 0);
protected:
    void mousePressEvent(QMouseEvent *event);
private slots:
    void onButton();
};
 
#endif // CUSTOMBUTTON_H

CustomButton.cpp

#include "CustomButton.h"
#include <QMouseEvent>
 
CustomButton::CustomButton(QWidget* parent):QPushButton(parent)
{
    connect(this, &CustomButton::clicked, this, &CustomButton::onButton);
}
 
void CustomButton::onButton()
{
  qDebug() << "child clicked";
}
 
void CustomButton::mousePressEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton)
    {
        qDebug() << "Left press";
    }
    else
    {
        QPushButton::mousePressEvent(event);
    }
}

Main.cpp

#include "CustomButton.h"
#include <QApplication>
 
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    CustomButton button;
    button.setText("button");
    button.show();
 
    return a.exec();
}

输出:
编译运行程序,鼠标点击按钮后打印出“Left press”字符串,没有打印出“child clicked”字符串。
原因在于自定义类CustomButton的事件处理函数mousePressEvent()覆盖了父类QPushButton相应的事件处理函数。父类QPushButton的mousePressEvent()事件处理函数会发出clicked()信号,自定义类CustomButton实现则不发出clicked()信号。因此,自定义类CustomButton对象在鼠标按下时没有发出clicked()信号,连接的槽函数onButton()不会执行。可以把QT的事件传递看成链状:如果子类没有处理这个事件,就会继续向其父类传递。

自定义类中重写父类的事件处理函数时会覆盖父类相应的事件处理函数,父类的事件处理函数中包含的操作(如发出某些信号)也将会被覆盖。因此当重写事件回调函数时,必须注意是否需要通过调用父类的同名函数来确保原有实现仍能进行。

源码实现:

void QWidget::mousePressEvent(QMouseEvent *event)
{
    event->ignore();
    if ((windowType() == Qt::Popup)) 
    {
        event->accept();
        QWidget* w;
        while ((w = QApplication::activePopupWidget()) && w != this)
        {
            w->close();
            if (QApplication::activePopupWidget() == w)
                w->hide(); // hide at least
        }
        if (!rect().contains(event->pos()))
        {
            close();
        }
    }
}

mousePressEvent()源码中包含了Qt 事件对象的两个函数:

(1)accept() 通知QT平台,事件处理函数要处理这个事件;
(2)ignore() 则通知QT平台,事件处理函数不处理这个事件。

如果一个事件处理函数调用了一个事件对象的accept()函数,这个事件就不会被继续传播给其父组件;如果事件对象调用了事件的ignore()函数,QT平台会从其父组件中寻找另外的接受者。

在事件处理函数中,可以使用isAccepted()来查询某个事件是不是已经被接受。

通常很少会使用accept()和ignore()函数,如果希望忽略事件,只要调用父类的相应事件处理函数即可。

由于无法确认父类中相应的事件处理函数有没有额外的操作,如果在子类中直接使用ignore()函数忽略事件,QT会去寻找其他的接受者,父类的操作会被忽略(因为没有调用父类的同名函数),这可能会有潜在的危险。

为了避免子类去调用accept()和ignore()函数,而是尽量调用父类实现,QT做了特殊的设计:事件对象默认是accept的,而作为所有组件的父类QWidget的默认实现则是调用ignore()。

因此,如果子类重新实现事件处理函数,不调用QWidget的默认实现,就等于接受事件;如果要忽略事件,只需调用QWidget的默认事件处理函数实现。

2.2 事件的传播

事件的传播是在组件层次上面的,而不是依靠类继承机制。

示例代码:
CustomButton.h

#ifndef CUSTOMBUTTON_H
#define CUSTOMBUTTON_H
 
#include <QPushButton>
 
class CustomButton : public QPushButton
{
    Q_OBJECT
public:
    CustomButton(QWidget* parent = 0);
protected:
    void mousePressEvent(QMouseEvent *event);
};
 
#endif // CUSTOMBUTTON_H

CustomButton.cpp

#include "CustomButton.h"
#include <QMouseEvent>
#include <QDebug>
 
CustomButton::CustomButton(QWidget* parent):QPushButton(parent)
{
}
 
void CustomButton::mousePressEvent(QMouseEvent *event)
{
   event->ignore();
   qDebug() << "CustomButton";
}

CustomButtonEx.h

#ifndef CUSTOMBUTTONEX_H
#define CUSTOMBUTTONEX_H
 
#include "CustomButton.h"
 
class CustomButtonEx : public CustomButton
{
    Q_OBJECT
public:
    CustomButtonEx(QWidget* parent = 0);
protected:
    void mousePressEvent(QMouseEvent *event);
};
 
#endif // CUSTOMBUTTONEX_H

CustomButtonEx.cpp

#include "CustomButtonEx.h"
#include "CustomWidget.h"
#include <QMouseEvent>
#include <QDebug>
 
CustomButtonEx::CustomButtonEx(QWidget* parent):CustomButton(parent)
{
}
 
void CustomButtonEx::mousePressEvent(QMouseEvent *event)
{
    event->ignore();
    qDebug() << "CustomButtonEx";
}

CustomWidget.h

#ifndef CUSTOMWIDGET_H
#define CUSTOMWIDGET_H
 
#include <QWidget>
 
class CustomWidget : public QWidget
{
    Q_OBJECT
public:
    explicit CustomWidget(QWidget *parent = 0);
protected:
    void mousePressEvent(QMouseEvent *event);
};
 
#endif // CUSTOMWIDGET_H

CustomWidget.cp

#include "CustomWidget.h"
#include <QMouseEvent>
#include <QDebug>
 
CustomWidget::CustomWidget(QWidget *parent) : QWidget(parent)
{
}
 
void CustomWidget::mousePressEvent(QMouseEvent *event)
{
    qDebug() << "CustomWidget";
}

Main.cpp

#include "CustomButton.h"
#include "CustomButtonEx.h"
#include "CustomWidget.h"
#include <QApplication>
#include <QHBoxLayout>
 
int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    CustomWidget* customWidget = new CustomWidget();
 
    CustomButton* custombutton = new CustomButton(customWidget);
    custombutton->setText("CustomBuuton");
    CustomButtonEx* custombuttonex = new CustomButtonEx(customWidget);
    custombuttonex->setText("CustomButtonEx");
 
    QHBoxLayout* layout = new QHBoxLayout(customWidget);
    layout->addWidget(custombutton);
    layout->addWidget(custombuttonex);
    customWidget->setLayout(layout);
 
    customWidget->show();
    return a.exec();
}

输出:

鼠标按下CustomButtonEx,会打印出“CustomButtonEx”、“CustomWidget”。可见, CustomButtonEx的事件传播给了父组件CustomWidget,而不是父类CustomButton。

窗口关闭事件

在窗口关闭事件处理函数closeEvent必须使用accept()和ignore()函数。对于窗口关闭QCloseEvent事件,调用accept()意味着QT会停止事件的传播,窗口关闭;调用ignore()则意味着事件继续传播,即阻止窗口关闭。

void CustomWidget::closeEvent(QCloseEvent *event)
{
 
    bool exit = QMessageBox::question(this,
                     tr("Quit"),
                     tr("Are you sure to quit this application?"),
                     QMessageBox::Yes | QMessageBox::No,
                     QMessageBox::No) == QMessageBox::Yes;
    if (exit)
    {
        event->accept();
    }
    else
    {
        event->ignore();
    }
}

2.3 事件的分发

事件对象创建完毕后,QT将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是将这些事件对象按照它们不同的类型分发给不同的事件处理器(event handler)。event()函数主要用于事件的分发。如果期望在事件分发前做一些操作,可以重写event()函数。

代码示例:

/*  在QWidget组件中监听K键按下事件 */
bool CustomWidget::event(QEvent *event)
{
    if (event->type() == QEvent::KeyPress)
    {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
        if (keyEvent->key() == Qt::Key_K)
        {
            qDebug() << "You press K.";
            return true;
        }
    }
    return QWidget::event(event);
}

如果传入的事件已被识别并且处理,则需要返回 true,否则返回 false。如果返回值是 true,并且事件对象设置了accept(),那么QT会认为这个事件已经处理完毕,不会再将这个事件发送给其它组件,而是会继续处理事件队列中的下一事件。

注意,在event()函数中,调用事件对象的accept()和ignore()函数是没有作用的,不会影响到事件的传播。

通过使用QEvent::type()函数可以检查事件的实际类型,其返回值是QEvent::Type类型的枚举。处理完自己感兴趣的事件后,可以直接返回 true,表示已经对此事件进行了处理;对于其它不关心的事件,则需要调用父类的event()函数继续转发,否则自定义组件就只能处理我们感兴趣的事件,其它事件将被丢弃。

源码实现:

bool QObject::event(QEvent *e)
{
    switch (e->type()) 
    {
    case QEvent::Timer:
        timerEvent((QTimerEvent*)e);
        break;
    case QEvent::ChildAdded:
    case QEvent::ChildPolished:
    case QEvent::ChildRemoved:
        childEvent((QChildEvent*)e);
        break;
        // ...
    default:
        if (e->type() >= QEvent::User) 
        {
            customEvent(e);
            break;
        }
        return false;
    }
    return true;
}

可以看到,QObject::event()的实现与示例代码原理相同。

2.4 事件过滤器

2.4.1 为什么不用event()

QT创建了QEvent事件对象后,会调用QObject的event()函数处理事件的分发。虽然可以在event()函数中实现拦截的操作,但event()函数有两大缺点:

  • event()函数是 protected的,需要继承已有类。如果组件很多,就需要重写很多个event()函数。

  • event()函数虽然可以拦截事件,但其实组件是接收到了事件的。

2.4.2 推荐方式

为了解决event()函数拦截事件的缺陷,QT提供了另外一种方式来实现对事件的拦截:事件过滤器。QObject使用eventFilter()函数,来建立事件过滤器。

virtual bool QObject::eventFilter(QObject * watched, QEvent * event);

事件过滤器会检查接收到的事件。如果这个事件是感兴趣的类型,就进行处理;如果不是,就继续转发。
eventFilter函数返回一个bool类型,如果想将参数event过滤出来,比如不想让它继续转发,就返回true,否则返回false。
事件过滤器的调用时间是目标对象(watched对象)接收到事件对象前。如果在事件过滤器中停止了某个事件,那么watched对象以及以后所有的事件过滤器都不会知道这个事件。

2.4.3 创建过滤器

代码示例:

bool CustomWidget::eventFilter(QObject *watched, QEvent *event)
{
    if (watched == this && event->type() == QEvent::KeyPress)
    {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
        if (keyEvent->key() == Qt::Key_K)
        {
            qDebug() << "eventFilter: You press K.";
            return true;
        }
        else
        {
            return false;
        }
    }
    return false;
}

自定义类CustomWidget重写了eventFilter()函数。为了过滤特定组件上的事件,首先需要判断这个组件对象是不是感兴趣的组件,然后判断要过滤事件的类型。如果是要过滤的事件则直接返回true,即过滤掉这个事件,其他事件还是要继续处理,所以返回false。对于其它的组件,由于不保证是不是还有过滤器,最保险的办法是调用父类的eventFilter()函数,保证父对象上面设置的事件过滤器可以被调用。

2.4.4 安装过滤器

eventFilter()函数相当于创建了过滤器,要使过滤器生效需要安装过滤器。安装过滤器需要调用QObject::installEventFilter()函数。

void QObject::installEventFilter (QObject * filterObj);

filterObj是过滤器对象,即事件过滤器所属的类对象。

代码示例:

CustomWidget::CustomWidget(QWidget *parent) : QWidget(parent)
{
/* 过滤CustomWidget自己的事件 */
    this->installEventFilter(this);
}

输出:
创建事件过滤器并安装事件过滤器后,在CustomWidget按下鼠标时,鼠标事件将会被过滤,因此会打印出“eventFilter: You press K.”字符串,mousePressEvent()函数将不会被调用,不会打印出“CustomWidget”字符串。

eventFilter()函数是QObject的一个成员函数,因此,任意QObject都可以作为事件过滤器(如果没有重写eventFilter()函数,事件过滤器是没有任何作用的,因为默认什么都不会过滤)。已经存在的过滤器则可以通过QObject::removeEventFilter()函数移除。

可以向一个对象上面安装多个事件处理器,只要调用多次installEventFilter()函数。如果一个对象存在多个事件过滤器,那么,最后一个安装的会第一个执行,也就是后进先执行的顺序。

事件过滤器的强大之处在于可以为整个应用程序添加一个事件过滤器。installEventFilter()函数是QObject的函数,QApplication或者QCoreApplication对象都是QObject的子类,因此,可以向QApplication或者QCoreApplication添加事件过滤器。
代码示例:

CustomWidget::CustomWidget(QWidget *parent) : QWidget(parent)
{
/* 过滤QApplication的事件 */
   QApplication::instance()->installEventFilter(this);
}
2.4.5 注意事项
  1. 如果使用installEventFilter()函数给一个对象安装事件过滤器,那么该事件过滤器只对该对象有效,只有这个对象的事件需要先传递给事件过滤器的eventFilter()函数进行过滤,其它对象不受影响。

  2. 如果给QApplication对象安装事件过滤器,那么该过滤器对程序中的每一个对象都有效,任何对象的事件都是先传给eventFilter()函数。这种全局的事件过滤器将会在所有其它特性对象的事件过滤器之前调用。尽管很强大,但这种行为会严重降低整个应用程序的事件分发效率。因此,除非是不得不使用的情况,否则的话不应该这么做。

  3. 如果在事件过滤器中delete了某个接收组件,务必将函数返回值设为 true。否则,QT还是会将事件分发给这个接收组件,从而导致程序崩溃。

  4. 事件过滤器和被安装过滤器的组件必须在同一线程,否则,过滤器将不起作用。如果在安装过滤器之后,两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。QT中,对象创建之后可以使用moveToThread()函数将一个对象移动到另外的线程。

2.5 经验总结

总结一下完整Qt的事件处理方法:

  1. 重写特定事件处理函数:最常见的事件处理办法就是重写mousePressEvent()、keyPressEvent()、paintEvent() 等特定事件处理函数。

  2. 重写event()函数:event()函数是所有对象的事件入口,重写event()函数时, 需要调用父类的event()函数来处理不需要处理或是不清楚如何处理的事件。QObject和QWidget中的实现,默认是把事件传递给特定的事件处理函数。

  3. 在Qt对象上安装事件过滤器:安装事件过滤器有两个步骤: (假设要用A来监视过滤B的事件)首先调用B的installEventFilter( const QOject *obj ), 以A的指针作为参数,所有发往B的事件都将先由A的eventFilter()处理。然后, A要重写QObject::eventFilter()函数, 在eventFilter() 中对事件进行处理。在特定对象上面安装事件过滤器,事件过滤器仅过滤该对象接收到的事件。

  4. 给QAppliction对象安装事件过滤器:如果给QApplication对象装上过滤器,那么所有的事件在发往任何其他的过滤器时,都要先经过当前eventFilter()。在QApplication::notify() 中, 是先调用qApp的过滤器, 再对事件进行分析, 以决定是否合并或丢弃。全局的事件过滤器可以看到disabled组件上面发出的鼠标事件。全局过滤器只能用在主线程。

  5. 继承QApplication类,并重载notify()函数:Qt是用QApplication::notify()函数来分发事件的,要在任何事件过滤器查看任何事件之前先得到这些事件,重写notify()函数是唯一的办法。与全局事件过滤器一样,提供完全控制,并且不受线程的限制。但全局范围内只能有一个被使用(因为QCoreApplication是单例的)。通常来说事件过滤器更好用一些, 因为不需要去继承QApplication类,而且可以给QApplication对象安装任意个数的事件过滤器。

总结一下完整的QT事件处理顺序:

  1. 首先,所有事件都要经过QApplication::eventFilter(或notify,notify是调用qApp的eventFilter并分析);

  2. 然后,一层层按后进先执行的顺序,经过在某对象上安装的eventFilter;

  3. 之后,经过对象的event;

  4. 最后,到对象的特定事件处理函数,比如mousePressEvent().

三、自定义事件

在事件处理中讲过的是自定义类的事件处理,这里要讲的是自定义的事件。

3.1 关于类型

QT自定义事件,首先要继承自QEvent。

QEvent提供了一个叫QEvent::Type类型的参数,作为自定义事件的类型值。QEvent::Type是QEvent定义的一个枚举。注意,自定义事件类型不能和已经存在的type值重复,否则会有不可预料的错误发生,因为系统会将新增加的自定义事件当做系统事件进行派发和调用。

QT中,系统保留0 – 999的值,自定义事件的type要大于 999。QT定义了两个边界值:QEvent::User和QEvent::MaxUser,自定义事件的type应该在两个值的范围之间。

其中,QEvent::User的值是1000,QEvent::MaxUser的值是65535。通过这两个枚举值,可以保证自定义的事件类型不会覆盖系统定义的事件类型。但并不能保证自定义事件相互之间不会被覆盖。

为了避免自定义事件间的相互覆盖,QT提供了一个函数:registerEventType(),用于自定义事件的注册。

示例代码:

static int QEvent::registerEventType ( int hint = -1 );

解释:

registerEventType函数是static的,可以使用QEvent类直接调用。函数返回值是向系统注册的新的Type类型的值。如果hint是合法的,即hint不会发生任何覆盖(系统的以及其它自定义事件的),则会直接返回这个值;否则,系统会自动分配一个合法值并返回。使用registerEventType函数即可完成type 值的指定。registerEventType函数是线程安全的,不必另外添加同步。

3.2 发送方式

可以在自定义事件中添加所需要的数据,然后进行事件的发送。QT提供了两种事件发送方式:

非阻塞式发送

static bool QCoreApplication::sendEvent(QObject *receiver,QEvent *event);

直接将event事件发送给receiver接收者,使用的是QCoreApplication::notify()函数。函数返回值就是事件处理函数的返回值。在事件被发送的时候,event对象并不会被销毁。通常会在栈上创建event对象,例如:

QMouseEvent event(QEvent::MouseButtonPress, pos, 0, 0, 0);
QApplication::sendEvent(receiver, &event);

阻塞式发送

static void QCoreApplication::postEvent(QObject *receiver,QEvent *event);

将event事件及其接收者receiver一同追加到事件队列中,函数立即返回。

因为post事件队列会持有事件对象,并且在其post的时候将其delete掉,因此,必须在堆上创建event对象。当对象被发送之后,再试图访问event对象就会出现问题(因为post后,event对象就会被delete)。

当控制权返回到主线程循环时,保存在事件队列中的所有事件都通过notify()函数发送出去。

事件会根据post的顺序进行处理。如果想要改变事件的处理顺序,可以考虑为其指定一个优先级。默认的优先级是Qt::NormalEventPriority。

postEvent函数是线程安全的。

批量发送

static void QCoreApplication::sendPostedEvents(QObject *receiver,int event_type);

sendPostedEvents函数的作用是将事件队列中的接收者为receiver,事件类似为event_type的所有事件立即发送给receiver进行处理。需要注意的是,来自窗口系统的事件并不由sendPostedEvents函数进行处理,而是processEvent()。

3.3 事件处理

Qt提供了两种处理自定义事件的方法,

3.3.1 重写QObject的customEvent()函数
void CustomWidget::customEvent(QEvent *event) {
        CustomEvent *customEvent = static_cast<CustomEvent *>(event);
        /* .... */
}
3.3.2 重写event()函数
bool CustomWidget::event(QEvent *event) {
        if (event->type() == MyCustomEventType) {
                CustomEvent *myEvent = static_cast<CustomEvent *>(event);
                /* processing... */
                return true;
        }

        return QWidget::event(event);
}

这两种办法都是可行的。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值