Qt——事件&&事件过滤器

事件

在这里插入图片描述

用户进行的各种操作,除了产生信号,也可能产生事件。我们同样可以给事件关联上处理函数,当事件出发的时候,就能够执行到对应的代码

事件本身是操作系统提供的机制。Qt也同样把操作系统事件机制进行了封装

Qt对于事件机制又进行了又一步封装,就得到了信号槽

可以说,事件是信号槽的底层机制

事件处理

处理事件的常见方法,就是:

利用多态机制,创建子类,继承Qt已有的类,让子类,重写某个事件处理函数

鼠标事件

enterEvent && leaveEvent

鼠标进入、离开事件

[virtual protected] void QWidget::enterEvent(QEnterEvent *event)
[virtual protected] void QWidget::leaveEvent(QEvent *event)

例如,当鼠标光标进入Widget窗口时,就会触发enterEnvent事件;离开窗口时,就会触发leaveEvent事件。我们就可以对这两个事件进行处理:

// label.h
#ifndef LABEL_H
#define LABEL_H

#include <QLabel>

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

    void enterEvent(QEnterEvent* event);
    void leaveEvent(QEvent* event);
};

#endif  // LABEL_H

// label.cpp
#include "label.h"

#include <QDebug>

Label::Label(QWidget* parent) : QLabel(parent) {}

void Label::enterEvent(QEnterEvent* event) { qDebug() << "enter..."; }

void Label::leaveEvent(QEvent* event) { qDebug() << "leave..."; }

注意:如果我们要测试的控件,是在Qt Designer中放置的控件,那么其还是Qt内置的类,如果我们需要让其能够执行我们自定义的事件处理函数,就需要对其进行提升:

在这里插入图片描述

同时,还可能需要在CMakeLists.txt中加上:include_directories(${CMAKE_CURRENT_SOURCE_DIR})。指定头文件搜索路径

效果:

在这里插入图片描述


mousePressEvent

鼠标点击事件

[virtual protected] void QWidget::mousePressEvent(QMouseEvent *event)

这里的鼠标点击,不仅仅是左键点击,只要是鼠标上的按键点击,都可以触发

例如,我们可以通过这个事件,来获取鼠标点击的位置

// label.h

#ifndef LABEL_H
#define LABEL_H

#include <QLabel>
#include <QMouseEvent>

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

    void mousePressEvent(QMouseEvent* event);
};

#endif  // LABEL_H

// label.cpp
#include "label.h"

#include <QDebug>

Label::Label(QWidget* parent) : QLabel(parent) {}

void Label::mousePressEvent(QMouseEvent* event) {
    qDebug() << "x: " << event->x() << ", y: " << event->y();
}

效果:

在这里插入图片描述


mouseReleaseEvent

鼠标释放事件

[virtual protected] void QWidget::mouseReleaseEvent(QMouseEvent *event)

例如:

#ifndef LABEL_H
#define LABEL_H

#include <QLabel>
#include <QMouseEvent>

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

    void mousePressEvent(QMouseEvent* event);
    void mouseReleaseEvent(QMouseEvent* event);
};

#endif  // LABEL_H

// label.cpp
#include "label.h"

#include <QDebug>

Label::Label(QWidget* parent) : QLabel(parent) {}

void Label::mousePressEvent(QMouseEvent* event) {
    qDebug() << "鼠标点击 " << "x: " << event->x() << ", y: " << event->y();
}

void Label::mouseReleaseEvent(QMouseEvent* event) { qDebug() << "鼠标释放"; }

效果:

在这里插入图片描述


mouseDoubleClickEvent

鼠标双击事件

[virtual protected] void QWidget::mouseDoubleClickEvent(QMouseEvent *event)

例如:

#ifndef LABEL_H
#define LABEL_H

#include <QLabel>
#include <QMouseEvent>

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

    void mousePressEvent(QMouseEvent* event);
    void mouseReleaseEvent(QMouseEvent* event);
    void mouseDoubleClickEvent(QMouseEvent* event);
};

#endif  // LABEL_H


// label.cpp
#include "label.h"

#include <QDebug>

Label::Label(QWidget* parent) : QLabel(parent) {}

