QT的使用3:鼠标事件

0 事件

用户对界面中的控件进行操作后,控件需要进行响应,响应一般有两种方式,一种是之前学过的槽函数,另一种是事件,部分操作无法通过槽函数响应,只能通过事件,例如鼠标移动,键盘输入等。
当某个事件(鼠标、键盘)发生的时候,相关控件就会收到这个事件,并且调用相应的事件处理函数进行相应,事件处理函数的命名都是以Event结尾的。

1 需求

在窗口内的任意位置点击,然后窗口会有一个标签(使用QLabel)显示鼠标所在位置的坐标:随着鼠标在窗口内不同位置进行点击,标签显示的坐标也在变换。效果如下图所示:
在这里插入图片描述

2 查看控件的事件处理函数

在帮助界面搜索QWidget,然后点击Protected Functions
在这里插入图片描述
可以看到很多以“Event”结尾的函数,这些函数的参数都是各个事件,并且这些函数都使用了virtual修饰,即他们都是虚函数,继承时表现为多态特性。
在这里插入图片描述
我们可以往下滑,找到鼠标事件
在这里插入图片描述
截图中红色方框框出来的几个分别为:鼠标单击、鼠标移动、鼠标点击、鼠标释放,说明这个控件可以对这四种鼠标事件进行响应,它们的参数均为鼠标事件指针(QMouseEvent *)。

3 UI设计

打开QT设计师,在Display Widgets中选择Label,将其拖到窗口中:在这里插入图片描述
给主窗口来个布局
在这里插入图片描述
右下角可以看到类的继承层次结构,我们刚刚拖到窗口中的控件,类型是QLabel
在QFrame的frameShape属性中选择Box
在这里插入图片描述
窗口中会出现黑色的框线
在这里插入图片描述

4 新建一个类,继承QLabel

设计好UI后,之所以还要新建类去继承已有类,是因为原先的事件处理函数无法满足需求,我们需要重写相应的事件处理函数(这部分稍后会说)。
由于新建类的时候,父类选项没有QLabel,所以这里新建类的时候先选择继承QWidget(也可以选其他),产生代码后手动改父类
在这里插入图片描述
打开新建类的头文件,将父类由QWidget改成QLabel
在这里插入图片描述
对应的cpp文件也要改,在构造函数的初始化列表中,将父类改过来
在这里插入图片描述

5 对已有对象进行类型提升

在UI设计界面的对象树中,右击我们刚刚在UI设计阶段,拖到窗口中的空间(QLabel对象),然后点击“提升为”
在这里插入图片描述
在弹出的对话框中,填写提升的类名称,然后点击添加
在这里插入图片描述
再次填写提升的类名称,然后点击提升
在这里插入图片描述
此时可以看到,我们刚刚拖到窗口中的控件,类型变成了MyLabel
在这里插入图片描述
然后Ctrl+R,看看有没有错。

6 重写事件处理函数

MyLabel.h内容如下:

#ifndef MYLABEL_H
#define MYLABEL_H

#include <QWidget>
#include <QLabel>

class MyLabel : public QLabel
{
    Q_OBJECT
public:
    explicit MyLabel(QWidget *parent = nullptr);

signals:

public:
    void mousePressEvent(QMouseEvent *event);
};

#endif // MYLABEL_H

输入完函数声明后,按下alt+回车,会显示提示消息,然后选择“在mylabel.cpp添加定义”,就能在mylabel.cpp中自动生成函数头,十分方便。
在这里插入图片描述

MyLabel.cpp内容如下:

#include "mylabel.h"
#include "QMouseEvent"

MyLabel::MyLabel(QWidget *parent) : QLabel(parent)
{

}

void MyLabel::mousePressEvent(QMouseEvent *event)
{
    //获取鼠标事件的一些信息
    //获取坐标
    int x = event->x();
    int y = event->y();

    //格式化显示坐标,并居中
    QString str = QString("<h1><center>[%1, %2]</center></h1>").arg(x).arg(y);
    this->setText(str);
}

