QT 学习笔记(九)

由于每次代码都是在原有程序上修改,因此除了新建项目,不然一般会在学完后统一展示代码。
提示:具体项目创建流程和注意事项见QT 学习笔记(一)
提示:具体项目准备工作和细节讲解见QT 学习笔记(二)

一、事件的接收和忽略

1. 准备工作

  • 在 ui 界面新建一个按钮,在项目文件当中新建一个 mybutton 的 C++ 类。
  • 将 ui 界面的按钮提升为 mybutton 。自定义控件提升详见QT 学习笔记(七)
  • 在 主窗口源文件 mywidget.cpp 当中对按钮进行操作,对基本功能进行测验。
  • 注意:要在项目文件 day8.pro 当中添加 CONFIG +=C++11 才可以使 connect 功能正常实现。
    在这里插入图片描述
  • 当我们点击按钮时,会在输出窗口输出按钮被按下,如下图所示:
    在这里插入图片描述

2. 接收和忽略

  • 在上一步的检测操作完成后,在按钮源文件 mybutton.cpp 当中,进行事件的接收和忽略操作。
  • 通过 if 语句,对鼠标左键按下在输出窗口输出按下的是左键,对其他鼠标按键不做处理,调用原函数。
  • 在上述操作当中,鼠标左键对应的就是事件的接收其他按键对应的就是事件的忽略
    在这里插入图片描述
  • 在这里有一个问题,当我们按下鼠标左键时,会输出按下的是左键,但当我们按下其他按键时,并没有调用原函数,即输出按钮被按下。
  • 这是因为当我们按下鼠标左键的时候,事件的信号被 qDebug() 输出语句给拦截了,不会再向后传递,无法走到 else 对应的内容。
  • 因此,在我们编写代码时,事件的忽略不要编写实现代码即可
  • 事件的接收,就不会往下传递。
  • 事件的忽略,事件会继续往下传递。
  • 我们可以把 QT 的事件传递看成链状:如果子类没有处理这个事件,就会继续向其父组件传递。
  • QT 的事件对象有两个函数:accept() 和 ignore() 。
  • accept() 用来告诉 QT,这个类的事件处理函数想要处理这个事件;
  • 如果一个事件处理函数调用了一个事件对象的 accept() 函数,这个事件就不会被继续传播给其父组件。
  • ignore() 用来告诉 QT,这个类的事件处理函数不想要处理这个事件;
  • 如果调用了事件的 ignore() 函数,QT 会从其父组件中寻找另外的接受者。在事件处理函数中,可以使 isAccepted() 来查询这个事件是不是已经被接收了。
  • 我们在主窗口 myWidget.cpp 中,重写鼠标点击事件,每当鼠标按下的时候,输出 ++++++++++,同时,将按钮窗口 mybutton.cpp 当中的 e->ignore() 注释掉,得到如下现象:
    在这里插入图片描述
  • 这样操作并不会输出 ++++++++++。此时,我们取消按钮窗口 mybutton.cpp 当中的 e->ignore() 的注释,得到下面的现象:
    在这里插入图片描述
  • 当我们点击按钮的时候,会同时输出按下的是左键和 ++++++++++,当我们点击按钮外的界面时,只会输出 ++++++++++。
  • 这是因为没有接收到信号,所以没有触发那里的 e->ignore()。而这里的 e->ignore() 可以让这个信号再次传递下去,不过不是传递给 mybutton,而是 父组件 mywidget。
  • 这个 e->ignore() 主要使用在关闭项目当中,也就是窗口右上角大家熟知的红色关闭按钮,在此简单实现一下(只展示实现现象,具体代码会在总体代码当中展示)。
  • 在我们实际关闭窗口的时候,往往会弹出一句 确定关闭吗? 这种类似的提示。
  • 为了实现这样的功能,需要包含头文件 QMessageBox ,使用 question() 函数。question() 函数中的第一个参数是指定父对象,第二个参数是标题,第三个参数是提示内容,第四个参数不写的话默认有两个按钮:yes 和 no 。
  • 为实现功能,只需要一个简单 if 语句即可,如果点击 yes ,则处理关闭窗口事件,接收事件,事件就不会再往下传递;如果点击 no ,则忽略事件,事件继续给父组件传递。
  • 具体现象如下图所示:
    在这里插入图片描述

二、event() 函数

