使用PyQt5绘制带有刻度的温度计控件

前言:进入学习Python开发上位机界面的第二阶段,学习如何开发自定义控件,从常用的控件入手学习,本期主要学习如何使用PyQt5绘制带有刻度的温度计控件。

1. 先找到一篇参考文章

参考文章:Qt编写自定义控件5-柱状温度计

在这里插入图片描述

参考文章代码是C++写的,改成python写的代码。修改代码成功后,本人生成的温度计控件显示效果如下所示:

在这里插入图片描述

2. 学习背后涉及的知识点

参考文章:实战PyQt5: 123-详解QPainter绘图

2.1 如何设置圆形图案的文本在圆形图案中居中显示

drawEllipse(): 绘制一个椭圆,当设置椭圆的宽度和高度相等,它实际上是一个圆形。

在这里插入图片描述

使用drawText(x, y, text_width, text_height, Qt.AlignCenter, text) 方法绘制文本,其中x是计算出的起始X坐标,y是你希望文本基线的位置(通常是你绘制区域的中心线或稍上方)。

 def draw_centered_text(self, qp):  
        # 设置字体  
        font = QFont('Arial', 20)  
        qp.setFont(font)  
  
        # 要绘制的文本  
        text = "Hello, PyQt5!"  
  
        # 获取文本的宽度和高度  
        fm = QFontMetrics(font)  
        text_width = fm.width(text)  
        text_height = fm.height()  
  
        # 计算文本居中绘制的起始位置  
        # 注意:这里假设我们要在整个QWidget的区域内居中  
        x = (self.width() - text_width) // 2  
        y = (self.height() + text_height) // 2  # 向上偏移文本高度的一半以垂直居中  
  
        # 绘制文本  
        qp.drawText(x, y, text_width, text_height, Qt.AlignCenter, text)  

2.2 绘制温度计刻度线

drawPoint(): 使用当前笔的颜色在给定位置绘制一个点。
drawLine(): 绘制一条线。

在这里插入图片描述

2.3 绘制标识当前温度值的三角形箭头

drawPolygon(): 绘制可填充一个多边形。

在这里插入图片描述

3. 温度计控件待优化点

3.1 温度计刻度显示的温度数值存在精度问题

温度计的数值范围是0℃-100℃,在某些数值下温度计的刻度箭头指示是不够准确的,例如下图所示:37℃的箭头应该指示在目前位置的偏下面一点。

在这里插入图片描述

原因分析:用于显示用户设置温度数值的三角形箭头Y轴位置计算是有精度误差的。

 # 计算标尺的高度
 rulerHeight = self.height() - 2 * self.radius
 # 计算每一格移动多少
 increment = rulerHeight / (self.maxValue - self.minValue)

 self.barX = self._get_bar_x_pos()
 self.barY = (self.maxValue - self.userValue + self.minValue) * increment

这个是因为python进行除法运算就存在计算精度问题:

在这里插入图片描述

3.2 用户设置温度为标尺的最大刻度值存在显示问题

原因分析:温度计标尺的最大刻度Y轴位置正好是温度计控件所在界面的高度。

在这里插入图片描述

4. 带有刻度的温度计控件的完整代码

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author : Logintern09
import sys

from PyQt5.QtCore import QRectF, QPoint, Qt, QRect, QPointF
from PyQt5.QtGui import (
    QPainter,
    QColor,
    QFont,
    QPainterPath,
    QPolygon,
)
from PyQt5.QtWidgets import QWidget, QApplication, QMainWindow, QVBoxLayout


"""
 * 柱状温度计控件
 * 1:可设置精确度(小数点后几位)
 * 2:可设置背景色/柱状颜色/线条颜色
 * 3:可设置长线条步长及短线条步长
 * 4:可设置温度计数值显示范围值
 * 5:支持负数刻度值
 * 6:可设置刻度尺位置 无 左侧 右侧 两侧
 * 7:可设置用户设定目标值
"""