这里QString str = QString("<h1><center>[%1, %2]</center></h1>").arg(x).arg(y);是将字符串进行格式化匹配,QString中是可以使用html语法的,随后的this->setText(str)是输出指定的字符串。

现在我们按下Ctrl+R运行,然后点击窗口中的任意一点,然后就能看到坐标了
在这里插入图片描述
至此,大功告成。

我们这个项目的结构如下图所示:
在这里插入图片描述
项目中的main.cpp、widget.h和widget.cpp都没有修改,这几个文件一般很少去动,除非要和后端进行联动。

7 项目进一步拓展

(1)获取鼠标按键

在mylabel.cpp中输入event->button();,然后移动光标到函数上,按两遍F1(笔记本电脑可能和某些屏幕、声音设置的快捷键冲突,这种情况下需要按住键盘左下角的fn键,然后按两遍F1)
在这里插入图片描述
可以看到这个函数的返回值类型是Buttons,点击QT::MouseButton
在这里插入图片描述
QT 5.14.2的帮助文档中,button()返回值是Qt3DInput::QMouseEvent::Buttons类型,而用这个类来定义event->button的返回值却报错,我不知道怎么回事,所以上面使用的是QT 5.9文档内容。也就是说,5.14.2并不是稳定版本,编译器和帮助文档有冲突,我们以后查看返回值类型的时候,用ctrl,然后鼠标点击要查看的函数,找到源码查看返回值类型,然后再调用帮助文档。

可以看到QT::MouseButton枚举类型(一般Qt后面跟两个冒号的,都是枚举类型),红框框出来的为需要掌握的键,分别是:未按下、任意键、左键、右键、中键(滚轮)、中键,最后两个含义相同。
在这里插入图片描述
将mylabel.cpp的代码修改如下:

#include "mylabel.h"
#include "QMouseEvent"

MyLabel::MyLabel(QWidget *parent) : QLabel(parent)
{

}

void MyLabel::mousePressEvent(QMouseEvent *event)
{
    //获取鼠标事件的一些信息
    //获取坐标
    int x = event->x();
    int y = event->y();

    //获取鼠标按键
    Qt::MouseButton btn = event->button();
    QString strButton ="" ;
    if(btn == Qt::LeftButton)
    {
        strButton = "LeftButton";
    }
    if(btn == Qt::RightButton)
    {
        strButton = "RightButton";
    }
    if(btn == Qt::MidButton)
    {
        strButton = "MidButton";
    }

    //格式化显示坐标,并居中
    QString str = QString("<h1><center>[%1, %2][%3]</center></h1>").
            arg(x).arg(y).arg(strButton);
    this->setText(str);

    qDebug()<<"press";
}

运行结果如下:
在这里插入图片描述

(2)鼠标移动

在MyLabel类中新增一个函数,定义如下(mylabel.h中也要声明,这里不再赘述):

void MyLabel::mouseMoveEvent(QMouseEvent *ev)
{
    //获取坐标
    int x = ev->x();
    int y = ev->y();

    QString str=QString("<h1><center>Move[%1, %2][%3]</center></h1>").arg(x).arg(y);
    this->setText(str);
}

点击运行后,按下鼠标,会调用mousePressEvent,移动鼠标会调用mouseMoveEvent
在这里插入图片描述

(3)显示多个按键

对于按下按键后(不松开)拖动的情况,会先调用mousePressEvent,然后调用mouseMoveEvent,我们希望标签能显示Move和坐标的同时,也显示按下了哪个按钮,例如Move[300, 300][LeftButton],对于鼠标移动过程中,同时按下左键和右键的情况,我们也希望能够检测到。则可以对mouseMoveEvent做如下修改:

