【PyQt】(自制类)处理鼠标点击逻辑

news2025/3/13 16:44:14

写了个自认为还算不错的类,用于简化mousePressEventmouseMoveEventmouseReleaseEvent中的鼠标信息。

功能有以下几点:
  • 鼠标当前状态,包括鼠标左/中/右键和单击/双击/抬起
  • 鼠标防抖(仅超出一定程度时才判断鼠标发生了移动),灵敏度可设置;
  • 鼠标长按(在鼠标长按并且未发生移动时触发),时长可设置;
  • 鼠标双击(两次点击的时间间隔足够小时判断为双击),时长可设置;
  • 鼠标偏移量,仅鼠标按下时有效,可返回自点击时的总偏移量,也可返回与上次鼠标事件之间的相对偏移量
补充:

这个自制类在多键按下时会产生歧义,也就是没法处理有如刁难一般的操作,像是右键拖拽然后左键来添乱之类的。本来是想再重新写份代码以填补这个缺陷的,但想想就有点怪,什么场合下才需要这种怪异的操作。




自制类XJ_MouseStatus

#XJ_MouseStatus.py
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtCore import QPoint,Qt,QObject
from PyQt5.QtGui import QMouseEvent

__all__=['XJ_MouseStatus']
class XJ_MouseStatus(QObject):#mousePressEvent、mouseMoveEvent和mouseReleaseEvent特供。只处理单键(多键行为请在外部代码控制)
    longClick=pyqtSignal()#鼠标原地不动长按时触发

    __antiJitter=5#防抖,当鼠标点击位置与鼠标当前位置的曼哈顿距离不超过该值时仍将鼠标视为不动状态
    __doubleClickInterval=500#双击间隔(ms)
    __longPressInterval=500#长按间隔(ms)
    __record={
        'lastPress':None,#上一次按下时的信息
        'lastMouse':None,#上一次的鼠标信息
        'currMouse':None,#当前鼠标信息
        }
    __press=[QMouseEvent.MouseButtonRelease,QMouseEvent.MouseButtonPress,QMouseEvent.MouseButtonDblClick]#偷懒用的
    __move=False#用于判断是否长按
    __timerID=0#鼠标按下时对应的定时器
    class __Data:
        pos=None#鼠标位置
        btn=None#鼠标按键(左中右)
        pressStatus=None#鼠标当前按下状态(单双击/抬起)
        timeStamp=None#鼠标事件时间刻
        def __init__(self,event):
            self.pos=event.globalPos()#这里不用pos是为了防暴毙
            self.btn=event.button()
            self.pressStatus=event.MouseButtonRelease
            self.timeStamp=event.timestamp()

    def __init__(self,*arg):
        super().__init__(*arg)
        record=self.__record.copy()
        fakeEvent=QMouseEvent(QMouseEvent.MouseButtonRelease,QPoint(0,0),Qt.NoButton,Qt.NoButton,Qt.NoModifier)
        data=self.__Data(fakeEvent)
        data.timeStamp-=self.__doubleClickInterval#小防,避免开局单击时触发双击行为
        record['lastMouse']=data
        record['currMouse']=data
        record['lastPress']=data
        self.__record=record
    def timerEvent(self,event):
        record=self.__record
        press=self.__press
        tId=event.timerId()
        cId=self.__timerID
        self.killTimer(event.timerId())
        if(cId==tId):#当前定时器
            if(not self.__move and record['currMouse'].pressStatus!=press[0]):#未发生移动,未抬起鼠标,触发长按信号
                self.longClick.emit()

    def Set_DoubleClickInterval(self,interval):#设置双击时间间隔(ms)
        self.__doubleClickInterval=interval
    def Set_LongPressInterval(self,interval):#设置长按时间间隔(ms)
        self.__longPressInterval=interval
    def Set_AntiJitter(self,val):#设置防抖值
        self.__antiJitter=val if val>0 else 0

    def Get_Position(self):#返回鼠标坐标。是屏幕坐标(global),需要使用QWidget.mapFromGlobal(QPoint)自行转换为控件相对坐标
        return self.__record['currMouse'].pos
    def Get_PressButtonStatus(self):#返回当前鼠标的键(左中右)以及按下状态(单击/双击/抬起)
        return self.__record['currMouse'].btn,self.__record['currMouse'].pressStatus
    def Get_MoveDelta(self,total=True,strict=True):#返回鼠标移动量(仅鼠标按下时有效),为QPoint对象
        press=self.__press
        record=self.__record
        data_curr=record['currMouse']
        if(data_curr.pressStatus!=press[0]):#说明鼠标按下
            if(not strict or self.__move):#严格模式下,仅判定发生移动时计算移动量
                p1=record['currMouse'].pos
                if(total):
                    p2=record['lastPress'].pos
                else:
                    p2=record['lastMouse'].pos
                return QPoint(p1.x()-p2.x(),p1.y()-p2.y())
        return QPoint(0,0)
    def Get_HasMoved(self):#判断是否发生移动(毕竟用Get_MoveDelta来判断移动的发生是有点麻烦,还不如多一个函数
        return self.__move

    def Opt_Update(self,event):#更新状态
        press=self.__press
        record=self.__record
        data_curr=self.__Data(event)
        if(event.type()==press[1] or event.type()==press[2]):#单/双击
            self.__move=False
            data_old=record['lastPress']
            data_curr.pressStatus=press[1]
            if(data_old.btn==data_curr.btn):#同键位按下
                if(data_curr.timeStamp-data_old.timeStamp<self.__doubleClickInterval):#在时间间隔内
                    if(data_old.pressStatus!=press[2]):#没有双击过
                        data_curr.pressStatus=press[2]#双击
            record['lastPress']=data_curr
            record['lastMouse']=data_curr
            record['currMouse']=data_curr
            self.__timerID=self.startTimer(self.__longPressInterval)
        else:#移动/抬起
            data_curr.btn=event.buttons()
            data_curr.pressStatus=record['lastMouse'].pressStatus
            if(event.type()==press[0]):#抬起
                if(data_curr.btn==Qt.NoButton):#确保无按键按下时设置为Release
                    data_curr.pressStatus=press[0]
                    data_curr.btn=event.button()
            else:#移动(QMouseEvent.MouseMove)
                if(data_curr.pressStatus!=press[0] and not self.__move):#判断有无发生拖拽
                    delta=self.Get_MoveDelta(strict=False)
                    if(abs(delta.x())+abs(delta.y())>self.__antiJitter):
                        self.__move=True
                        record['currMouse'].pos=record['lastPress'].pos
            record['lastMouse']=record['currMouse']
            record['currMouse']=data_curr