1. 简介

  • 事件对象创建完毕后,QT 将这个事件对象传递给 QObject 的 event() 函数。event() 函数并不直接处理事件,而是将这些事件对象按照它们不同的类型,分发给不同的事件处理器(event handler)。
  • 因此,event() 函数主要用于 事件的分发。所以,如果你希望在事件分发之前做一些操作,就可以重写这个 event() 函数了。
  • 例如,我们希望在一个 QWidget 组件中监听 tab 键的按下,那么就可以继承 QWidget,并重写它的 event() 函数,来达到这个目的。
  • 事件分发如下图所示。
    在这里插入图片描述
  • 与其他的事件返回值不同的是,event() 事件的返回值是 bool 类型。
  • 如果传入的事件已被识别并且处理,则需要返回 true,否则返回 false。如果返回值是 true,那么 QT 会认为这个事件已经处理完毕,不会再将这个事件发送给其它对象, 而是会继续处理事件队列中的下一事件。
  • 在 event() 函数中,调用事件对象的 accept() 和 ignore() 函数是没有作用的,不会影响到事件的传播。
  • 相当于在所有信号接收前进行一个检查,满足要求的停止工作,其他的继续原动作。

2. 实例演示

  • 在此以定时器为例,重写 event() 函数,使得定时器停止工作,其他部件保持原动作。代码和实现结果如下:
//重写event事件
bool myWidget::event(QEvent *e)
{
        if(e->type()==QEvent::Timer)
    {
        //干掉定时器
        //如果返回true,事件停止传播
        return true;
    }
    else
    {
        return QWidget::event(e);
    }
}

在这里插入图片描述

  • 将其恢复正常需要一个强制类型转换,只需在上述代码当中添加如下代码即可:
QTimerEvent *env = static_cast<QTimerEvent *>(e);
        timerEvent(env);
  • 正常现象如下图所示:

在这里插入图片描述

  • 我们在此继续进行一个按键 B 的 event() 函数重写,只有当我们按下按键 B 的时候,事件才继续,其他的事件均终止。代码和实现结果如下:
else if(e->type()==QEvent::KeyPress)
    {
        //类型转换
        QKeyEvent *env = static_cast<QKeyEvent *>(e);
        if(env->key()==Qt::Key_B)
        {
            return QWidget::event(e);
        }
        return true;
    }

在这里插入图片描述

3. 总结

  • QTimerEvent() 和 QKeyEvent() 这样的函数,就是前面所说的事件处理器 event handler。
  • event() 函数中实际是通过事件处理器来响应一个具体的事件。这相当于 event() 函数将具体事件的处理“委托”给具体的事件处理器。而这些事件处理器是 protected virtual 的,因此,我们重写了某一个事件处理器,即可让 QT 调用我们自己实现的版本。
  • event() 是一个集中处理不同类型的事件的地方。如果你想重写一大堆事件处理器,就可以重写这个 event() 函数,通过 QEvent::type() 判断不同的事件。由于重写 event() 函数需要十分小心注意父类的同名函数的调用,一不留神就可能出现问题。
  • 因此,一般只重写事件处理器(当然,也必须记得是不是应该调用父类的同名处理器)。这其实也表明了 event() 函数的另外一个作用:屏蔽掉某些不需要的事件处理器

三、事件过滤器

  • 很多时候,对象需要查看、甚至要拦截发送到另外对象的事件。例如,对话框可能想要拦截按键事件,不让别的组件接收到;或者要修改回车键的默认处理。
  • 通过前文,我们已经知道,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 对象以及以后所有的事件过滤器根本不会知道这么一个事件。
  • 在实现事件过滤器之前,我们要在 ui 界面选取一个合适的标签,在这里选择的是 label_2 。 在选择完成后进行操作安装过滤器,该函数的参数是由哪个函数的父对象进行处理。
//安装过滤器
ui->label_2->installEventFilter(this);
  • 通过事件过滤器,将 label_2 ,转换为鼠标移动,并实时显示出对应的 x 和 y 坐标,其他事件保持不变。(鼠标按下和释放的整体流程与鼠标移动是相同的,按钮的操作方法和标签也是相同的,具体代码在汇总时展示)

在这里插入图片描述

  • 注意:事件过滤器和被安装过滤器的组件必须在同一线程,否则,过滤器将不起作用。如果在安装过滤器之后,这两个组件到了不同的线程,那么,只有等到二者重新回到同一线程的时候过滤器才会有效。

四、总结(细看)

