pyqt5中QGraphicsView弹出菜单

news2024/11/15 15:32:47

QPainter与Graphics View 架构的区别

PyQt5 提供了两种绘图方法。一种是使用 QPainter 类在 QWidget 类提供的画布上画图,可以 绘制点、线、圆等各种基本形状,从而组成自己需要的图形。所有界面组件都是 QWidget 的子类, 界面上的按钮、编辑框等各种组件的界面效果都是使用 QPainter 绘制出来的。用户从 QWidget 继 承定义自己的界面组件时,要绘制组件的界面效果也是使用这种方法。QPainter 绘制的图形如同 位图,是不可交互操作的图形。 PyQt5 另外提供一种基于 Graphics View 架构的绘图方法,这种方法使用 QGraphicsView、 QGraphicsScene 和各种 QGraphicsItem 图形项绘图,在一个场景中可以绘制大量图件,且每个图件 是可选择、可交互的,如同矢量图编辑软件那样可以操作每个图件。Graphics View 架构为用户绘制 复杂的组件化图形提供了便利。

关于QPainter跟Graphics View的基础知识请参考如下连接:

https://blog.csdn.net/zhengyanan815/article/details/127119421

https://blog.csdn.net/qq_40732350/article/details/90116319

需求说明

在主窗口中,存在一个按钮(或者label),点击该按钮,会弹出一个圆形菜单,如下图红框所示,我点击主窗口中的工具箱按钮,会弹出一个按钮菜单,跟悬浮桌面球的效果类似,虽然最终实现了,但是还存在两个小bug,这个后面会提到。当然使用QPainter也可以实现,这里主要是为了学习QGraphicsView

实现思路

  1. 新建一个主窗口,里面放置一个自定义的label(不想自定义也可以直接使用按钮,不过按钮也要重写,因为涉及到鼠标事件)

  1. 自定义一个QWidget继承自QGraphicsView,主要用来加载弹出菜单按钮,该自定义widget包含弹出动作,每次弹出时,会获取工具集按钮的全局坐标(相对于整个屏幕的),然后设置该widget的中心为工具集的中心坐标,最后做弹出动作,弹出菜单使用的是并行动画类:QParallelAnimationGroup()

  1. 当鼠标离开自定义widget区域时,菜单收起来,这里没有做收起动作,感兴趣的可以自己做一下,跟展开动作相反即可。

说明一下,为什么不做鼠标悬浮进入工具集区域,就弹出按钮,因为QGraphicsView设置为透明属性后,存在鼠标穿透问题,弹出菜单将会反复切换。

存在的问题:

存在两个问题如下:

  1. QGraphicsView想要设置为背景透明,组件不透明,就必须设置透明背景区域不响应鼠标事件,这时,如果鼠标从透明区域出去,菜单将不会收起,如下图所示。

#设置为无边框模式,背景透明,组件不透明
self.view.setStyleSheet("background: transparent;border:0px")
self.view.setWindowFlags(Qt.FramelessWindowHint)
self.view.setAttribute(Qt.WA_TranslucentBackground) # 透明区域不响应鼠标事件

2. QGraphicsView组件中加载的按钮,样式设置为原型边框时,四角的白色区域仍然显示,不知道如何去除,正在的QWidget就不存在这个问题,大家看上面第一张图,那个大的原型按钮,就没有这个问题。

真对上面的两个问题,看到此篇文章的大佬,如果有解决办法,还请留言,感激不尽。

代码部分:

    • 主窗口QDsigner设计的代码

mainWin.py

# -*- coding: utf-8 -*-

# Form implementation generated from reading ui file 'mainWin.ui'
#
# Created by: PyQt5 UI code generator 5.15.4
#
# WARNING: Any manual changes made to this file will be lost when pyuic5 is
# run again.  Do not edit this file unless you know what you are doing.


from PyQt5 import QtCore, QtGui, QtWidgets