测试代码与运行结果:

与鼠标相关的部分枚举量:
  • 单击QMouseEvent.MouseButtonPress
  • 双击QMouseEvent.MouseButtonDblClick
  • 抬起QMouseEvent.MouseButtonRelease
  • 左键Qt.LeftButton
  • 中键Qt.MidButton
  • 右键Qt.RightButton
#Main.py
import sys
from PyQt5.QtWidgets import QApplication,QWidget
from XJ_MouseStatus import *

class Test(QWidget):
    __mouseStatus=None
    def __init__(self,*arg):
        super().__init__(*arg)
        ms=XJ_MouseStatus()
        ms.longClick.connect(lambda:print("<LongClick!>"))
        self.__mouseStatus=ms
    def __EasyPrint(self):
        press={
            QMouseEvent.MouseButtonRelease:"Release",
            QMouseEvent.MouseButtonPress:"Press",
            QMouseEvent.MouseButtonDblClick:"DblClick",}
        button={
            Qt.LeftButton:'Left',
            Qt.MidButton:'Middle',
            Qt.RightButton:'Right',}
        tPoint=lambda point:(point.x(),point.y())
        tBtn=lambda btn:[button[key] for key in button if key&btn]
        tBtnStatus=lambda status:(tBtn(status[0]),press[status[1]])

        ms=self.__mouseStatus
        pos=tPoint(self.mapFromGlobal(ms.Get_Position()))
        moveDelta=tPoint(ms.Get_MoveDelta())
        btnStatus=tBtnStatus(ms.Get_PressButtonStatus())
        print(f'pos{pos},\tdelta{moveDelta},\t{btnStatus[0]}-{btnStatus[1]}')
        if(btnStatus[1]=='Release'):
            print()
    def mousePressEvent(self,event):
        self.__mouseStatus.Opt_Update(event)
        self.__EasyPrint()
    def mouseMoveEvent(self,event):
        self.__mouseStatus.Opt_Update(event)
        self.__EasyPrint()
    def mouseReleaseEvent(self,event):
        self.__mouseStatus.Opt_Update(event)
        self.__EasyPrint()


