• 【QT开发笔记-基础篇】| 第四章 事件QEvent | 4.4 鼠标按下、移动、释放事件


    本章要实现的整体效果如下:

    整体效果

    QEvent::MouseButtonPress

    ​ 鼠标按下时,触发该事件,它对应的子类是 QMouseEvent

    QEvent::MouseMove

    鼠标移动时,触发该事件,它对应的子类是 QMouseEvent

    QEvent::MouseButtonRelease

    ​ 鼠标释放时,触发该事件,它对应的子类是 QMouseEvent


    本节通过两个案例来讲解这 3 个事件:

    • 按下、移动、释放事件的基本使用
    • 拖动一个标签,使之移动位置

    1. 按下、移动、释放事件的基本使用

    同样使用上一节自定义的标签 LabelX,来进行讲解

    1.1 鼠标按下、释放事件

    首先,来到 labelx.h,声明这 3 个函数:

    class LabelX : public QLabel
    {
    protected:
        void mousePressEvent(QMouseEvent* ev);
        void mouseReleaseEvent(QMouseEvent* ev);
        void mouseMoveEvent(QMouseEvent* ev);
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    然后,来到 labelx.cpp 实现这 3 个函数:

    void LabelX::mousePressEvent(QMouseEvent* ev)
    {
        // qDebug() << "mousePressEvent: " << ev->button() << ev->pos() << ev->globalPos();
        if ( ev->button() == Qt::LeftButton ) {
            qDebug() << "左键按下: " << "x=" << ev->x() << ", y=" << ev->y();
        }
    }
    
    void LabelX::mouseReleaseEvent(QMouseEvent* ev)
    {
        // qDebug() << "mouseReleaseEvent: " << ev->button() << ev->pos() << ev->globalPos();
        if ( ev->button() == Qt::LeftButton ) {
            qDebug() << "左键释放: " << "x=" << ev->x() << ", y=" << ev->y();
        }
    }
    
    void LabelX::mouseMoveEvent(QMouseEvent* ev)
    {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    最后,来到 press_move_release_widget.cpp,在构造函数中添加 LabelX 控件,如下:

    #include "labelx.h"
    
    PressMoveReleaseWidget::PressMoveReleaseWidget(QWidget* parent) : QWidget{parent}
    {
        QVBoxLayout* verticalLayout = new QVBoxLayout(this);
        verticalLayout->setSpacing(0);
        verticalLayout->setContentsMargins(0, 0, 0, 0);
    
        // 1. 添加一个自定义的标签 LabelX
        LabelX* lblX = new LabelX(this);
        lblX->setText("");
        lblX->setFrameShape(QFrame::Box);
        lblX->setFixedHeight(50);
        lblX->setAlignment(Qt::AlignCenter);
        lblX->setStyleSheet("background-color: blue;color: white;font-size: 25px");
        verticalLayout->addWidget(lblX);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    此时运行程序,在标签上点击时,就会在控制台打印按下还是释放,并显示点击的位置:

    image-20230911094301640


    1.2 鼠标移动事件

    鼠标移动,与鼠标按下和释放,在判断按键时有些许不同

    如果 mouseMoveEvent 实现如下:

    void LabelX::mouseMoveEvent(QMouseEvent* ev)
    {
        qDebug() << "mouseMoveEvent: " << ev->button() << ev->pos() << ev->globalPos();
    }
    
    • 1
    • 2
    • 3
    • 4

    运行结果如下:

    image-20230911095642358

    我明明按下的是左键,但是打印的却是没有按键按下

    因为,此时不能使用 ev->button(),而是要使用 ev->buttons(),如下:

    void LabelX::mouseMoveEvent(QMouseEvent* ev)
    {
        // 而是要用buttons()方法
        qDebug() << "mouseMoveEvent: " << ev->buttons() << ev->pos() << ev->globalPos();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    此时,就可以正确打印了,如下:

    image-20230911095838664

    可见,刚开始移动只按左键,移动过程中又按下了右键,也是可以识别到的。

    在移动过程中,判断有左键按下的代码,如下:

    void LabelX::mouseMoveEvent(QMouseEvent* ev)
    {
        if ( ev->buttons() & Qt::LeftButton ) {
            qDebug() << "左键移动中: " << "x=" << ev->x() << ", y=" << ev->y();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这样,鼠标按下、移动、释放的整体效果,如下:

    image-20230911100344699


    1.3 鼠标跟踪

    以上,需要鼠标保持按下的状态下,系统才会调用 mouseMoveEvent,实际工作中往往有这么一种需求:

    鼠标悬浮在控件上,而不是按下,就触发 mouseMoveEvent 事件,这怎么实现呢?

    答案:设置鼠标跟踪,默认情况下鼠标跟踪是关闭的,需要开启


    首先,来到 labelx.cpp 中,设置标签使能鼠标跟踪,如下:

    LabelX::LabelX(QWidget* parent) : QLabel{parent}
    {
        this->setMouseTracking(true);
    }
    
    • 1
    • 2
    • 3
    • 4

    然后,在 mouseMoveEvent 中添加打印,如下:

    void LabelX::mouseMoveEvent(QMouseEvent* ev)
    {
        qDebug() << "mouseMoveEvent: " << ev->buttons() << ev->pos() << ev->globalPos();
        if ( ev->buttons() & Qt::LeftButton ) {
            qDebug() << "左键移动中: "
                     << "x=" << ev->x() << ", y=" << ev->y();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    此时,在标签上悬浮移动时,也可以跟踪到鼠标,如下:

    image-20230911101622483


    2. 鼠标事件移动标签

    接下来,实现一个小案例:拖动标签来移动标签的位置

    2.1 界面上添加标签

    首先,在 press_move_release_widget.h 中添加成员变量:

    #include 
    
    class PressMoveReleaseWidget : public QWidget
    {
    private:
        QLabel* lbl;
        QWidget* widget;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    QLable 外边套一层 QWidget,是为了让标签在这个 widget 范围内移动


    然后,在 press_move_release_widget.cpp 的构造中添加一个标签:

    PressMoveReleaseWidget::PressMoveReleaseWidget(QWidget* parent) : QWidget{parent}
    {
    	// ...
        
        // 2. 添加一个 QLabel
        widget = new QWidget(this);
        lbl = new QLabel(widget);
        lbl->setText("");
        lbl->setFrameShape(QFrame::Box);
        lbl->setFixedSize(100, 100);
        lbl->setStyleSheet("background-color: red;");
        verticalLayout->addWidget(widget);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    此时,运行效果如下:

    image-20230911102727749


    2.2 为 QLabel 安装事件过滤器

    PressMoveReleaseWidget::PressMoveReleaseWidget(QWidget* parent) : QWidget{parent}
    {
       	// ...
        lbl->installEventFilter(this);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.3 重写 eventFilter() 函数

    重写当前窗口的 eventFilter() 函数

    首先,在 press_move_release_widget.h 文件中声明该函数,

    同时声明记录窗口位置和鼠标按下位置的变量,如下:

    class PressMoveReleaseWidget : public QWidget
    {
    protected:
        bool eventFilter(QObject* watched, QEvent* event);
        
    private:
        QPoint pressPos;
        QPoint wndPos;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    然后,在 press_move_release_widget.cpp 文件中实现该函数,如下:

    #include 
    #include 
    #include 
    bool PressMoveReleaseWidget::eventFilter(QObject* watched, QEvent* event)
    {
        if ( watched != lbl ) {
            return QWidget::eventFilter(watched, event);
        }
    
        if ( event->type() == QEvent::MouseButtonPress ) {
            qDebug() << "MouseButtonPress";
            QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
            pressPos = mouseEvent->globalPos();
            wndPos = lbl->pos();
            qDebug() << wndPos;
        } else if ( event->type() == QEvent::MouseMove ) {
            qDebug() << "MouseMove";
            QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
            QPoint dstPos = wndPos + (mouseEvent->globalPos() - pressPos);
            lbl->move(dstPos);
            // 超出了最左边
            if ( lbl->pos().x() < 0 ) {
                lbl->move(0, dstPos.y());
            }
            // 超出了最右边
            if ( lbl->pos().x() > widget->width() - lbl->width() ) {
                lbl->move(widget->width() - lbl->width(), dstPos.y());
            }
            // 超出了最上边
            if ( lbl->pos().y() < 0 ) {
                lbl->move(dstPos.x(), 0);
            }
            // 超出了最下边
            if ( lbl->pos().y() > widget->height() - lbl->height() ) {
                lbl->move(dstPos.x(), widget->height() - lbl->height());
            }
        } else if ( event->type() == QEvent::MouseButtonRelease ) {
            qDebug() << "MouseButtonRelease";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    这里有些实现细节,说明如下:

    • 如果不是 lbl 的事件,直接调用父类处理 return QWidget::eventFilter(watched, event)
    • 在鼠标按下时,记录 lbl 的位置和鼠标按下位置,作为窗口移动时的参考
    • lbl 超出 widget 边界时,让它等于边界值

    此时,就可以通过鼠标拖动标签,在 widget 范围内移动了,如下:

    mousemove

  • 相关阅读:
    html写一个table表
    【网络协议】OSPF
    【2022秋招面经】——测试
    动环监控系统的主要功能,动环监控系统的监控对象有哪些
    vue3 使用 mitt 插件实现非父子组件传值
    com.lowagie:itext:jar:2.1.7.js9 was not found
    数据结构与算法_哈希表_线性探测法原理和代码实现
    Harmony import和export
    SOLIDWORKS工程图BOM表子装配体显示控制
    CICD——gitea+drone部署
  • 原文地址:https://blog.csdn.net/bili_mingwang/article/details/133606976