本节讲解3个事件:

  • 鼠标按下事件(QEvent::MouseButtonPress
  • 鼠标移动事件(QEvent::MouseMove
  • 鼠标释放事件(QEvent::MouseButtonRelease

当鼠标在窗口/控件上按下、移动、释放时,会被触发。该事件对应的子类是QMouseEvent

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

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

本节实现效果,如下:
122.gif

1. 基本使用

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

1.1 鼠标按下、释放事件

首先,来到 labelx.h,声明并实现三个函数,如下:

// labelx.h
class LabelX : public QLabel {

protected:
    void mousePressEvent(QMouseEvent* ev);
    void mouseReleaseEvent(QMouseEvent* ev);
    void mouseMoveEvent(QMouseEvent* ev);
};

// labelx.cpp
#include <QMouseEvent>

void LabelX::mousePressEvent(QMouseEvent *ev)
{
    // qDebug() << "mousePressEvent: " << ev->button() << ev->position() << ev->globalPosition();
    if (ev->button() == Qt::LeftButton) {
        qDebug() << "左键按下: " << ev->position();
    }
}

void LabelX::mouseReleaseEvent(QMouseEvent *ev)
{
    // qDebug() << "mouseReleaseEvent: " << ev->button() << ev->position() << ev->globalPosition();
    if (ev->button() == Qt::LeftButton) {
        qDebug() << "左键释放: " << ev->position();
    }
}

void LabelX::mouseMoveEvent(QMouseEvent *ev)
{
}

然后,来到 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);

    LabelX* lblx = new LabelX();
    lblx->setFrameShape(QFrame::Box);
    lblx->setFixedHeight(50);
    lblx->setAlignment(Qt::AlignCenter);
    lblx->setStyleSheet("background-color: blue; color: white; font-size: 25px");
    verticalLayout->addWidget(lblx);
}

此时,运行程序:
image-20260820100613649

1.2 鼠标移动事件

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

void LabelX::mouseMoveEvent(QMouseEvent *ev)
{
    // 写法一:button() 错误×
    // qDebug() << "mouseMoveEvent: " << ev->button() << ev->position() << ev->globalPosition();

    // 写法二:buttons() 正确√
    qDebug() << "mouseMoveEvent: " << ev->buttons() << ev->position() << ev->globalPosition();
}

两种写法,控制台打印如下:

// 方法一:错误
// 我明明按下的是左键,但是打印的却是没有按键按下
mouseMoveEvent:  Qt::NoButton QPointF(5,25) QPointF(831,517)
mouseMoveEvent:  Qt::NoButton QPointF(6,25) QPointF(832,517)
mouseMoveEvent:  Qt::NoButton QPointF(7,25) QPointF(833,517)
mouseMoveEvent:  Qt::NoButton QPointF(8,25) QPointF(834,517)
mouseMoveEvent:  Qt::NoButton QPointF(9,25) QPointF(835,517)

// 方法二:正确
// 刚开始移动只按左键,移动过程中又按下了右键,也是可以识别到的
mouseMoveEvent:  QFlags<Qt::MouseButton>(LeftButton) QPointF(42,22) QPointF(868,514)
mouseMoveEvent:  QFlags<Qt::MouseButton>(LeftButton) QPointF(43,22) QPointF(869,514)
mouseMoveEvent:  QFlags<Qt::MouseButton>(LeftButton) QPointF(44,22) QPointF(870,514)
mouseMoveEvent:  QFlags<Qt::MouseButton>(LeftButton|RightButton) QPointF(45,22) QPointF(871,514)
mouseMoveEvent:  QFlags<Qt::MouseButton>(LeftButton|RightButton) QPointF(46,23) QPointF(872,515)
mouseMoveEvent:  QFlags<Qt::MouseButton>(LeftButton|RightButton) QPointF(47,23) QPointF(873,515)

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

void LabelX::mouseMoveEvent(QMouseEvent* ev)
{
    if (ev->buttons() & Qt::LeftButton) {
        qDebug() << "左键移动中: " << ev->position();
    }
}

这样,鼠标按下、移动、释放的过程中,控制台打印如下:

左键移动中:  QPointF(15,15)
左键移动中:  QPointF(17,15)
左键移动中:  QPointF(19,15)
左键移动中:  QPointF(21,16)
左键移动中:  QPointF(23,17)

1.3 鼠标跟踪

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

:鼠标悬浮在控件上,而不是按下,就触发mouseMoveEvent事件,这怎么实现呢?
:设置鼠标跟踪。默认情况下鼠标跟踪是关闭的,需要开启

只需来到labelx.cpp构造中,使能鼠标跟踪,并开启打印,如下:

LabelX::LabelX(QWidget* parent) : QLabel{parent}
{
    this->setMouseTracking(true);
}

void LabelX::mouseMoveEvent(QMouseEvent *ev)
{
    // 写法一:button() 错误×
    // qDebug() << "mouseMoveEvent: " << ev->button() << ev->position() << ev->globalPosition();

    // 写法二:buttons() 正确√
    qDebug() << "mouseMoveEvent: " << ev->buttons() << ev->position() << ev->globalPosition();

    if (ev->buttons() & Qt::LeftButton) {
        qDebug() << "左键移动中: " << ev->position();
    }
}

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

mouseMoveEvent:  QFlags<Qt::MouseButton>(NoButton) QPointF(16,0) QPointF(842,492)
mouseMoveEvent:  QFlags<Qt::MouseButton>(NoButton) QPointF(16,1) QPointF(842,493)
mouseMoveEvent:  QFlags<Qt::MouseButton>(NoButton) QPointF(16,2) QPointF(842,494)
mouseMoveEvent:  QFlags<Qt::MouseButton>(NoButton) QPointF(16,3) QPointF(842,495)
mouseMoveEvent:  QFlags<Qt::MouseButton>(NoButton) QPointF(17,4) QPointF(843,496)

2. 案例:鼠标移动标签

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

2.1 添加标签控件

修改PressMoveReleaseWidget类,声明控件并添加到布局中,如下:

// press_move_release_widget.h
#include <QLabel>

class PressMoveReleaseWidget : public QWidget {
private:
    QLabel* lbl;
    QWidget* widget;
};

// press_move_release_widget.cpp
PressMoveReleaseWidget::PressMoveReleaseWidget(QWidget* parent) : QWidget{parent}
{
    // ...

    // 2. 添加QLabel
    // 在 QLable 外边套一层 QWidget,是为了让标签在这个 widget 范围内移动
    widget = new QWidget(this);
    lbl = new QLabel(widget);
    lbl->setFrameShape(QFrame::Box);
    lbl->setFixedSize(100, 100);
    lbl->setStyleSheet("background-color: red;");
    verticalLayout->addWidget(widget);
}

此时,运行效果:
image-20260820100650337

2.2 实现事件过滤函数

首先,来到press_move_release_widget.cpp构造中,为lbl安装事件过滤器,如下:

PressMoveReleaseWidget::PressMoveReleaseWidget(QWidget* parent) : QWidget{parent}
{
    // ...
    
    lbl->installEventFilter(this);
}

然后,来到press_move_release_widget.h中,声明eventFilter()函数,如下:

class PressMoveReleaseWidget : public QWidget {
protected:
    bool eventFilter(QObject* watched, QEvent* event);

private:
    QPoint offset;  // 鼠标按下时, 鼠标与标签左上角的偏移
};

最后,来到press_move_release_widget.cpp中,实现eventFilter()函数,如下:

#include <QEvent>
#include <QMouseEvent>
#include <QDebug>

bool PressMoveReleaseWidget::eventFilter(QObject* watched, QEvent* event)
{
    if (watched == lbl) {
        if (event->type() == QEvent::MouseButtonPress) {
            qDebug() << "MouseButtonPress";
            QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
            offset = mouseEvent->globalPos() - lbl->pos();
        } else if (event->type() == QEvent::MouseMove) {
            qDebug() << "MouseMove";
            QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
            QPoint dstPos = mouseEvent->globalPos() - offset;

            // 超出了最左边
            if (dstPos.x() < 0) {
                dstPos.setX(0);
            }
            // 超出了最右边
            if (dstPos.x() + lbl->width() > widget->width()) {
                dstPos.setX(widget->width() - lbl->width());
            }
            // 超出了最上边
            if (dstPos.y() < 0) {
                dstPos.setY(0);
            }
            // 超出了最下边
            if (dstPos.y() + lbl->height() > widget->height()) {
                dstPos.setY(widget->height() - lbl->height());
            }

            lbl->move(dstPos);
        } else if (event->type() == QEvent::MouseButtonRelease) {
            qDebug() << "MouseButtonRelease";
        }
    }

    return QWidget::eventFilter(watched, event);
}

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