if __name__=='__main__':
    app = QApplication(sys.argv)

    t=Test()
    t.show()

    sys.exit(app.exec())

运行结果




本文发布于CSDN,未经个人允许不得私自转载:https://blog.csdn.net/weixin_44733774/article/details/134349820

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Java面向对象(进阶)-- 面向对象特征之三:多态性

文章目录 一、多态的形式和体现&#xff08;1&#xff09;为什么需要多态性(polymorphism)&#xff1f;&#xff08;2&#xff09; 对象的多态性 二、 多态的理解&#xff08;1&#xff09;如何理解多态性&#xff08;2&#xff09;Java中多态性的体现&#xff08;3&#xff09…

SpringMvc 常见面试题

1、SpringMvc概述 1.1、什么是Spring MVC &#xff1f;简单介绍下你对springMVC的理解? Spring MVC是一个基于Java的实现了MVC设计模式的请求驱动类型的轻量级Web框架&#xff0c;通过把Model&#xff0c;View&#xff0c;Controller分离&#xff0c;将web层进行职责解耦&am…

CentOs7 NAT模式连接网络

1.配置动态网络 1.1 检查主机网卡配置 检查主机的网络设置 进入控制面板&#xff0c;找到网络共享中心 查看适配器是否都已经开启 1.2 设置虚拟机的网络配置 打开虚拟机网络配置设置&#xff0c;对网卡VMnet8 进行设置 记住网关 全部选择应用&#xff0c;确定 1.3 设置…

操作系统(一)基础知识及操作系统启动

文章目录 前言前置基础知识计算机组成CPU磁盘内核中断、异常、系统调用局部性原理 启动操作系统计算机加电是如何正常执行服务的&#xff1f;开机自检BIOS&#xff08;Basic Input/Output System&#xff09;BootLoader 小结 前言 本文主要涉及操作系统的简介、硬件结构、内存…

从Hadoop到对象存储,抛弃Hadoop,数据湖才能重获新生?

Hadoop与数据湖的关系 1、Hadoop时代的落幕2、Databricks和Snowflake做对了什么3、Hadoop与对象存储&#xff08;OSD&#xff09;4、Databricks与Snowflake为什么选择对象存储5、对象存储面临的挑战 1、Hadoop时代的落幕 十几年前&#xff0c;Hadoop是解决大规模数据分析的“白…

C++17中std::optional的使用

模版类std::optional管理一个可选的(optional)存储值(contained value)&#xff0c;即可能存在也可能不存在的值。std::optional的一个常见用例是存储可能失败的函数的返回值。与其它方法相反(例如std::pair<T, bool>),std::optional可以很好地处理构造成本高昂的对象&am…

ETW HOOK原理探析

ETW HOOK研究 文章目录 ETW HOOK研究前言原理探究内核开启ETW日志HOOK ETW修改ETW日志上下文代理GetCpuClock函数寻找SSDT和SSDT Shadow 总结参考 前言 关于ETW是什么我就不多说了&#xff0c;可以通过微软的相关文档了解到。据网上得知这项技术最早被披露于2345的驱动中&…

Netty--ByteBuffer

2. ByteBuffer 有一普通文本文件 data.txt&#xff0c;内容为 1234567890abcd 使用 FileChannel 来读取文件内容 Slf4j public class ChannelDemo1 {public static void main(String[] args) {// FileChannel// 1. 输入输出流&#xff0c; 2. RandomAccessFile// try (F…

反转链表 --- 递归回溯算法练习三

目录 1. 分析题意 2. 分析算法原理 2.1. 递归思路&#xff1a; 1. 挖掘子问题&#xff1a; 3. 编写代码 3.1. step 1&#xff1a; 3.2. step 2&#xff1a; 3.3. step 3&#xff1a; 3.4. 递归代码&#xff1a; 1. 分析题意 力扣原题链接如下&#xff1a; 206. 反转链…

组件的设计原则