1. 知识点汇总

  • QT 的事件是整个 QT 框架的核心机制之一。涉及到的函数众多,而处理方法也很多。
  • QT 中有很多种事件:鼠标事件、键盘事件、大小改变的事件、位置移动的事件等等。如何处理这些事件,实际有两种选择:
  • (1) 所有事件对应一个事件处理函数,在这个事件处理函数中用一个很大的分支语句进行选择。
  • (2) 每一种事件对应一个事件处理函数。QT 就是使用的这么一种机制:mouseEvent() ,keyPressEvent() 等。
  • 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() 函数的两点不足:
  • (1) 事件过滤器不是 protected 的,因此我们可以向任何 QObject 子类安装事件过滤器;
  • (2) 事件过滤器在目标对象接收到事件之前进行处理,如果我们将事件过滤掉,目标对象根本不会见到这个事件。

2. QT 的事件处理

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

五、事件、事件的接收和忽略、event() 函数和事件过滤器代码

1. 主窗口头文件 mywidget.h

#ifndef MYWIDGET_H
#define MYWIDGET_H

#include <QWidget>

namespace Ui {
class myWidget;
}

class myWidget : public QWidget
{
    Q_OBJECT

public:
    explicit myWidget(QWidget *parent = nullptr);
    ~myWidget();

protected:
    //键盘按下事件
    void keyPressEvent(QKeyEvent *);
    //计时器事件
    void timerEvent(QTimerEvent *);
    //重写鼠标点击事件
    void mousePressEvent(QMouseEvent *);
    //关闭事件
    void closeEvent(QCloseEvent *);
    //重写event事件
    bool event(QEvent *);
    //事件过滤器
    bool eventFilter(QObject *,QEvent *);

private:
    Ui::myWidget *ui;

    int timeid;
    int timeid2;
};

#endif // MYWIDGET_H

2. 主窗口源文件 mywidget.cpp

#include "mywidget.h"
#include "ui_mywidget.h"
#include <QDebug>
#include <QKeyEvent>
#include <QCloseEvent>
#include <QMessageBox>
#include <QEvent>

myWidget::myWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::myWidget)
{
    ui->setupUi(this);

    //启动计时器
    timeid = this->startTimer(1000);  //毫秒为单位。每隔1s触发一次定时器
    timeid2 = this->startTimer(500);  //毫秒为单位。每隔0.5s触发一次定时器

    connect(ui->pushButton,&mybutton::clicked,
            [=]()
    {
        qDebug()<<"按钮被按下";
    }
            );

    //安装过滤器
    ui->label_2->installEventFilter(this);
    //设置鼠标追踪
    ui->label_2->setMouseTracking(tr);
}

myWidget::~myWidget()
{
    delete ui;
}

//键盘按下事件
void myWidget::keyPressEvent(QKeyEvent *e)
{
    qDebug()<<(char)e->key();
    if(e->key()==Qt::Key_A)
    {
        qDebug()<<"Qt::Key_A";
    }
}

//计时器事件
void myWidget::timerEvent(QTimerEvent *e)
{
    if(e->timerId()==this->timeid)
    {
        static int sec = 0;
        ui->label->setText(
            QString("<center><h1>time out:%1</h1></center>").arg(sec++)
               );

        //停止计时器
        /*if(5==sec)
        {
            this->killTimer(this->timeid);
        }    */
    }
    else if (e->timerId()==this->timeid2)
    {
        static int sec = 0;
        ui->label_2->setText(
            QString("<center><h1>time out:%1</h1></center>").arg(sec++)
               );

        //停止计时器
        /*if(5==sec)
        {
            this->killTimer(this->timeid);
        }    */
    }
}

//重写鼠标点击事件
void myWidget::mousePressEvent(QMouseEvent *e)
{
    qDebug()<<"++++++++++";
}

//重写鼠标点击事件
void myWidget::closeEvent(QCloseEvent *e)
{
    int ret = QMessageBox::question(this,"question","是否需要关闭窗口");
    //question 第一个参数是指定父对象,第二个参数是标题,
    //第三个参数是提示内容,第四个参数不写的话默认有两个按钮:yes和no。
    if(ret==QMessageBox::Yes)
    {
        //关闭窗口
        //处理关闭窗口事件,接收事件,事件就不会再往下传递
        e->accept();
    }
    else
    {
        //不关闭窗口
        //忽略事件,事件继续给父组件传递
        e->ignore();
    }
}