class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName("MainWindow")
        MainWindow.resize(505, 346)
        self.centralwidget = QtWidgets.QWidget(MainWindow)
        self.centralwidget.setObjectName("centralwidget")
        self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
        self.verticalLayout.setObjectName("verticalLayout")
        spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
        self.verticalLayout.addItem(spacerItem)
        self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
        self.horizontalLayout_2.setObjectName("horizontalLayout_2")
        spacerItem1 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_2.addItem(spacerItem1)
        self.pushButton = QtWidgets.QPushButton(self.centralwidget)
        self.pushButton.setMinimumSize(QtCore.QSize(100, 100))
        self.pushButton.setStyleSheet("border-color: rgb(255, 255, 255);\n"
"font: 15pt \"MS Shell Dlg 2\";\n"
"background-color: rgb(255, 255, 255);")
        icon = QtGui.QIcon()
        icon.addPixmap(QtGui.QPixmap("tool.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.pushButton.setIcon(icon)
        self.pushButton.setIconSize(QtCore.QSize(64, 64))
        self.pushButton.setObjectName("pushButton")
        self.horizontalLayout_2.addWidget(self.pushButton)
        spacerItem2 = QtWidgets.QSpacerItem(40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum)
        self.horizontalLayout_2.addItem(spacerItem2)
        self.verticalLayout.addLayout(self.horizontalLayout_2)
        spacerItem3 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
        self.verticalLayout.addItem(spacerItem3)
        self.horizontalLayout = QtWidgets.QHBoxLayout()
        self.horizontalLayout.setObjectName("horizontalLayout")
        self.label = MyQLabel(self.centralwidget)
        self.label.setText("")
        self.label.setAlignment(QtCore.Qt.AlignCenter)
        self.label.setObjectName("label")
        self.horizontalLayout.addWidget(self.label)
        self.verticalLayout.addLayout(self.horizontalLayout)
        spacerItem4 = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
        self.verticalLayout.addItem(spacerItem4)
        MainWindow.setCentralWidget(self.centralwidget)
        self.statusbar = QtWidgets.QStatusBar(MainWindow)
        self.statusbar.setObjectName("statusbar")
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)

    def retranslateUi(self, MainWindow):
        _translate = QtCore.QCoreApplication.translate
        MainWindow.setWindowTitle(_translate("MainWindow", "GraphicsView Demo"))
from myLabel import MyQLabel

2. 自定义label组件

myLabel.py

from PyQt5.QtWidgets import QLabel
from PyQt5.QtCore import pyqtSignal, QPoint, Qt



class MyQLabel(QLabel):
    enterSignal = pyqtSignal(tuple)
    def __init__(self, parent=None):
        super(MyQLabel, self).__init__(parent)

    # 鼠标左键点击触发
    def mousePressEvent(self, event):
        if (event.button() == Qt.LeftButton):
            # label在屏幕的绝对位置
            global_coordinates = self.mapToGlobal(QPoint(0, 0)).x(), self.mapToGlobal(QPoint(0, 0)).y()
            # 获取label组件的中心绝对坐标(全屏幕范围内)
            label_central = global_coordinates[0] + self.width() // 2, global_coordinates[1] + self.height() // 2
            # print(label_central)
            self.enterSignal.emit(label_central)

    # 鼠标离开label
    def leaveEvent(self, event):
        # self.setAttribute(Qt.WA_TransparentForMouseEvents, True)
        pass

3. 主程序入口以及自定义QGraphicsView

这两个写到了一起:runMainWin.py

import sys
import math
from PyQt5.QtCore import Qt, QPropertyAnimation, QParallelAnimationGroup, QPointF, QEasingCurve, QSize
from PyQt5.QtGui import QGuiApplication, QPixmap, QIcon
from PyQt5.QtWidgets import QMainWindow, QApplication, QGraphicsView, QGraphicsScene, QPushButton

from mainWin import Ui_MainWindow


# 自定义GraphicsView的子类,并实现按钮的添加
class MyGraphics(QGraphicsView):
    def __init__(self, parent=None):
        super(MyGraphics, self).__init__(parent)
        self.__initView()
        self.resize(166, 156)

    def __initView(self):
        self.scene = QGraphicsScene()
        self.scene.setSceneRect(0, 0, 150, 150)
        self.ref_x0 = self.scene.width() / 2    # 场景的中心x坐标
        self.ref_y0 = self.scene.height() / 2   # 场景的中新y坐标
        self.btnProxyList = []

        btn1 = QPushButton(QIcon(QPixmap("openConfigFolder.png")), None)
        btn2 = QPushButton(QIcon(QPixmap("newFile.png")), None)
        btn2.setToolTip('New case file')
        btn_size = 40
        btn1.resize(btn_size, btn_size)
        btn2.resize(btn_size, btn_size)
        btn1_proxy = self.scene.addWidget(btn1)
        btn1_proxy.setPos(self.ref_x0 - btn1_proxy.rect().width()/2, self.ref_y0 - btn1_proxy.rect().height()/2)
        self.btnProxyList.append(btn1_proxy)
        btn2_proxy = self.scene.addWidget(btn2)
        btn2_proxy.setPos(self.ref_x0 - btn2_proxy.rect().width()/2, self.ref_y0 - btn2_proxy.rect().height()/2)
        self.btnProxyList.append(btn2_proxy)

        btn3 = QPushButton(QIcon("uploadWeight.png"), None)
        btn4 = QPushButton(QIcon("coffee.png"), None)
        btn3.resize(btn_size, btn_size)
        btn4.resize(btn_size, btn_size)
        btn3_proxy = self.scene.addWidget(btn3)
        btn3_proxy.setPos(self.ref_x0 - btn3_proxy.rect().width() / 2, self.ref_y0 - btn3_proxy.rect().height() / 2)
        self.btnProxyList.append(btn3_proxy)
        btn4_proxy = self.scene.addWidget(btn4)
        btn4_proxy.setPos(self.ref_x0 - btn4_proxy.rect().width() / 2, self.ref_y0 - btn4_proxy.rect().height() / 2)
        self.btnProxyList.append(btn4_proxy)

        btn5 = QPushButton(QIcon("punch.png"), None)
        btn6 = QPushButton(QIcon("role.png"), None)
        btn5.resize(btn_size, btn_size)
        btn6.resize(btn_size, btn_size)
        btn5_proxy = self.scene.addWidget(btn5)
        btn5_proxy.setPos(self.ref_x0 - btn5_proxy.rect().width() / 2, self.ref_y0 - btn5_proxy.rect().height() / 2)
        self.btnProxyList.append(btn5_proxy)
        btn6_proxy = self.scene.addWidget(btn6)
        btn6_proxy.setPos(self.ref_x0 - btn6_proxy.rect().width() / 2, self.ref_y0 - btn6_proxy.rect().height() / 2)
        self.btnProxyList.append(btn6_proxy)

        for x in range(6):
            eval(f"btn{x+1}").setIconSize(QSize(30,30))
            eval(f"btn{x+1}").setStyleSheet("background: #ffffff;border:2px solid #001721;border-radius: 20px;")

        self.setScene(self.scene)


    # 鼠标离开widget时执行的动作
    def leaveEvent(self, event):
        if self.isVisible():
            self.hide()
            pass


    # 按钮动画,每次弹出时,都要调用一次
    def btnAnimate(self):
        R = 60     # 动画时,圆周的半径
        item_number = len(self.items())
        angle = [x*round(float(360/item_number), 2) for x in range(item_number)]
        animateTime = 200       # 动画持续时间

        self.group = QParallelAnimationGroup(self)

        num = 0
        for item in self.btnProxyList:
            animation = QPropertyAnimation(item, b'pos')
            animation.setStartValue(QPointF(self.ref_x0 - item.rect().width() / 2,
                                            self.ref_y0 - item.rect().height() / 2))
            animation.setEndValue(
                QPointF(self.ref_x0 + R * math.cos(angle[num] * math.pi / 180) - item.rect().width() / 2,
                        self.ref_y0 - R * math.sin(angle[num] * math.pi / 180) - item.rect().height() / 2))
            animation.setDuration(animateTime)
            animation.setEasingCurve(QEasingCurve.InCubic)
            self.group.addAnimation(animation)
            num += 1

        self.group.start()

        self.update()


class MainWin(QMainWindow, Ui_MainWindow):
    def __init__(self):
        super(MainWin, self).__init__()
        self.setupUi(self)
        self.resize(730, 500)
        self.__initUI()
        self.handle()


    def __initUI(self):
        self.label.setFixedHeight(30)
        self.label.setFixedWidth(30)
        pix = QPixmap("tool.png")
        pix.scaled(self.label.size(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation)        # 让图片填充满QLabel
        # pix.scaled(self.label.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
        self.label.setPixmap(pix)
        self.label.setScaledContents(True)
        self.pushButton.setStyleSheet("background: #ffffff;border:3px solid #FF0000;border-radius:50px;")
        self.view = MyGraphics()


    def handle(self):
        self.label.enterSignal.connect(self.showPopMenu)


    def showPopMenu(self, labelPos):
        # print(self.view.style().pixelMetric(QStyle.PM_TitleBarHeight))      # 获取标题栏高度
        self.view.setStyleSheet("background: transparent;border:0px")
        self.view.setWindowFlags(Qt.FramelessWindowHint)
        self.view.setAttribute(Qt.WA_TranslucentBackground)
        self.view.move(int(labelPos[0] - self.view.width() / 2), int(labelPos[1] - self.view.height()/2))
        self.view.show()
        self.view.btnAnimate()
        self.view.setMouseTracking(True)


if __name__ == '__main__':
    QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
    QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps)
    QGuiApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough)

    app = QApplication(sys.argv)
    main_win = MainWin()

    main_win.show()
    sys.exit(app.exec_())

资源链接

使用的资源以及代码打包放在一个文件夹,想学习的直接下载即可使用,CSDN有自动调整积分机制,如果不能下载,请联系我,我单独发给你们。

https://download.csdn.net/download/weixin_43054437/87361094

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

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

相关文章

74、【哈希表】leetcode——18. 四数之和(C++版本)

题目描述 原题链接:18. 四数之和 解题思路 思路与三数之和:15. 三数之和,区别之处在于: 1、多一层for循环,用于多加一个数。 四数之和就是在三数之和多加一个数,用前两个数相加,后面两个数继…

Hadoop之MapReduce

一、概述 MapReduce 核心功能是将用户编写的业务逻辑代码和自带默认组件整合成一个完整的 分布式运算程序,并发运行在一个 Hadoop 集群上。 1、优缺点:优点:1)MapReduce 易于编程 它简单的实现一些接口,就可以完成一个…

(day2)自学java综合练习

目录 1.卖飞机票 2.找质数 3.开发验证码 4.数组元素的复制 5.评委打分 6.数字加密 7.数字解密 8.抢红包 9.模拟双色球 10.二维数组 1.卖飞机票 机票价格按照淡季旺季、头等舱和经济舱收费、输入机票原价、月份和头等舱或经济舱。按照如下规则计算机票价格&#xff1…

推荐两个好用的虚拟机、SSH 终端开源工具(Virtual Box、WindTerm)

笔者最近因一些变故,加上阳了,停更了一段时间,并提前回老家过年了。因并没有带笔记本电脑回去,故在折腾了一番老家电脑后,选择拥抱开源,使用一些开源的工具,而非习惯的 VMware Workstation 和 S…

Python3,区区10行代码,批量把图片插入Excel指定单元格中,省下时间去烫头发。

这里写目录标题1、引言2、代码实战2.1 代码示例2.2 遇到问题及处理方案2.2.1 遇到问题2.2.2 解决方案3、总结1、引言 小屌丝:鱼哥, 想请教你个问题。 小鱼:啥问题呢? 小屌丝:我想把图片插入到excel里面 小鱼&#xff…

AVS3变换之ISP和ISTS

ISP(Implicit Selected Transform)是AVS3中新增的针对intra块的变换工具,IST对intra块提供了两种可分离的变换核,编码器根据RDO选择最优的变换核,但是对于选中的变换核不在码流中传输其索引,而是将其索引隐…

今天给大家介绍一篇医院医疗管理系统的设计与实现(源码+论文)

项目描述 临近学期结束,还是毕业设计,你还在做java程序网络编程,期末作业,老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。这里根据疫情当下,你想解决的问…

Java开发学习(三十七)----SpringBoot多环境配置及配置文件分类

一、多环境配置 在工作中,对于开发环境、测试环境、生产环境的配置肯定都不相同,比如我们开发阶段会在自己的电脑上安装 mysql ,连接自己电脑上的 mysql 即可,但是项目开发完毕后要上线就需要该配置,将环境的配置改为…

亚马逊云科技 2022 re:Invent 的几个关键词:数据、云原生端到端、安全

一转眼,又是一年。2022 年云计算行业重要的技术趋势和方向里,亚马逊云科技一年一度的 re:Invent 大会是不可或缺的一环。 今年已经是 re:Invent 大会连续举办的第十一年,和往年一样,亚马逊云科技将一年的重磅技术观察和实践干货悉…

【数据结构】单链表 — 纯C实现单链表

目录💌前言一、定义1.概念2.特点3.优点4.缺点5.结点定义接口实现创建链表结点创建单个结点创建链表打印链表测试创建功能尾插尾删尾部插入尾部删除尾插尾删测试头插头删头部插入头部删除头插头删测试pos位的插入删除查找pos位置在pos位置前插入在pos位置后插入删除p…

CUDA和Compute Capability

Compute Capability 参考 指示GPU硬件能够支持的特性,可以被应用参考哪些特性可以运行。 这里能够找到不同Compute Capability对应的硬件特性。比如我的笔记本搭载了一块Geforce830m,Compute Capability为5.0,硬件特性为 另外有关技术细节比…

面试官问我HTTP,我真的是

面试官:今天要不来聊聊HTTP吧? 候选者:嗯,HTTP「协议」是客户端和服务器「交互」的一种通迅的格式 候选者:所谓的「协议」实际上就是双方约定好的「格式」,让双方都能看得懂的东西而已 候选者&#xff1…

2023-01-02 Echarts学习笔记(一) 基础概念和应用示例:折线图

文章目录0.什么是Echarts?1.常见使用场景2.使用Echarts的基本步骤3.应用示例:做一个折线图4.参考资料0.什么是Echarts? ECharts.js是 百度出品的一个开源 Javascript 数据可视化库 一个使用 JavaScript 实现的开源可视化库, 可以流畅的运行在 PC 和移动设备上&a…

【数据结构】二叉树递归算法代码总结

文章目录一、内容介绍二、算法总结2.1 二叉树结构2.2 完整代码2.3 输出结果三、Reference四、总结一、内容介绍 上一年备考数据结构中自己整理并验证过的二叉树递归算法。包括: 1、二叉树的创建; 2、二叉树的先、中、后序的递归遍历; 3、输出…

[项目说明]-基于人工智能博弈树,极大极小(Minimax)搜索算法并使用Alpha-Beta剪枝算法优化实现的可人机博弈的AI智能五子棋游戏。

个人选题项目 基于人工智能博弈树,极大极小(Minimax)搜索算法并使用Alpha-Beta剪枝算法优化实现的可人机博弈的AI智能五子棋游戏。 设计目标及主要内容 本系统是根据传统五子棋游戏的功能编写,其功能实现了基于AI人工智能算法实现智能的人机对弈五子棋…

Java jdk安装及环境配置

Java环境安装一、 jdk和jre的安装1、安装目录创建java文件夹2、java文件夹内创建jdk和jre3、解压下载好的jdk安装包二、环境变量的配置一、 jdk和jre的安装 首先下载jdk 1、安装目录创建java文件夹 2、java文件夹内创建jdk和jre 3、解压下载好的jdk安装包 双击运行解压的jdk …

Kali Linux中shutdown指令的用法3-2

2.4 屏蔽重启指令 -h参数表示屏蔽重启指令,使用如图6所示的指令,可以屏蔽reboot指令,该指令的作用为关闭(poweroff)系统。 图6 屏蔽重启指令 从图6中可以看出,-h屏蔽了--reboot。 需要注意的是&#xff…

关于OLTP 和OLAP 干货知识分享

OLTP 和 OLAP 这两个概念在十来年前、十几年前BI这个词还不是那么普及的时候,还经常放在一起做比较,现在已经很少再单独拿出来做对比了,但也总还是有人会问到,我在这里大概讲下两个概念的差别和联系。 什么是OLTP OLTP 英文全称…

81.Zabbix之Window服务器agent监控

Zabbix版本:6.2.3 1.官网上下载对应的agent Download Zabbix agents 我们下载Zabbix agent 2 2.配置Zabbix agent2 下载完成后,将压缩包复制到服务器,然后放在移至C盘目录下(其他目录也是可以的),然后进行解压。 3.修改配置文件 用文本编辑软件打开zabbix_agent2.c…

DC-4靶场练习

今天抽时间做了下DC-4的实验,整理了实验步骤,并提炼总结方法论。内网存活主机扫描命令nmap -sP 192.168.101.0/24 arp-scan -l以上IP地址使用排除法,最后得出192.168.101.79是靶机地址。探测目标开放的端口推荐masscannmap快速扫描:masscan -…