void Label::mousePressEvent(QMouseEvent* event) {
    qDebug() << "鼠标点击 " << "x: " << event->x() << ", y: " << event->y();
}

void Label ::mouseDoubleClickEvent(QMouseEvent* event) { qDebug() << "鼠标双击"; }

void Label::mouseReleaseEvent(QMouseEvent* event) { qDebug() << "鼠标释放"; }
  • 鼠标双击,同样可以触发鼠标点击事件
  • 因此,如果一个控件既要处理鼠标单击事件,又要处理鼠标双击事件,此时就可能出现bug

效果:

在这里插入图片描述


mouseMoveEvent

鼠标移动事件

[virtual protected] void QWidget::mouseMoveEvent(QMouseEvent *event)

需要注意:

  • 鼠标移动不同于鼠标按下
  • 随便一动一下鼠标,就会产生大量的鼠标移动事件,Qt就需要花费大量的资源来处理事件处理函数
  • 因此Qt为了保证效率,默认不会处理鼠标移动事件,除非我们显式指定**setMouseTracking()**

例如:

#ifndef LABEL_H
#define LABEL_H

#include <QLabel>
#include <QMouseEvent>

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

    void mouseMoveEvent(QMouseEvent* event);
};

#endif  // LABEL_H

// label.cpp
#include "label.h"

#include <QDebug>

Label::Label(QWidget* parent) : QLabel(parent) {
    this->setMouseTracking(true);  // 设置开启鼠标追踪
}

void Label::mouseMoveEvent(QMouseEvent* event) {
    qDebug() << "移动 " << "x: " << event->x() << ", y: " << event->y();
}

效果:

在这里插入图片描述


wheelEvent

鼠标滚动事件

[virtual protected] void QWidget::wheelEvent(QWheelEvent *event)

例如:

#ifndef WIDGET_H
#define WIDGET_H

#include <QWheelEvent>
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget {
    Q_OBJECT

   public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    void wheelEvent(QWheelEvent *event);

   private:
    Ui::Widget *ui;

    int total_ = 0;
};
#endif  // WIDGET_H


// widget.cpp
#include "widget.h"

#include <QDebug>

#include "./ui_widget.h"

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

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

void Widget::wheelEvent(QWheelEvent *event) {
    total_ += event->angleDelta().y();

    qDebug() << total_;
}
  • 方法angleDelta用于获取滚轮的角度增量

  • 它返回一个 QPoint 对象,包含了水平和垂直两个方向的滚动信息:

    • angleDelta().y() 对应原来的垂直方向滚动(与旧的 delta() 功能相同)
    • angleDelta().x() 可以获取水平滚轮(如果鼠标支持)的滚动信息

效果:

在这里插入图片描述


键盘事件

keyPressEvent

键盘按下事件

[virtual protected] void QWidget::keyPressEvent(QKeyEvent *event)

例如:

#ifndef WIDGET_H
#define WIDGET_H

#include <QKeyEvent>
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget {
    Q_OBJECT

   public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    void keyPressEvent(QKeyEvent *event);

   private:
    Ui::Widget *ui;
};
#endif  // WIDGET_H


// widget.cpp
#include "widget.h"

#include <QDebug>

#include "./ui_widget.h"

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

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

void Widget::keyPressEvent(QKeyEvent *event) {
    qDebug() << "按下按键: " << event->key();

    if (event->key() == 'A' && event->modifiers() == Qt::ShiftModifier) {
        qDebug() << "按下了: Shift + A";
    }
}
  • QKeyEvent的方法key(),用于返回用户按下的按键的Key值,不区分大小写
  • QKeyEvent的方法modifiers(),用于返回键盘修饰键(CTRL,SHIFT、ALT…)的状态(是否被按下)
  • 而这组合就可以判断组合键的按下

定时器事件 QTimer

之前我们将定时器和信号槽机制关联使用过,实际上,QTimer背后是QTimerEvent定时器事件进行支撑的

QObject提供了一个timerEvent这个函数,来处理定时器事件

同时可以使用方法startTimerkillTimer来启动和关闭定时器事件

int QObject::startTimer(int interval, Qt::TimerType timerType = Qt::CoarseTimer)
void QObject::killTimer(int id)
  • startTimer会返回一个time_id,即定时器的唯一标识
  • killTimer需要传入一个time_id,即要关闭哪一个计时器