目录 插槽的基本概念 基础用法 具名插槽 使用场景 布局控制 嵌套组件 组件的灵活性 高级用法 作用域插槽 总结 前言 Vue 的 slot 是一项强大的特性&#xff0c;用于组件化开发中。它允许父组件向子组件传递内容&#xff0c;使得组件更加灵活和可复用。通过 slot&…

抢量双11!抖音商城「官方立减」 缘何成为“爆单神器”?

10月20日抖音商城双11好物节正式开跑&#xff0c;仅仅三天&#xff0c;抖音商城整体GMV对比去年同期提升了200%&#xff0c;而在开跑一周后&#xff0c;一些品牌的销售额已经超过了今年整个618&#xff0c;可谓增势迅猛。其中&#xff0c;平台官方特别推出的「官方立减」玩法&a…

抢占全球30%碳化硅市场份额!英飞凌押注低碳化和数字化“新时代”

“未来十年将是低碳化和数字化‘双轮驱动’发展的时代。” 英飞凌科技全球高级副总裁及大中华区总裁、英飞凌科技大中华区电源与传感系统事业部负责人潘大伟在英飞凌2023年大中华区生态创新峰会上表示。 当前&#xff0c;“数字化低碳化”新趋势正在席卷和重塑着未来世界的千行…

vue.cli 中怎样使用自定义的组件

目录 创建自定义组件 注册并使用自定义组件 全局注册自定义组件 使用 Props 传递数据 总结 前言 在Vue CLI中使用自定义组件是构建交互式和模块化Web应用的重要一环。Vue CLI为开发者提供了使用自定义组件的灵活性和简便性。通过Vue CLI&#xff0c;你可以创建、注册和使…

UI自动化测试最佳设计模式POM

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;加入1000人软件测试技术学习交流群&#x1f4e2;资源分享&#xff1a;进了字节跳动之后&#xff0c;才…

LabVIEW中NIGPIB设备与驱动程序不相关的MAX报错

LabVIEW中NIGPIB设备与驱动程序不相关的MAX报错 当插入GPIB-USB设备时&#xff0c;看到了NI MAX中列出该设备&#xff0c;但却显示了黄色警告指示&#xff0c;并且指出Windows没有与您的设备相关的驱动程序。 解决方案 需要安装能兼容的NI-488.2驱动程序。 通过交叉参考以下有…

WebSphere Liberty 8.5.5.9 (一)

WebSphere Liberty 8.5.5.9 (一) 安装 1. 从官网下载 WebSphere Liberty 8.5.5.9 2. 解压 解压到 D:\wlp-webProfile7-java8-8.5.5.93. 启动 D:\wlp-webProfile7-java8-8.5.5.9\wlp\bin>server start 正在启动服务器 defaultServer。 服务器 defaultServer 已启动。4. …

UWB人员定位系统的原理与应用

uwb定位技术源码 uwb高精度定位系统源码 uwb人员定位系统基于什么原理&#xff1f; UWB人员定位系统基于超宽带(Ultra WideBand)技术进行位置定位。它利用超短脉冲信号&#xff0c;通过测量信号的到达时间差和信号强度等信息&#xff0c;实现对目标位置的定位。UWB技术具有高…

Docker安装详细步骤及相关环境安装配置

目录 一、从空白系统中克隆Centos7系统 二、使用xshell连接docker_tigerhhzz虚拟机 ​编辑 三、在CentOS7基础上安装Docker容器 最近自己在虚拟机上搭建一个docker,将项目运行在虚拟机中。 需要提前准备的工具&#xff0c;XShell(远程链接工具)&#xff0c;VM&#xff08;…

Qt——连接mysql增删查改(仓库管理极简版)

目录 UI布局设计 .pro文件 mainwindow.h main.cpp UI布局设计 .pro文件 QT core gui QT core gui sql QT sqlgreaterThan(QT_MAJOR_VERSION, 4): QT widgetsCONFIG c11# The following define makes your compiler emit warnings if you use # any …

C语言数据结构-----链表类型详解及链表练习题

0.前言 之前我讲解了循序表以及单链表&#xff0c;接下来我会在介绍几个不同的链表&#xff0c;并举例相关习题使大家能够更加深入的理解。 前期内容如下&#xff1a; 链接: 顺序表(动态顺序表增删查改的代码实现) 链接: 单链表(无头单向不循环)增删查改的代码实现 链接: [双向…