QT的使用3:鼠标事件

鼠标事件

  • 0 事件
  • 1 需求
  • 2 查看控件的事件处理函数
  • 3 UI设计
  • 4 新建一个类,继承QLabel
  • 5 对已有对象进行类型提升
  • 6 重写事件处理函数
  • 7 项目进一步拓展
    • (1)获取鼠标按键
    • (2)鼠标移动
    • (3)显示多个按键
    • (4)设置默认鼠标跟踪
  • 8 QT事件分发机制

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::MouseButtons btns = event->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>[%1, %2][%3]</center></h1>").
            arg(x).arg(y).arg(strButton);
    this->setText(str);
}

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

(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));注释掉,则当鼠标移动时将不会有反应,此时就把鼠标移动给拦截过滤了。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/2517.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【数据结构】Java实现栈

目录 1. 概念 2. 栈的使用 3. 自己动手实现栈&#xff08;使用动态数组实现栈&#xff09; 1. 创建一个MyStack类 2. push入栈 3. pop出栈 4. 查看栈顶元素 5. 判断栈是否为空与获取栈长 6. toString方法 4. 整体实现 4.1 MyStack类 4.2 Test类 4.3 测试结果 1.…

计算机网络笔记——物理层

计算机网络笔记——物理层2. 物理层2.1 通信基础2.1.1 信号2.1.2 信源、信道及信宿2.1.3 速率、波特及码元2.1.4 带宽2.1.5 奈奎斯特定理采样定理奈奎斯特定理2.1.6 香农定理2.1.7 编码与调制调制数字信号调制为模拟信号模拟数据调制为模拟信号编码数字数据编码为数字信号模拟数…

C#中WPF实现依赖注入和MVVM,以及服务定位ServiceLocator

最近在想重写架构于是就研究了一套WPF的相关内容&#xff0c;WPF不像MAUI内置了容器&#xff0c;需要我们自己手动添加&#xff0c;于是就有了今天的内容。 首先&#xff0c;我们新建一个.net6.0的WPF项目 由于WPF没有内置容器,我们先安装一下依赖注入的nuget包 Microsoft.Ex…

网络技术与应用概论(上)——“计算机网络”

各位CSDN的uu们你们好呀&#xff0c;今天&#xff0c;小雅兰的内容依旧是计算机网络的一些知识点噢&#xff0c;下面&#xff0c;让我们进入计算机网络的世界吧 网络内涵 网络特征 网络定义 互联网发展过程 从ARPA网络到Internet 从低速互联网到高速互联网 从数据结构到统一网…

【C语言】通讯录的实现(静态版)

【C语言】通讯录的实现(静态版一.前言1.前期准备a.菜单实现b.联系人结构体的构建c.菜单选项的功能d.#define 的定义2.功能的实现a.初始化通讯录b.增加联系人c.显示通讯录d.查找联系人e.修改联系人d.删除联系人3. 总代码test.ccontact.ccontact.h一.前言 本文将会用c语言实现一…

Golang每日一练(leetDay0013)

目录 37. 解数独 Sudoku Solver &#x1f31f;&#x1f31f;&#x1f31f; 38. 外观数列 Count and Say &#x1f31f;&#x1f31f; 39. 组合总和 Combination Sum &#x1f31f;&#x1f31f; &#x1f31f; 每日一练刷题专栏 &#x1f31f; Golang每日一练 专栏 Py…

大数据技术之Hive

第1章Hive基本概念1.1 Hive1.1.1 Hive的产生背景在那一年的大数据开源社区&#xff0c;我们有了HDFS来存储海量数据、MapReduce来对海量数据进行分布式并行计算、Yarn来实现资源管理和作业调度。但是面对海量数据和负责的业务逻辑&#xff0c;开发人员要编写MR来对数据进行统计…

【Go】K8s 管理系统项目[Jenkins Pipeline K8s环境–应用部署]

K8s 管理系统项目[Jenkins Pipeline K8s环境–应用部署] 1. k8s-plantform-api-Pipeline 考虑到实际工作中前后端可能是不同的同学完成,一般Api部分完成后改动会比较小,web部分改动会比较频繁.于是将api和web分了2个pipeline实现 1.1 GIt仓库 docker目录存放镜像构建相关文件…

简介虚拟地址空间:保障进程间独立性的机制

我们知道&#xff0c;进程之间是相互独立的&#xff0c;在操作系统级别中&#xff0c;一个进程所执行的程序无法直接访问另一个进程所执行的内存区域&#xff08;即实现进程间通信比较困难&#xff09;&#xff1b;一个进程运行的失败也不会影响其它进程的运行。这使我们的操作…

vue编程方法

1&#xff0c;app.vue 其中的moundted只是被执行一次。 系统中所有的组件都放到app。vue文件中。放到根组件中的只是被执行一次的代码可以放到main.js中码&#xff1f; 不可以&#xff0c;因为main文件只是一个js文件不是一个组件。组件中的一些属性不能被使用。比如&#xff…

VS Code上搭建Vue开发环境超详细教程

这篇关于在Visual Studio Code上搭建vue开发环境的超详细教程手把手教会你! 首先在Visual Studio Code上搭建vue开发环境有几个步骤&#xff1a; 1、下载安装node.js 2、安装npm 3、安装cnpm 4、安装vue/cli脚手架 5、创建vue项目 6、运行vue项目 1.下载安装node.js 地址&…

鸟哥的Linux私房菜 正则表示法与文件格式化处理

第十一章、正则表示法与文件格式化处理 https://linux.vbird.org/linux_basic/centos7/0330regularex.php 简体版 http://cn.linux.vbird.org/linux_basic/0330regularex.php 11.2.2 grep的一些高级选项 例题一、搜索特定字符串 例题二、利用中括号 [] 来搜寻集合字符 例题四…

8个python自动化脚本提高打工人幸福感~比心~

人生苦短&#xff0c;我用Python 最近有许多打工人都找我说打工好难 每天都是执行许多重复的任务&#xff0c; 例如阅读新闻、发邮件、查看天气、打开书签、清理文件夹等等&#xff0c; 使用自动化脚本&#xff0c;就无需手动一次又一次地完成这些任务&#xff0c; 非常方便…

蓝桥杯嵌入式RTC实时时钟

文章目录 前言一、RTC是什么二、cubemx的配置三、函数的使用总结前言 本篇文章将给大家介绍RTC实时时钟。 一、RTC是什么 STM32的实时时钟RTC是一个独立的定时器,RTC时钟内部依靠BCD码计数。RTC实时时钟提高时钟、闹钟、日历功能。RTC功耗较低,可以使用在低功耗设备上。 …

Redis为什么选择单线程?Redis为什么这么快?

目录专栏导读一、Redis版本迭代二、Redis4.0之前为什么一直采用单线程&#xff1f;三、Redis6.0引入多线程四、Redis主线程和IO线程是如何完成请求的&#xff1f;1、服务端和客户端建立socket连接2、IO线程读取并解析请求3、主线程执行请求命令4、IO线程会写回socket和主线程清…

DM8:LINUX环境安装DM8数据库安装条件--GLIBC版本要求

DM8&#xff1a;LINUX环境安装DM8数据库安装条件--GLIBC版本要求环境介绍1 检查 GLIBC 版本号2 /tmp 临时目录空间要等于或大于2GB3 报错截图3.1 导入授权报错3.2 设置时区报错3.3 DmAPService启动失败3.4 初始化实例报错4 更多达梦数据库使用经验环境介绍 在LINUX环境安装达梦…

一线大厂软件测试常见面试题1500问,背完直接拿捏面试官,

三、测试理论 3.1 你们原来项目的测试流程是怎么样的? 我们的测试流程主要有三个阶段&#xff1a;需求了解分析、测试准备、测试执行。 1、需求了解分析阶段 我们的SE会把需求文档给我们自己先去了解一到两天这样&#xff0c;之后我们会有一个需求澄清会议&#xff0c; 我…

基于Springboot实现口腔牙诊所网站平台【源码+论文】

基于Springboot实现口腔牙诊所网站平台【源码论文】开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea M…

整合SpringCache

整合SpringCache 1、引入依赖cache还有redis <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId> </dependency>2、写配置 spring:cache:type: redis3、测试使用缓存 Cache…

大数据现在找工作难么

大数据行业工作好找还是难找不是光靠嘴说出来的结合实际&#xff0c;看看市场上的招聘需求和岗位要求就大致知道了 要想符合企业用人规范&#xff0c;学历&#xff0c;工作经验&#xff0c;掌握技能都是非常重要的~ 先来看几个招聘网站的报告数据&#xff1a; Boss直聘发布的…