class BasicRulerTemp(QWidget):
    def __init__(self, parent=None):
        super(BasicRulerTemp, self).__init__(parent)

        self.TickPosition = {
            "TickPosition_Null": 0,  # 不显示
            "TickPosition_Left": 1,  # 左侧显示
            "TickPosition_Right": 2,  # 右侧显示
            "TickPosition_Both": 3,  # 两侧显示
        }

        self.minValue = 0  # 最小值
        self.maxValue = 100  # 最大值
        self.userValue = 80  # 用户设定值
        self.userValueColor = QColor("red")  # 用户设定值颜色
        self.precision = 0  # 精确度, 小数点后几位
        self.longStep = 10  # 长线条等分步长
        self.shortStep = 2  # 短线条等分步长
        self.showUserValue = True  # 是否显示三角箭头显示用户设定值
        self.lineColor = QColor("black")  # 线条颜色
        self.barBgColor = QColor("white")  # 柱状背景色
        self.barColor = QColor("red")  # 柱状颜色
        self.tickPosition = 3  # 刻度尺位置
        self.barWidth = 15  # 水银柱宽度
        self.barHeight = 100  # 水银柱高度
        self.radius = 30  # 水银柱底部圆半径
        self.circleX_pos = self.width() / 3  # 水银柱底部圆X轴坐标位置

    def get_min_value(self):
        """
        获取温度计刻度最小值
        """
        return self.minValue

    def get_max_value(self):
        """
        获取温度计刻度最大值
        """
        return self.maxValue

    def get_current_value(self):
        """
        获取用户设置的温度计当前刻度值
        """
        return self.userValue

    def get_precision(self):
        """
        获取温度计刻度显示的数值精度(保留到小数点后几位)
        """
        return self.precision

    def set_range(self, min_value, max_value):
        """
        设置温度计数值显示范围
        """
        self.minValue = min_value
        self.maxValue = max_value

    def set_min_value(self, min_value):
        """
        设置温度计刻度显示最小数值
        """
        self.minValue = min_value

    def set_max_value(self, max_value):
        """
        设置温度计刻度显示最大数值
        """
        self.maxValue = max_value

    def set_value(self, value):
        """
        设置温度计刻度显示的数值
        """
        self.userValue = value

    def set_precision(self, precision):
        """
        设置温度计显示刻度数值的精度(保留到小数点后几位)
        """
        self.precision = precision

    def set_long_step(self, long_step):
        """
        设置温度计刻度线的长线条步长
        """
        self.longStep = long_step

    def set_short_step(self, short_step):
        """
        设置温度计刻度线的短线条步长
        """
        self.shortStep = short_step

    def set_show_user_value(self, show_user_value):
        """
        设置是否显示用户设定值
        """
        self.showUserValue = show_user_value

    def set_bar_bg_color(self, color):
        """
        设置水银柱背景颜色
        """
        self.userValueColor = QColor(color)

    def set_line_color(self, color):
        """
        设置温度计刻度线的线条颜色
        """
        self.lineColor = QColor(color)

    def set_tick_position(self, tick_position):
        """
        设置刻度尺位置
        """
        self.tickPosition = self.TickPosition[tick_position]


class QRulerTemp(BasicRulerTemp):
    def __init__(self, parent=None):
        super(QRulerTemp, self).__init__(parent)
        self.setWindowFlags(self.windowFlags() | Qt.FramelessWindowHint)
        self.setAttribute(Qt.WA_TranslucentBackground)
        self.setFixedSize(500, 500)

    def paintEvent(self, event):
        # 绘制准备工作,启用反锯齿
        painter = QPainter(self)
        # 保存当前画笔状态
        painter.save()
        painter.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing)

        # 绘制水银柱背景,包含水银柱底部圆
        self.drawBarBg(painter)

        # 绘制当前水银柱,包含水银柱底部圆
        self.drawBar(painter)

        # 绘制标尺及刻度尺
        if self.tickPosition == self.TickPosition["TickPosition_Left"]:
            self.drawRuler(painter, 0)
        elif self.tickPosition == self.TickPosition["TickPosition_Right"]:
            self.drawRuler(painter, 1)
        elif self.tickPosition == self.TickPosition["TickPosition_Both"]:
            self.drawRuler(painter, 0)
            self.drawRuler(painter, 1)

        # 绘制用于显示水银柱当前值的三角箭头
        if self.showUserValue:
            self.draw_value_flag(painter)

    def _get_bar_x_pos(self):
        # 计算在背景宽度的基础上缩小的百分比, 至少为 2
        circlePercent = self.radius / 3

        if circlePercent < 2:
            circlePercent = 2

        bar_x_pos = self.circleX_pos + circlePercent
        return bar_x_pos

    def drawBarBg(self, painter):
        # 绘制水银柱背景颜色,包含水银柱底部圆
        painter.save()
        painter.setPen(Qt.NoPen)
        painter.setBrush(self.barBgColor)

        # 计算标尺的高度
        offset = 4  # 背景形状偏移水银柱的像素
        barHeight = self.height() - self.radius
        bar_x_pos = self._get_bar_x_pos()
        barX = bar_x_pos - offset
        barY = 1
        barRect = QRectF(barX, barY, self.barWidth + offset * 2, barHeight)

        circleX = self.circleX_pos - 10
        circleY = self.height() - self.radius * 2 - 1
        circleWidth = self.radius * 2
        circleRect = QRectF(circleX, circleY, circleWidth, circleWidth)

        path = QPainterPath()
        path.addRect(barRect)
        path.addEllipse(circleRect)
        path.setFillRule(Qt.WindingFill)
        painter.drawPath(path)
        painter.restore()

    def drawBar(self, painter):
        # 绘制水银柱及标识当前刻度的三角形箭头
        painter.save()
        painter.setPen(Qt.NoPen)
        painter.setBrush(self.barColor)

        # 计算在背景宽度的基础上缩小的百分比, 至少为 2
        circlePercent = self.radius / 3

        if circlePercent < 2:
            circlePercent = 2

        # 计算标尺的高度
        rulerHeight = self.height() - 2 * self.radius
        # 计算每一格移动多少
        increment = rulerHeight / (self.maxValue - self.minValue)

        self.barX = self._get_bar_x_pos()
        self.barY = (self.maxValue - self.userValue + self.minValue) * increment
        barRect = QRectF(
            self.barX,
            self.barY,
            self.barWidth,
            (self.userValue - self.minValue) * increment + self.radius,
        )

        circleX = self.circleX_pos
        # 偏移 2 个像素,使得看起来边缘完整
        circleY = self.height() - self.radius * 2 - 2
        circleWidth = self.radius * 2 - circlePercent * 2
        circleRect = QRectF(circleX, circleY + circlePercent, circleWidth, circleWidth)

        path = QPainterPath()
        path.addRect(barRect)
        path.addEllipse(circleRect)
        path.setFillRule(Qt.WindingFill)
        painter.drawPath(path)
        # 设置水银柱底部圆的文本和文本颜色
        painter.save()
        font = QFont()
        font.setPixelSize(circleRect.width() * 0.55)
        painter.setFont(font)
        painter.setPen(Qt.white)
        painter.drawText(circleRect, Qt.AlignCenter, "%s" % self.userValue)
        painter.restore()

    def drawRuler(self, painter, type):
        # 绘制刻度线
        painter.save()
        painter.setPen(self.lineColor)

        barPercent = self.barWidth / 8  # 水银柱宽度

        if barPercent < 2:
            barPercent = 2

        # 绘制纵向标尺刻度
        length = self.height() - 2 * self.radius
        # 计算每一格移动多少
        increment = length / (self.maxValue - self.minValue)

        # 长线条短线条长度
        longLineLen = 10
        shortLineLen = 7

        # 绘制纵向标尺线 偏移line_offset像素
        self.line_offset = 8
        offset = self.barWidth / 2 + self.line_offset

        # 左侧刻度尺需要重新计算
        if type == 0:
            offset = -1 * offset
            longLineLen = -1 * longLineLen
            shortLineLen = -1 * shortLineLen

        initX = self.barX + self.barWidth / 2 + offset
        initY = barPercent
        topPot = QPointF(initX, initY)
        bottomPot = QPointF(initX, self.height() - 2 * self.radius)
        painter.drawLine(topPot, bottomPot)

        # 根据范围值绘制刻度线及刻度值
        for i in range(
            self.maxValue, self.minValue - self.shortStep, -1 * self.shortStep
        ):
            if i % self.longStep == 0:
                # 绘制长线条
                leftPot = QPoint(initX + longLineLen, initY)
                rightPot = QPoint(initX, initY)
                painter.drawLine(leftPot, rightPot)

                # 绘制文字
                strValue = f"{i:.{self.precision}f}"
                fontHeight = painter.fontMetrics().height()

                if type == 0:
                    # 左侧刻度线
                    x_offset = 45 + self.precision * 10
                    text_width = 30 + self.precision * 10
                    textRect = QRect(
                        initX - x_offset, initY - fontHeight / 3, text_width, 15
                    )
                    painter.drawText(textRect, Qt.AlignRight, strValue)
                elif type == 1:
                    # 右侧刻度线
                    text_width = 30 + self.precision * 10
                    textRect = QRect(
                        initX + longLineLen + self.line_offset,
                        initY - fontHeight / 3,
                        text_width,
                        15,
                    )
                    painter.drawText(textRect, Qt.AlignLeft, strValue)
            else:
                # 绘制短线条
                leftPot = QPointF(initX + shortLineLen, initY)
                rightPot = QPointF(initX, initY)
                painter.drawLine(leftPot, rightPot)

            initY += increment * self.shortStep
        painter.restore()

    def draw_value_flag(self, painter):
        painter.save()
        painter.setPen(Qt.NoPen)
        # 绘制用户设定值三角号
        if self.showUserValue:
            if (
                self.tickPosition == self.TickPosition["TickPosition_Left"]
                or self.tickPosition == self.TickPosition["TickPosition_Both"]
            ):
                pts = QPolygon()
                offset = 15
                initX = self.barX - self.line_offset
                initY = self.barY
                pts.append(QPoint(initX, initY))
                pts.append(QPoint(initX - offset, initY - offset / 2))
                pts.append(QPoint(initX - offset, initY + offset / 2))
                painter.setBrush(self.userValueColor)
                painter.drawPolygon(pts)
            if (
                self.tickPosition == self.TickPosition["TickPosition_Right"]
                or self.tickPosition == self.TickPosition["TickPosition_Both"]
            ):
                pts = QPolygon()
                offset = 15
                initX = self.barX + self.barWidth + self.line_offset
                initY = self.barY
                pts.append(QPoint(initX, initY))
                pts.append(QPoint(initX + offset, initY - offset / 2))
                pts.append(QPoint(initX + offset, initY + offset / 2))
                painter.setBrush(self.userValueColor)
                painter.drawPolygon(pts)
        painter.restore()


def main():
    app = QApplication(sys.argv)
    window = QMainWindow()
    window.setGeometry(100, 100, 100, 290)
    window.setWindowTitle("RulerTemp Example")
    RulerTemp = QRulerTemp()
    layout = QVBoxLayout()
    layout.addWidget(RulerTemp)
    window.setCentralWidget(QWidget())
    window.centralWidget().setLayout(layout)
    window.show()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()
    # app = QApplication([])
    # widget = QRulerTemp()
    # widget.show()
    # app.exec_()

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

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

相关文章

DIFY源码解析

偶然发现Github上某位大佬开源的DIFY源码注释和解析&#xff0c;目前还处于陆续不断更新地更新过程中&#xff0c;为大佬的专业和开源贡献精神点赞。先收藏链接&#xff0c;后续慢慢学习。 相关链接如下&#xff1a; DIFY源码解析

赛博算卦之周易六十四卦JAVA实现:六幺算尽天下事,梅花化解天下苦。

佬们过年好呀~新年第一篇博客让我们来场赛博算命吧&#xff01; 更多文章&#xff1a;个人主页 系列文章&#xff1a;JAVA专栏 欢迎各位大佬来访哦~互三必回&#xff01;&#xff01;&#xff01; 文章目录 #一、文化背景概述1.文化起源2.起卦步骤 #二、卦象解读#三、just do i…

「AI学习笔记」深度学习的起源与发展:从神经网络到大数据(二)

深度学习&#xff08;DL&#xff09;是现代人工智能&#xff08;AI&#xff09;的核心之一&#xff0c;但它并不是一夜之间出现的技术。从最初的理论提出到如今的广泛应用&#xff0c;深度学习经历了几乎一个世纪的不断探索与发展。今天&#xff0c;我们一起回顾深度学习的历史…

AIGC技术中常提到的 “嵌入转换到同一个向量空间中”该如何理解

在AIGC&#xff08;人工智能生成内容&#xff09;技术中&#xff0c;“嵌入转换到同一个向量空间中”是一个核心概念&#xff0c;其主要目的是将不同类型的输入数据&#xff08;如文本、图像、音频等&#xff09;映射到一个统一的连续向量空间中&#xff0c;从而实现数据之间的…

单细胞分析基础-第一节 数据质控、降维聚类

scRNA_pipeline\1.Seurat 生物技能树 可进官网查询 添加链接描述 分析流程 准备:R包安装 options("repos"="https://mirrors.ustc.edu.cn/CRAN/") if(!require("BiocManager")) install.packages("BiocManager",update = F,ask =…

【13】WLC HA介绍和配置

1.概述 本文对AireOS WLC的HA进行介绍,和大多数网络架构设计一样,单台的WLC是无法保证设备的冗余性的,而且WLC也不是双引擎的设备,所以需要依靠High Available的技术来为WLC提供高可用性。 2.WLC HA类型 AireOS WLC的高可用性技术可以分为N+1的SSO的HA。不是所有的设备都…

Alibaba开发规范_编程规约之命名风格

文章目录 命名风格的基本原则1. 命名不能以下划线或美元符号开始或结束2. 严禁使用拼音与英文混合或直接使用中文3. 类名使用 UpperCamelCase 风格&#xff0c;但以下情形例外&#xff1a;DO / BO / DTO / VO / AO / PO / UID 等4. 方法名、参数名、成员变量、局部变量使用 low…

【Elasticsearch 基础入门】Centos7下Elasticsearch 7.x安装与配置(单机)

Elasticsearch系列文章目录 【Elasticsearch 基础入门】一文带你了解Elasticsearch&#xff01;&#xff01;&#xff01;【Elasticsearch 基础入门】Centos7下Elasticsearch 7.x安装与配置&#xff08;单机&#xff09; 目录 Elasticsearch系列文章目录前言单机模式1. 安装 J…

Gurobi基础语法之 addConstr, addConstrs, addQConstr, addMQConstr

在新版本的 Gurobi 中&#xff0c;向 addConstr 这个方法中传入一个 TempConstr 对象&#xff0c;在模型中就会根据这个对象生成一个约束。更重要的是&#xff1a;TempConstr 对象可以传给所有addConstr系列方法&#xff0c;所以下面先介绍 TempConstr 对象 TempConstr TempC…

深度学习可视化指标方法工具

1. TensorBoard 简介&#xff1a;由TensorFlow提供的可视化工具&#xff0c;现已支持多种深度学习框架。 功能&#xff1a; 图可视化&#xff1a;展示计算图结构&#xff0c;帮助理解模型架构。 标量仪表板&#xff1a;跟踪损失和准确率等指标的变化。 直方图仪表板&#xf…

【自开发工具介绍】SQLSERVER的ImpDp和ExpDp工具01

1、开发背景 大家都很熟悉&#xff0c;Oracle提供了Impdp和ExpDp工具&#xff0c;功能很强大&#xff0c;可以进行db的导入导出的处理。但是对于Sqlserver数据库只是提供了简单的图形化的导出导入工具&#xff0c;在实际的开发和生产环境不太可能让用户在图形化的界面选择移行…

小程序-视图与逻辑

前言 1. 声明式导航 open-type"switchTab"如果没有写这个&#xff0c;因为是tabBar所以写这个&#xff0c;就无法跳转。路径开始也必须为斜线 open-type"navigate"这个可以不写 现在开始实现后退的效果 现在我们就在list页面里面实现后退 2.编程式导航…

list的使用,及部分功能的模拟实现(C++)

目录&#xff08;文章中"节点"和"结点"是同一个意思&#xff09; 1. list的介绍及使用 1.1 list的介绍 1.2 list的使用 1.2.1 list的构造 1.2.2 list iterator的使用 1.2.3 list capacity 1.2.4 list element access 1.2.5 list modifiers 1.2.6 list…

StarRocks BE源码编译、CLion高亮跳转方法

阅读SR BE源码时&#xff0c;很多类的引用位置爆红找不到&#xff0c;或无法跳转过去&#xff0c;而自己的Linux机器往往缺乏各种C依赖库&#xff0c;配置安装比较麻烦&#xff0c;因此总体的思路是通过CLion远程连接SR社区已经安装完各种依赖库的Docker容器&#xff0c;进行编…

Axure PR 9 旋转效果 设计交互

大家好&#xff0c;我是大明同学。 这期内容&#xff0c;我们将学习Axure中的旋转效果设计与交互技巧。 旋转 创建旋转效果所需的元件 1.打开一个新的 RP 文件并在画布上打开 Page 1。 2.在元件库中拖出一个按钮元件。 创建交互 创建按钮交互状态 1.选中按钮元件&#xf…

Java - 引用类型:强引用、软引用、弱引用和虚引用详解

文章目录 概述1. 强引用&#xff08;Strong Reference&#xff09;1.1 什么是强引用&#xff1f;1.2 强引用的特点1.3 强引用的使用场景1.4 强引用的注意事项 2. 软引用&#xff08;Soft Reference&#xff09;2.1 什么是软引用&#xff1f;2.2 软引用的特点2.3 软引用的使用场…

S4 HANA给科目分配允许记账的税码

本文主要介绍在S4 HANA OP中给科目分配允许记账的税码相关设置。具体请参照如下内容&#xff1a; 1. 给科目分配允许记账的税码 以上配置定义了总账科目可以使用什么税码进行记账。通常在科目主数据中会明确总账科目的“Tax Category”来请明确总账科目可以使用什么类型的税码…

xss-labs靶场

xss-labs靶场 xss攻击类型 反射型xss 即攻击者将恶意脚本嵌入到url或者表单中&#xff0c;当用户访问特定的url或者提交表单时&#xff08;用户端请求时)&#xff0c;恶意脚本会执行 攻击需要用户点击恶意链接或访问包含恶意参数的url触发 存储型xss 即攻击者将恶意脚本提交…

CVE-2024-23897-Jenkins任意文件读取漏洞复现

content Jenkins是什么CVE-2024-23897总结修复建议 Jenkins是什么 Jenkins是一人基于Java开发的、可扩展的持续集成引擎&#xff0c;用于持续、自动地构建/测试软件项目&#xff0c;可以监控一些定时执行的任务。 官网文档&#xff1a; Jenkins是一款开源 CI&CD 软件&…

解析 Oracle 中的 ALL_SYNONYMS 和 ALL_VIEWS 视图:查找同义词与视图的基础操作

目录 前言1. ALL_SYNONYMS 视图2. ALL_VIEWS 视图3. 扩展 前言 &#x1f91f; 找工作&#xff0c;来万码优才&#xff1a;&#x1f449; #小程序://万码优才/r6rqmzDaXpYkJZF 1. ALL_SYNONYMS 视图 在 Oracle 数据库中&#xff0c;同义词&#xff08;Synonym&#xff09;是对数…