例如:

#ifndef WIDGET_H
#define WIDGET_H

#include <QTimerEvent>
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget {
    Q_OBJECT

   public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    void timerEvent(QTimerEvent *event);

   private:
    Ui::Widget *ui;

    int time_id_;
};
#endif  // WIDGET_H

// widget.cpp
#include "widget.h"

#include <QDebug>

#include "./ui_widget.h"

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

    time_id_ = startTimer(1000);  // 设置一个1000ms触发一次的定时器,并记录time_id
    ui->lcdNumber->display(6);
}

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

void Widget::timerEvent(QTimerEvent *event) {
    // 说明是自己这个类触发的定时器
    if (event->timerId() == time_id_) {
        int val = ui->lcdNumber->value();
        if (val > 0) {
            val -= 1;
            ui->lcdNumber->display(val);
        } else {
            killTimer(time_id_);
        }
    }
}

窗口事件

moveEvent && resizeEvent

[virtual protected] void QWidget::moveEvent(QMoveEvent *event)
[virtual protected] void QWidget::resizeEvent(QResizeEvent *event)

moveEvent:用来检测窗口的移动

resizeEvent:用来检测窗口的大小

例如:

#ifndef WIDGET_H
#define WIDGET_H

#include <QMoveEvent>
#include <QResizeEvent>
#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget {
    Q_OBJECT

   public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    void moveEvent(QMoveEvent *event);
    void resizeEvent(QResizeEvent *event);

   private:
    Ui::Widget *ui;
};
#endif  // WIDGET_H

// widget.cpp
#include "widget.h"

#include <QDebug>

#include "./ui_widget.h"

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

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

void Widget::moveEvent(QMoveEvent *event) {
    qDebug() << "窗口移动 " << "之前的位置: " << event->oldPos() << "现在的位置: " << event->pos();
}

void Widget::resizeEvent(QResizeEvent *event) {
    qDebug() << "窗口大小变化 " << "之前的大小: " << event->oldSize()
             << "现在的大小: " << event->size();
}

效果:

在这里插入图片描述


事件过滤器

问题

如果要实现这样的功能:

点击按钮,弹出对话框,对话框中有多个QLineEdit,我们想要在对话框中,以按下空格的方式,切换QLineEdit的焦点

利用上述学到的事件知识,可以利用事件keyPressEvent,重写QLineEditkeyPressEvent,不难写出下面的代码:

// LineEdit.cpp
#include "lineedit.h"

LineEdit::LineEdit(QWidget *parent) : QLineEdit(parent) {}

void LineEdit::keyPressEvent(QKeyEvent *event) {
	// 如果按下空格,就将焦点移动到下一个widget
    if (event->key() == Qt::Key_Space) {
        this->focusNextChild();
    } else {
        QLineEdit::keyPressEvent();	// 如果不是空格,就使用默认的keyPressEvent事件
    }
}

注意:方法focusNextChild的功能为,将输入焦点从当前控件转移到父窗口中 “下一个” 符合焦点规则的子控件

但是,如果对话框中,不只有LineEdit,还有其他的控件,仅仅重写QLineEditkeyPressEvent事件,就无法解决问题了:

在这里插入图片描述

可以看到,因为我没有重写QPushButtonkeyPressEvent事件,导致当焦点转移到QPushButton上时,再按下空格,就会触发QPushButton的默认逻辑,这不是我们想要的。

解决办法之一,就是继续重写QPushButtonkeyPressEvent事件,但是如果不同的控件多起来,这种方法效率显然就不高了


事件过滤器

针对上述情况,最好的办法就是让对话框Dialog监视其子控件的键盘事件