//重写event事件
bool myWidget::event(QEvent *e)
{
    if(e->type()==QEvent::Timer)
    {
        //干掉定时器
        //如果返回true,事件停止传播
        //QTimerEvent *e
        //QTimerEvent *env = static_cast<QTimerEvent *>(e);
        //timerEvent(env);
        return true;
    }
    else if(e->type()==QEvent::KeyPress)
    {
        //类型转换
        QKeyEvent *env = static_cast<QKeyEvent *>(e);
        if(env->key()==Qt::Key_B)
        {
            return QWidget::event(e);
        }
        return true;
    }
    else
    {
        return QWidget::event(e);
    }
}

//事件过滤器
bool myWidget::eventFilter(QObject *obj,QEvent *e)
{
    if(obj==ui->label_2)
    {
        //类型转换
        QMouseEvent *env=static_cast<QMouseEvent *>(e);
        //判断事件
        if(e->type()==QEvent::MouseMove)
        {
            ui->label_2->setText
                    (QString("mouse move:(%1,%2)").arg(env->x()).arg(env->y()));
            return true;
        }
        else
        {
            return QWidget::eventFilter(obj,e);
        }
    }
    else
    {
        return QWidget::eventFilter(obj,e);
    }
}

3. 标签头文件 mylabel.h

#ifndef MYLABEL_H
#define MYLABEL_H

#include <QLabel>

class mylabel : public QLabel
{
    Q_OBJECT
public:
    explicit mylabel(QWidget *parent = nullptr);

protected:
    //鼠标点击事件
    void mousePressEvent(QMouseEvent *ev);
    //鼠标释放事件
    void mouseReleaseEvent(QMouseEvent *ev);
    //鼠标移动事件
    void mouseMoveEvent(QMouseEvent *ev);
    //进入窗口区域
    void enterEvent(QEvent *);
    //离开窗口区域
    void leaveEvent(QEvent *);

signals:

public slots:
};

#endif // MYLABEL_H

4. 标签源文件 mylabel.cpp

#include "mylabel.h"
#include <QMouseEvent>

mylabel::mylabel(QWidget *parent) : QLabel(parent)
{

}

//鼠标点击事件
void mylabel::mousePressEvent(QMouseEvent *ev)
{
    int i=ev->x();
    int j=ev->y();
    //sprinf 字符串格式化命令
    /*
     * QString str = QString("abc %1 ^_^ %2").arg(123).arg("mike");
     * str = abc 123 ^_^ mike
    */

    QString text = QString("<center><h1>mouse press:(%1,%2)</h1></center>")
            .arg(i).arg(j);
    // center 居中,h1 一级标题
    this->setText(text);
}

//鼠标释放事件
void mylabel::mouseReleaseEvent(QMouseEvent *ev)
{
    QString text = QString("<center><h1>mouse release:(%1,%2)</h1></center>")
            .arg(ev->x()).arg(ev->y());
    this->setText(text);
}

//鼠标移动事件
void mylabel::mouseMoveEvent(QMouseEvent *ev)
{
    QString text = QString("<center><h1>mouse move:(%1,%2)</h1></center>")
            .arg(ev->x()).arg(ev->y());
    //this->setText(text);
}

//进入窗口区域
void mylabel::enterEvent(QEvent *e)
{
    QString text = QString("<center><h1>mouse enter</h1></center>");
    this->setText(text);
}

//离开窗口区域
void mylabel::leaveEvent(QEvent *e)
{
    QString text = QString("<center><h1>mouse leave</h1></center>");
    this->setText(text);
}

5. 按钮头文件 mybutton.h

#ifndef MYBUTTON_H
#define MYBUTTON_H

#include <QPushButton>

class mybutton : public QPushButton
{
    Q_OBJECT
public:
    explicit mybutton(QWidget *parent = nullptr);

protected:
    void mousePressEvent(QMouseEvent *e);

signals:

public slots:
};

#endif // MYBUTTON_H

6. 按钮源文件 mybutton.cpp

#include "mybutton.h"
#include <QMouseEvent>
#include <QDebug>

mybutton::mybutton(QWidget *parent) : QPushButton(parent)
{

}

void mybutton::mousePressEvent(QMouseEvent *e)
{

    if(e->button() == Qt::LeftButton)
    {
        //如果是左键按下
        qDebug()<<"按下的是左键";
        //事件接收后,就会往下传递

        e->ignore();
        //忽略,事件继续往下传递,事件传递给了父组件,不是给父类(基类)
    }
    else
    {
        //不做处理
        QPushButton::mousePressEvent(e);
        //事件的忽略,事件继续往下传递
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虚心求知的熊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值