『QT』事件处理机制详解 (一)

个人博客地址

|----------------------------------------|
| 个人博客: 花开富贵 |

文章目录

  • 个人博客地址
    • [1 概述](#1 概述)
    • [2 事件处理](#2 事件处理)
    • [3 鼠标事件](#3 鼠标事件)
      • [3.1 处理鼠标点(单)击事件](#3.1 处理鼠标点(单)击事件)
      • [3.2 鼠标释放事件](#3.2 鼠标释放事件)
      • [3.3 鼠标双击事件](#3.3 鼠标双击事件)
      • [3.4 鼠标移动事件](#3.4 鼠标移动事件)
      • [3.5 滚轮事件](#3.5 滚轮事件)

1 概述

事件是应用程序内部或者外部产生的事件或动作的简称;

这个概念与信号和槽类似, 关于信号和槽而言, 我们将信号和对应的槽通过connect进行连接, 连接后档某个控件对象发出信号后, 将会调用所绑定的槽函数, 从而进行对应的处理动作;

事件也是类似, 但事件与信号的概念区别于粒度, 事件的粒度要小于事件的粒度, 简单来说, 当一个按钮控件被按下时将会发出Clicked()信号, 而事件作为更细粒度的概念, 当我们在进行鼠标移动, 窗口挪动或是窗口大小的变动, 都会产生对应的事件, 而在Qt中, 可以对某个控件Widget进行事件的重写从而完善一个应用的多功能;

换句话来说, 实际上信号也是事件的一种体现, 信号通过封装事件, 如判断一个点击事件在一个QPushButton控件中产生, 因此发出对应的信号;

cpp 复制代码
// 伪代码:QPushButton 的内部逻辑

// 1. 系统检测到鼠标按下,发送 QMouseEvent
void QPushButton::mousePressEvent(QMouseEvent *e) {
    // 记录下:按钮被按下了
    this->isPressed = true;
    
    // 调用父类处理,保证默认外观变化(比如按钮凹下去)
    QWidget::mousePressEvent(e); 
}

// 2. 系统检测到鼠标抬起,发送 QMouseEvent
void QPushButton::mouseReleaseEvent(QMouseEvent *e) {
    // 逻辑判断:
    // 如果之前被按下了,且鼠标抬起的位置还在按钮范围内
    if (this->isPressed && this->rect().contains(e->pos())) {
        
        // 【关键点】:在这里,事件被转化为了信号!
        emit clicked(); 
    }
    
    this->isPressed = false;
    QWidget::mouseReleaseEvent(e);
}

换个角度可以这么理解, 信号是桌上的饺子, 而事件是饺子的原材料(面粉, 水, 馅...), 只是有时候我们常吃的是饺子, 但除了饺子以外, 我们还希望可能吃到包子馒头面条面包等食物, 而Qt不一定提供这些成品, 因此Qt提供了一系列的接口让我们可以通过组合原材料生成自定义的成品;

Qt中, 所有的事件基于QEvent类进行派生, 常见的事件有:

  • QMouseEvent - 鼠标事件

    当鼠标在控件内进行按下, 释放, 移动或者双击时将会触发;

    常见于获取鼠标点击的坐标(x, y), 判断是左键还是右键;

  • QKeyEvent - 键盘事件

    当键盘按下或释放时触发;

    常见于捕获快捷键, 如Ctrl+C, 或者控制角色移动的W/A/S/D;

  • QTimerEvent - 定时器事件

    当一个由startTimer()启动的定时器时间达到间隔时触发;

    常见于周期性任务, 如每秒刷新一次时钟显示;

  • QDropEvent - 拖放结束事件

    属于拖放机制的一部分, 当拖拽操作进入控件并在内松开鼠标左键时触发;

    常见于处理被拖动的文件, 或处理被拖入的数据;

  • QInputEvent - 输入事件基类

    这是一个抽象类, 不是具体的某个动作, 是QMouseEvent, QKeyEvent, QTouchEvent等的父类;

    通常不直接进行处理, 在某些通用处理时, 可以用来检查通用的修饰键状态, 如(Shift, Ctrl)是否被按下;

  • QPaintEvent - 绘图事件

    当控件需要重新绘制自身外观时触发, 如窗口刚显示, 窗口大小改变, 被遮挡后重新露出, 或是手动调用update()/repaint();

    画图时必须在该事件处理函数中使用QPainter;

除了这些事件以外, 还有:

事件 描述
鼠标事件 鼠标左键、鼠标右键、鼠标滚轮,鼠标的移动,鼠标按键的按下和松开
键盘事件 按键类型、按键按下、按键松开
定时事件 定时时间到达
进入离开事件 鼠标的进入和离开
滚轮事件 鼠标滚轮滚动
绘屏事件 重绘屏幕的某些部分
显示隐藏事件 窗口的显示和隐藏
移动事件 窗口位置的变化
窗口事件 是否为当前窗口
大小改变事件 窗口大小改变
焦点事件 键盘焦点移动
拖拽事件 用鼠标进行拖拽

2 事件处理

事件处理本质上就是将一个事件与处理函数进行关联, 与信号类似, 当一个事件触发后, 调用制定的函数调用;

但对于事件处理而言, 在Qt中针对事件的处理大多是重写Qt所给的事件的虚函数;

以鼠标的进入和离开事件为例, 鼠标的进入事件为enterEvent, 离开事件为leaveEvent;

而在进行重写事件时也表示, 我们需要继承哪一个基类, 此次我们基于QLabel写一个派生类Label;

cpp 复制代码
/*label.h*/
class Label : public QLabel
{
    Q_OBJECT
public:
    Label(QWidget* parent = nullptr);

    void enterEvent(QEnterEvent* event); // 需要注意类型
    void leaveEvent(QEvent* event);
};
/*label.cpp*/
Label::Label(QWidget* parent):QLabel(parent) {}

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

void Label::leaveEvent(QEvent *event)
{
    (void)event;
    qDebug()<<"leaveEvent";
}
/*mainwidow.cpp*/
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    /* 创建核心控件并设置 */
    QWidget* centerWidget = new QWidget(this);
    this->setCentralWidget(centerWidget);

    /* 创建布局管理器 */
    QHBoxLayout* hlayout = new QHBoxLayout(centerWidget);
    centerWidget->setLayout(hlayout); // 设置布局管理器进核心控件

    /* 创建自定义Label */
    Label* _label = new Label();
    _label->setFrameShape(QLabel::Box); // 设置外框线
    _label->setText("This is a My Label"); // 设置文本

    hlayout->addWidget(_label); // 将Label设置进布局管理器
}

当鼠标进入时, 打印进入信息 "enterEvent", 离开时打印 "leaveEvent" ;

运行结果为:

除此之外, 也可以使用QtDesigner, 在设计模式中拖进基类, 并 "提升于" 对应的派生类;

此处忽略逻辑, 运行结果与上Gif相同;


3 鼠标事件

在上文的介绍中, 介绍了鼠标的进入与离开事件, 此处针对进入与离开的事件就不进行赘述, 除了进入与离开事件以外, 鼠标还有一系列的其他事件, 包括但不限于, 点击, 释放, 双击;

此处重点介绍点击, 释放, 双击, 滚轮以及移动事件;


3.1 处理鼠标点(单)击事件

这里主要是获取鼠标的点击事件, 我们在上文提到, 实际在鼠标点击时同样会触发对应的事件;

cpp 复制代码
[virtual protected] void QWidget::mousePressEvent(QMouseEvent *event);

鼠标点击事件通常依靠mousePressEvent(QMouseEvent*), 其中参数QMouseEvent中将包含各类信息, 包括鼠标所点击的位置信息, 因此可以通过这些信息实现, 当鼠标点击时获取对应的坐标位置;

cpp 复制代码
/* label.cpp */
void Label::mousePressEvent(QMouseEvent *event)
{
    qDebug()<<"X: "<<event->x()<<" Y: "<<event->y();
    qDebug()<<"globalX: "<<event->globalX()<<" globalY: "<<event->y();
    qDebug()<<"|| ######################################### ||";
}
/* mainwindow.cpp */
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    QWidget*  centerWidget = new QWidget(this);
    this->setCentralWidget(centerWidget);

    QHBoxLayout* hLayout = new QHBoxLayout(centerWidget);
    centerWidget->setLayout(hLayout);
    hLayout->setContentsMargins(50,50,50,50);

    Label* label = new Label();
    label->setText("Click here to get X and Y");
    label->setAlignment(Qt::AlignCenter); // 字体居中
    hLayout->addWidget(label);
}

运行结果为:

可以看到, 获取到的不仅是基于当前控件的坐标, 还能获取基于全局控件(主控件)的位置;

除此之外, 该事件并不是只能触发左键, 能触发该事件的鼠标键包括, 左右键, 滚轮键, 或者一些前后键;

本质来说, 当鼠标上的有效键被点击时, 都将触发该事件;

当然, 在这个event对象中, 也包含着不同按键的属性, 可以通过判断对应的按键来判断是什么按键进行了点击;

cpp 复制代码
void Label::mousePressEvent(QMouseEvent *event)
{
    qDebug()<<"|| ######################################### ||";
    switch (event->button()) {
    case Qt::LeftButton:
        qDebug()<<"左键";
        break;
    case Qt::RightButton:
        qDebug()<<"右键";
        break;
    case Qt::MiddleButton:
        qDebug()<<"滚轮键";
        break;
    case Qt::BackButton:
        qDebug()<<"后退键";
        break;
    case Qt::ForwardButton:
        qDebug()<<"前进键";
        break;
    default:
        qDebug()<<"其他键";
        break;
    }
    qDebug()<<"X: "<<event->x()<<" Y: "<<event->y();
    qDebug()<<"globalX: "<<event->globalX()<<" globalY: "<<event->y();
    qDebug()<<"|| ######################################### ||";
}

运行结果为:


3.2 鼠标释放事件

当鼠标按下后, 将会触发点击事件, 除此之外, 当鼠标从点击状态松开, 将同样会触发一个事件, 这个事件为释放事件, 即Release;

对应的虚函数为:

cpp 复制代码
[virtual protected] void QWidget::mouseReleaseEvent(QMouseEvent *event) override;

同样的, 鼠标释放时, 将会返回一个QMouseEvent对象指针, 这个对象中, 同样给出了获得了释放鼠标的键属性, 可以采用event->button()的方式进行获取;

cpp 复制代码
Label::Label(QWidget* parent):QLabel(parent) {
    this->setFrameShape(QLabel::Box);
}

void Label::mousePressEvent(QMouseEvent *event)
{
    switch (event->button()) {
    case Qt::LeftButton:
        qDebug()<<"左键";
        break;
    case Qt::RightButton:
        qDebug()<<"右键";

        break;
    case Qt::MiddleButton:
        qDebug()<<"滚轮键";

        break;
    case Qt::BackButton:
        qDebug()<<"后退键";

        break;
    case Qt::ForwardButton:
        qDebug()<<"前进键";
        break;
    default:
        qDebug()<<"其他键";
        break;
    }
}

void Label::mouseReleaseEvent(QMouseEvent *event)
{
    switch (event->button()) {
    case Qt::LeftButton:
        qDebug()<<"左键释放";
        break;
    case Qt::RightButton:
        qDebug()<<"右键释放";
        break;
    case Qt::MiddleButton:
        qDebug()<<"滚轮键释放";
        break;
    case Qt::BackButton:
        qDebug()<<"后退键释放";
        break;
    case Qt::ForwardButton:
        qDebug()<<"前进键释放";
        break;
    default:
        qDebug()<<"其他键";
        break;
    }
}

运行结果为:

实际上, 一个Clicked信号就是由一个mousePreeeEvent事件与一个mouseReleaseEvent事件共同组成的;


3.3 鼠标双击事件

鼠标双击事件是通过虚函数mouseDoubleClickEvent()实现的;

cpp 复制代码
[virtual protected] void QWidget::mouseDoubleClickEvent(QMouseEvent *event);

通常情况下, 在进行双击操作时, 可能会误操作单击, 因此此处将单击与释放部分进行注释;

cpp 复制代码
void Label::mouseDoubleClickEvent(QMouseEvent *event){
    switch (event->button()) {
    case Qt::LeftButton:
        qDebug()<<"左键双击";
        break;
    case Qt::RightButton:
        qDebug()<<"右键双击";
        break;
    case Qt::MiddleButton:
        qDebug()<<"滚轮键双击";
        break;
    case Qt::BackButton:
        qDebug()<<"后退键双击";
        break;
    case Qt::ForwardButton:
        qDebug()<<"前进键双击";
        break;
    default:
        qDebug()<<"其他键";
        break;
    }
}

运行结果为:


3.4 鼠标移动事件

鼠标移动事件对应的虚函数为:

cpp 复制代码
[virtual protected] void QWidget::mouseMoveEvent(QMouseEvent *event);

此次基于QWidget类派生一个Widget类, 并在Widget中重写该函数;

cpp 复制代码
/*widgeht.cpp*/
Widget::Widget(QWidget *parent)
    : QWidget{parent}
{}
void Widget::mouseMoveEvent(QMouseEvent *event)
{
    qDebug()<<"x: "<<event->x()<<"  y: "<<event->y();
}
/*mainwindow.cpp*/
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    Widget* centerWidget = new Widget(this);
    this->setCentralWidget(centerWidget);
}

运行结果为:

可以看到运行结果;

但这里有一个问题, 这里的鼠标是在按下后才进行移动跟踪, 本身并没有进行移动跟踪, 本质上是因为通常情况下, 鼠标每当移动都会进行大量的事件发生, 因此为了减缓CPU的负担, 因此通常情况下, 对应的默认鼠标跟踪并不会被打开, 当需要跟踪时进行点击(点击时跟踪);

但并不是完全不能进行停留跟踪, 当需要停留跟踪时, 需要手动将mouseTracking属性设置为True;

cpp 复制代码
MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    Widget* centerWidget = new Widget(this);
    this->setCentralWidget(centerWidget);
    centerWidget->setMouseTracking(true); // 设置鼠标跟踪
}

运行结果为:


3.5 滚轮事件

滚轮事件通过wheelEvent产生;

cpp 复制代码
[virtual protected] void QWidget::wheelEvent(QWheelEvent *event);

3.4中对应的Widget中实现该功能:

cpp 复制代码
/*widget.h*/
class Widget : public QWidget
{
    Q_OBJECT
public:
    explicit Widget(QWidget *parent = nullptr);
    void mouseMoveEvent(QMouseEvent* event) override;
    void wheelEvent(QWheelEvent *event) override;
signals:
private:
    int cur=0;
};
/*widget.cpp*/
void Widget::wheelEvent(QWheelEvent *event)
{
    cur = cur+event->angleDelta().y();
    if (cur > 1200) {
        cur = 1200;  // 超过上限,强制拉回 1200
    }
    else if (cur < -1200) {
        cur = -1200; // 低于下限,强制拉回 -1200
    }
    qDebug()<<cur;
}

当滚轮在进行上下滚动时, 将会触发对应的事件, 事件记录每次操作的值;

而在这段代码中, 涉及了一个滚轮的值, 默认为0, 并设置上限与下限位±1200, 当数值超过时将会拉回最值, 以完成界限的控制;

运行结果为:

相关推荐
R_.L37 分钟前
【QT】常用控件(概述、QWidget核心属性)
qt·控件
烧酒同学38 分钟前
【Qt】QScrollArea的滑动条无法拖动(已解决)
qt·bug
孤独斗士41 分钟前
maven的pom文件总结
java·开发语言
confiself43 分钟前
通义灵码分析ms-swift框架中CHORD算法实现
开发语言·算法·swift
1024小神44 分钟前
在 Swift 中,self. 的使用遵循明确的规则
开发语言·ios·swift
Swift社区1 小时前
Swift 类型系统升级:当协议遇上不可拷贝的类型
开发语言·ios·swift
chengpei1471 小时前
I²C协议简介
c语言·开发语言
唐古乌梁海1 小时前
【IT】常见计算机编程语言多继承问题
开发语言
雨中散步撒哈拉1 小时前
18、做中学 | 初升高 | 考场一 | 面向过程-家庭收支记账软件
开发语言·后端·golang