上面的方法,也就是Qt的事件过滤器。实现一个事件过滤包括两个步骤:

  1. 在监视对象的构造函数中,被监视对象调用installEventFilter(),注册监视对象。

  2. 在监视对象的eventFilter()函数中处理目标对象的事件。

    [virtual] bool QObject::eventFilter(QObject *watched, QEvent *event)
    
  • 作用:如果watched被注册到了Filter中,那么所有发送到watched的事件,都会先经过事件过滤器Filter(即函数eventFilter
  • 它相当于一个 “事件拦截站”,可以在事件到达目标对象前对其进行处理或拦截
  • 如果返回TRUE,表示事件过滤器对watchedevent事件进行了处理并拦截,不会再将event事件发送给watched
  • 如果返回FALSE,表示过滤器不拦截event,Qt 会继续将事件传递给 watched 对象,由其正常处理(如调用自身的事件处理函数

例如:

#include "dialog.h"

#include <QKeyEvent>

#include "ui_dialog.h"

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

    ui->lineEdit->installEventFilter(this);
    ui->lineEdit_2->installEventFilter(this);
    ui->lineEdit_3->installEventFilter(this);
    ui->pushButton->installEventFilter(this);
    ui->pushButton_2->installEventFilter(this);

    ui->pushButton->setFocusPolicy(Qt::StrongFocus);
    ui->pushButton_2->setFocusPolicy(Qt::StrongFocus);
}

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

bool Dialog::eventFilter(QObject *watched, QEvent *event) {
    // 如果watched被注册到了Filter中
    if (watched == ui->lineEdit || watched == ui->lineEdit_2 || watched == ui->lineEdit_3 ||
        watched == ui->pushButton || watched == ui->pushButton_2) {
        
        // 如果是按钮按下事件
        if (event->type() == QEvent::KeyPress) {
            QKeyEvent *key_event = static_cast<QKeyEvent *>(event);
            
            
            // 如果按下的是空格
            if (key_event->key() == Qt::Key_Space) {
                this->focusNextChild();

                return true;    // 拦截事件
            }
        }
    }
    
    return QDialog::eventFilter(watched, event);
}

效果:

在这里插入图片描述

事件过滤器的优先级

在 Qt 中,事件过滤器的优先级遵循 “安装顺序”和“作用范围” 的规则,具体可分为以下几种情况:

同一目标对象的多个过滤器

当一个目标对象(如 QLineEdit)被多个过滤器对象监视时,后安装的过滤器会优先处理事件

例如:

// 目标对象
QLineEdit *edit = new QLineEdit;

// 过滤器 A 先安装
edit->installEventFilter(filterA);
// 过滤器 B 后安装
edit->installEventFilter(filterB);

此时,事件到达顺序为:filterB->eventFilter()filterA->eventFilter() → 目标对象自身的事件处理。

  • filterB 返回 true(拦截事件),则 filterA 和目标对象都不会收到该事件。
  • filterB 返回 false(不拦截),事件才会传递给 filterA

2. 不同作用范围的过滤器

当事件同时被局部过滤器(针对单个目标对象)和全局过滤器(如 QApplication 安装的过滤器)监视时,局部过滤器优先处理

例如:

// 全局过滤器(监视所有对象)
qApp->installEventFilter(globalFilter);

// 局部过滤器(只监视 edit)
edit->installEventFilter(localFilter);

此时,事件到达顺序为:localFilter->eventFilter()globalFilter->eventFilter() → 目标对象自身的事件处理。

  • 局部过滤器(localFilter)先判断是否处理事件,若拦截(返回 true),全局过滤器和目标对象均无感知。
  • 若局部过滤器不拦截,事件才会传递给全局过滤器。

3. 目标对象与父对象的过滤器

如果目标对象和它的父对象(如 QDialog)都安装了过滤器,目标对象自身的过滤器(或针对它的过滤器)优先于父对象的过滤器

例如:

// 父窗口 dialog 安装了过滤器(监视自身及子对象)
dialog->installEventFilter(dialogFilter);

// 子控件 edit 安装了自己的过滤器
edit->installEventFilter(editFilter);

此时,事件到达顺序为:editFilter->eventFilter()dialogFilter->eventFilter()edit 自身事件处理。

  • 子控件的过滤器先处理,若拦截,父窗口的过滤器和子控件自身均不会收到事件。

总结

  1. 同一目标的多个过滤器:后安装的过滤器优先级更高(先执行)。
  2. 局部过滤器 vs 全局过滤器:局部过滤器(针对单个对象)优先级高于全局过滤器(针对所有对象)。
  3. 子对象 vs 父对象过滤器:子对象的过滤器(或针对子对象的过滤器)优先级高于父对象的过滤器。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Forward♞

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

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

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

打赏作者

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

抵扣说明:

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

余额充值