本章要实现的整体效果如下:
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);
};
然后,来到 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)
{
}
最后,来到 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 鼠标移动事件
鼠标移动,与鼠标按下和释放,在判断按键时有些许不同
如果 mouseMoveEvent
实现如下:
void LabelX::mouseMoveEvent(QMouseEvent* ev)
{
qDebug() << "mouseMoveEvent: " << ev->button() << ev->pos() << ev->globalPos();
}
运行结果如下:
我明明按下的是左键,但是打印的却是没有按键按下
因为,此时不能使用 ev->button()
,而是要使用 ev->buttons()
,如下:
void LabelX::mouseMoveEvent(QMouseEvent* ev)
{
// 而是要用buttons()方法
qDebug() << "mouseMoveEvent: " << ev->buttons() << ev->pos() << ev->globalPos();
}
此时,就可以正确打印了,如下:
可见,刚开始移动只按左键,移动过程中又按下了右键,也是可以识别到的。
在移动过程中,判断有左键按下的代码,如下:
void LabelX::mouseMoveEvent(QMouseEvent* ev)
{
if ( ev->buttons() & Qt::LeftButton ) {
qDebug() << "左键移动中: " << "x=" << ev->x() << ", y=" << ev->y();
}
}
这样,鼠标按下、移动、释放的整体效果,如下:
1.3 鼠标跟踪
以上,需要鼠标保持按下的状态下,系统才会调用 mouseMoveEvent
,实际工作中往往有这么一种需求:
鼠标悬浮在控件上,而不是按下,就触发 mouseMoveEvent
事件,这怎么实现呢?
答案:设置鼠标跟踪,默认情况下鼠标跟踪是关闭的,需要开启
首先,来到 labelx.cpp
中,设置标签使能鼠标跟踪,如下:
LabelX::LabelX(QWidget* parent) : QLabel{parent}
{
this->setMouseTracking(true);
}
然后,在 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();
}
}
此时,在标签上悬浮移动时,也可以跟踪到鼠标,如下:
2. 鼠标事件移动标签
接下来,实现一个小案例:拖动标签来移动标签的位置
2.1 界面上添加标签
首先,在 press_move_release_widget.h
中添加成员变量:
#include <QLabel>
class PressMoveReleaseWidget : public QWidget
{
private:
QLabel* lbl;
QWidget* widget;
};
在 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);
}
此时,运行效果如下:
2.2 为 QLabel 安装事件过滤器
PressMoveReleaseWidget::PressMoveReleaseWidget(QWidget* parent) : QWidget{parent}
{
// ...
lbl->installEventFilter(this);
}
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;
};
然后,在 press_move_release_widget.cpp
文件中实现该函数,如下:
#include <QEvent>
#include <QMouseEvent>
#include <QDebug>
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";
}
}
这里有些实现细节,说明如下:
- 如果不是 lbl 的事件,直接调用父类处理
return QWidget::eventFilter(watched, event)
- 在鼠标按下时,记录
lbl
的位置和鼠标按下位置,作为窗口移动时的参考 - 当
lbl
超出 widget 边界时,让它等于边界值
此时,就可以通过鼠标拖动标签,在 widget
范围内移动了,如下: