『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, 当数值超过时将会拉回最值, 以完成界限的控制;

运行结果为:

相关推荐
用户805533698034 天前
不止三件套:QObject 属性系统全关键字与运行时反射!
c++·qt
xcyxiner4 天前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz9 天前
QML Hello World 入门示例
qt
xcyxiner12 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner13 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner13 天前
DicomViewer (添加模型类)3
qt
xcyxiner14 天前
DicomViewer (目录调整) 2
qt
xcyxiner14 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00616 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术16 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript