当前位置: 首页 > article >正文

使用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_()


http://www.kler.cn/a/528057.html

相关文章:

  • LeetCode:322.零钱兑换
  • 【C语言】内存管理
  • 【项目集成Husky】
  • OpenCV:闭运算
  • AJAX笔记入门篇
  • FFmpeg(7.1版本)编译:Ubuntu18.04交叉编译到ARM
  • 第十二章 I 开头的术语
  • Java数据结构和算法(一)
  • 【Java异步编程】CompletableFuture综合实战:泡茶喝水与复杂的异步调用
  • 【机器学习】自定义数据集 使用pytorch框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测,对预测结果计算精确度和召回率及F1分数
  • 【愚公系列】《循序渐进Vue.js 3.x前端开发实践》040-Vue过渡动画
  • git基础使用--1--版本控制的基本概念
  • Flutter常用Widget小部件
  • 数据结构 树2
  • Spring Boot 实例解析:从概念到代码
  • STM32 01 LED
  • [原创](Modern C++)现代C++的关键性概念: 流格式化
  • 网络基础
  • 8.[前端开发-CSS]Day08-图形-字体-字体图标-元素定位
  • 【Git】初识Git Git基本操作详解
  • XML Schema 数值数据类型
  • 物联网 STM32【源代码形式-ESP8266透传】连接OneNet IOT从云产品开发到底层MQTT实现,APP控制 【保姆级零基础搭建】
  • 50. 正点原子官方系统镜像烧写实验
  • 100.1 AI量化面试题:解释夏普比率(Sharpe Ratio)的计算方法及其在投资组合管理中的应用,并说明其局限性
  • 什么是“知识蒸馏”
  • 云计算部署模式全面解析