0


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内容如下:

#ifndefMYLABEL_H#defineMYLABEL_H#include<QWidget>#include<QLabel>classMyLabel:publicQLabel{
    Q_OBJECT
public:explicitMyLabel(QWidget *parent =nullptr);

signals:public:voidmousePressEvent(QMouseEvent *event);};#endif// MYLABEL_H

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

MyLabel.cpp内容如下:

#include"mylabel.h"#include"QMouseEvent"MyLabel::MyLabel(QWidget *parent):QLabel(parent){}voidMyLabel::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){}voidMyLabel::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中也要声明,这里不再赘述):

voidMyLabel::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做如下修改:

voidMyLabel::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的事件中,这个函数可以在帮助文档中查看,它是一个虚函数,可以被子类重写。

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

这里仅仅以鼠标移动为例

boolMyLabel::event(QEvent *e){//返回值:true表示该事件得到处理,如果是false,没被处理,事件会继续传递到父窗口//QEvent就是所有Event类的父类,这里其实是使用了多态//判断event的类型if(e->type()==QEvent::MouseMove)//e->type()返回的是鼠标事件的类型{this->mouseMoveEvent(static_cast<QMouseEvent *>(e));//这句如果注释掉,则鼠标移动将不会有反应returntrue;}//其他类型的事件,交给父类的event函数去处理returnQLabel::event(e);//父类的event函数因为被覆盖了,所以需要域作用符}

如果将

this->mouseMoveEvent(static_cast<QMouseEvent *>(e));

注释掉,则当鼠标移动时将不会有反应,此时就把鼠标移动给拦截过滤了。

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

标签: qt 计算机外设 ui

本文转载自: https://blog.csdn.net/weixin_44457930/article/details/129695362
版权归原作者 weixin_44457930 所有, 如有侵权,请联系我们删除。

“QT的使用3:鼠标事件”的评论:

还没有评论