void MyLabel::mouseMoveEvent(QMouseEvent *ev)
{
    //获取坐标
    int x = ev->x();
    int y = ev->y();

    //获取鼠标按键
    Qt::MouseButtons btns = ev->buttons();  //可以一次获取多个按键
    QString strButton ="" ;
    if(btns & Qt::LeftButton)               //按位与操作
    {
        strButton += "LeftButton ";			//+是字符串拼接,因为有可能按了多个键
    }
    if(btns & Qt::RightButton)
    {
        strButton += "RightButton ";		//+是字符串拼接,因为有可能按了多个键
    }
    if(btns & Qt::MidButton)
    {
        strButton += "MidButton ";
    }

    QString str=QString("<h1><center>Move[%1, %2][%3]</center></h1>").
            arg(x).arg(y).arg(strButton);
    this->setText(str);
}

这里需要注意的是,ev->buttons()返回一个数,我们用二进制的角度来看,假如它返回的是000101,那么它是000100(MidButton)和000001(LeftButton)按位与的结果,表示同时按下了两个按键。
后面的几个if判断条件,其实是按位与运算。因为每一种Qt::MouseButton常量,只有某一位是1,其他位都为0,因此要从ev->buttons()的返回值中将某一位抽取出来,可以使用按位与运算,例如要判断是否按下了RightButton(000010),可以用RightButton与返回值进行按位与运算,结果为0则表示没按下,非0则表示按下了,假设返回值为000011,000010&000011结果是000010,结果非0,因此按下了。

运行后,按下鼠标拖动如下:
在这里插入图片描述
在按下鼠标(左键)拖动的那一瞬间,是否调用了mousePressEvent?可以在mousePressEvent函数中增加一行打印功能,来查看是否调用。
我自己尝试了,点下的那一瞬间,确实是先调用mousePressEvent,然后调用mouseMoveEvent,即点下的一瞬间先相应mousePressEvent,随后移动鼠标时相应mouseMoveEvent,此时由于鼠标左键还没松开,因此能被ev->buttons()检测到。

对于同时按下多个按键进行拖动,也能正常显示
在这里插入图片描述

(4)设置默认鼠标跟踪

上面的程序,在开始的时候,没有跟踪鼠标,也就是下面这个样子:
在这里插入图片描述
只有点击了窗口以后,才会显示“Move+坐标+按键名称”

如果我们想在窗口打开后,鼠标移动到窗口中(未点击)就开始跟踪鼠标的移动,那么可以在构造函数中进行设置

MyLabel::MyLabel(QWidget *parent) : QLabel(parent)
{
    //默认情况下,窗口不会主动跟踪鼠标
    //只有当某个鼠标按键按下的情况下才开始跟踪
    //如果想一开始跟踪,就要使用以下函数
    this->setMouseTracking(true);
}

8 QT事件分发机制

为什么我们点击了鼠标,就会调用mousePressEvent,移动了鼠标就会调用mouseMoveEvent?
当某个事件(鼠标、键盘)发生的时候,相应的控件就会收到这个事件,并且根据事件的类型调用相应的事件处理函数,这个过程都在一个名为event的事件中,这个函数可以在帮助文档中查看,它是一个虚函数,可以被子类重写。

在这里插入图片描述
一般情况下,不会去重写这个函数,我们能这里为了了解事件分发机制,因此对其进行重写。

这里仅仅以鼠标移动为例

bool MyLabel::event(QEvent *e)
{
    //返回值:true表示该事件得到处理,如果是false,没被处理,事件会继续传递到父窗口
    //QEvent就是所有Event类的父类,这里其实是使用了多态
    //判断event的类型
    if(e->type()==QEvent::MouseMove)    //e->type()返回的是鼠标事件的类型
    {
        this->mouseMoveEvent(static_cast<QMouseEvent *>(e));	//这句如果注释掉,则鼠标移动将不会有反应
        return true;
    }

    //其他类型的事件,交给父类的event函数去处理
    return QLabel::event(e);    //父类的event函数因为被覆盖了,所以需要域作用符
}

如果将this->mouseMoveEvent(static_cast<QMouseEvent *>(e));注释掉,则当鼠标移动时将不会有反应,此时就把鼠标移动给拦截过滤了。

事件的机制,可以用下面这张图来总结,事件过滤器这里没讲,这个并不重要:
在这里插入图片描述

  • 2
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值