PyQt实战——随机涂格子的特色进度条(十一)

embedded/2024/12/28 1:45:59/

系类往期文章:
PyQt5实战——多脚本集合包,前言与环境配置(一)
PyQt5实战——多脚本集合包,UI以及工程布局(二)
PyQt5实战——多脚本集合包,程序入口QMainWindow(三)
PyQt5实战——操作台打印重定向,主界面以及stacklayout使用(四)
PyQt5实战——UTF-8编码器UI页面设计以及按钮连接(五)
PyQt5实战——UTF-8编码器功能的实现(六)
PyQt5实战——翻译器的UI页面设计以及代码实现(七)
PyQt5实战——翻译的实现,第一次爬取微软翻译经验总结(八)
PyQt5实战——翻译的实现,成功爬取微软翻译(可长期使用)经验总结(九)
PyQt实战——使用python提取JSON数据(十)

前言

这是实现音频编解码器功能模块的第二篇文章,在本文中,我们将实现自定义的特色进度条以及简单介绍编码器的UI布局,本文主要实现特色进度条,在本文中,我们会涉及到的知识点有:特色进度条的实现原理,一个python生成器的用法,PyQt的控件绘制机制。本文仅介绍编解码器UI的布局,因为实现UI时会用到开线程的部分,因此本文先不展示。

本次笔者设计的特色进度条,是涂格子形式的进度条,当进度增加时,进度条内的方格会被随机涂上颜色,供大家参考。

UI框架

如下图所示:

请添加图片描述

  • 编码器的UI布局同样为垂直分布

  • 在第一层级,由经典的选择文件构成,这个选择文件的布局样式已经在本系列中多次出现

  • 一个下拉选择框,目前只有ADPCM一个算法可供选择

  • 两个按钮水平布局,编码按钮启动编码器,解码按钮启动解码器

  • 编解码器读取选中的文件,从文件中读取数据。注意,文件最好是txt文件格式,数据是十六进制数1字节为单位,以空格分隔开,可选择一帧数据一行,如下图所示:

    请添加图片描述

  • 解码器会在workspaces目录下生成pcm.txt数据文件,其中包含了解码后的PCM数据,以文本形式保存,后续将其放入pcm构建器中可生成二进制文件,在PCM播放器中播放即可。

  • 编码器会在workspaces目录下生成adpcm.txt数据文件,其中包含了编码后的ADPCM数据,以文本形式保存,后续如何处理数据,可自行决定。

  • 最下方是进度条,由20*5共100个方格组成,在未启动状态下为灰色,当开始解码或编码时,编码进度每增加1%,那么就会随机涂蓝一个格子,在进度为100%时,即编码完成,则所有格子全部为蓝色。

  • 当重新开始编码或解码时,格子会重新初始化,全部被重新涂成灰色。

进度条展示

下面是正在解码过程中的进度条展示,在右边的日志展示区中可以看到,正在进行ADPCM解码,在功能区中,下方进度条在随机选择格子涂蓝,将这些以显示当前进度

请添加图片描述

下面是解码结束后的进度条样式,可以看到,在右边的日志区中,显示ADPCM decode end,adpcm解码完成,下方的进度条已被完全涂成蓝色。

请添加图片描述

代码详解

下面给出进度条的代码:

python">import sys
import random
from PyQt5.QtCore import Qt, QTimer
from PyQt5.QtGui import QColor, QPainter, QPainterPath
from PyQt5.QtWidgets import QApplication, QWidget, QVBoxLayout, QPushButton
import queue
class ProgressBar(QWidget):def __init__(self):super().__init__()self.setFixedSize(640, 160)  # 设置进度条的大小self.grid_size = 32  # 每个方格的宽度和高度self.total_grids = (self.width() // self.grid_size) * (self.height() // self.grid_size)  # 方格总数self.lit_grids = 0  # 当前已点亮的方格数量self.grid_status = [False] * self.total_grids  # 每个方格是否被点亮def init_grids(self):self.lit_grids = 0  # 当前已点亮的方格数量self.grid_status = [False] * self.total_gridsself.update()  # 更新进度条绘制def update_progress(self,q):"""随机点亮一个未点亮的方格"""while True:percent = q.get()if self.lit_grids < self.total_grids and percent <= 100:# 随机选择一个未点亮的方格unlit_grids = [i for i, lit in enumerate(self.grid_status) if not lit]random_grid = random.choice(unlit_grids)self.grid_status[random_grid] = True  # 点亮该方格self.lit_grids += 1  # 增加已点亮的方格数量self.update()  # 更新进度条绘制if percent == 100:breakelse:breakdef paintEvent(self, event):"""自定义绘制进度条"""painter = QPainter(self)painter.setRenderHint(QPainter.Antialiasing)  # 启用抗锯齿效果,使边角更平滑# 绘制方格for row in range(self.height() // self.grid_size):for col in range(self.width() // self.grid_size):x = col * self.grid_sizey = row * self.grid_size# 计算当前方格的索引index = row * (self.width() // self.grid_size) + colif self.grid_status[index]:color = QColor(144, 203, 251)   # 已点亮的部分,蓝色else:color = QColor(224, 224, 224) # 未点亮的部分,灰色painter.fillRect(x, y, self.grid_size, self.grid_size, color)  # 绘制方格painter.end()

我们来解释一下上面的代码:

  • __init__方法中,设置了一些基础配置,进度条的大小,方格的大小,方格的总数,已点亮的方格数,每个方格的状态(是否被点亮了),值得注意的是,方格的总是是通过进度条的长除以方格的长,进度条的宽除以方格的宽然后相乘的来的,这里直接翻译为20*5 = 100
  • init_grids方法重新初始化方格的基础配置,归零已点亮的方格数量,擦除方格的状态,并重新绘制进度条,即恢复初始状态
  • update_progress方法用以更新进度,可以看到,该函数接收一个q参数,该参数为一个队列对象,用以线程之间的沟通(线程方面我们下篇文章讨论),此处做一个while的死循环,从队列中获取来自另一个线程的消息,这个操作是阻塞的,也就是说,当未从队列中获取新的消息,则线程不会继续进行下一条指令。
  • 如果已经点亮的方格数小于全部方格数,且进度≤100%,则执行以下操作,随机选择一个未点亮的方格,标志为点亮
  • unlit_grids = [i for i, lit in enumerate(self.grid_status) if not lit],解释一下这一段代码:
    • enumerate(self.grid_status)将会返回一个生成器,生成的是每个元素的索引i和对应的值lit,例如:假设self.grid_status = [True, False, True, Flase],那么生成器将会返回,(0,True),(1,False),(2,True),(3,False)
    • for i, lit in enumerate(self.grid_status)是一个for循环,用来遍历enumerate(self.grid_status)返回的每一个(i,lit)元组,i是索引,litself.grid_status中对应的状态值
    • if not lit这个条件判断会检查lit是否为False,如果为False,就会被加入到列表unlit_grids
    • 总的来说,这是整个列表推导式的语法,它会创建一个包含所有符合条件if not lit的元素索引i的新列表
  • random.choice方法在unlit_grids中随机选择一个方格
  • 将选择的的方格状态设置为点亮
  • 增加已点亮的方格数量
  • 更新进度条绘制
  • 如果进度条达到100%,则直接退出该函数,不再循环,也不再接收来自其它线程的消息
  • paintEvent方法是用以绘制进度条的,创建一个在PyQt中可绘制的对象QPainter。
  • 两层for循环,计算现在的位置,确定这个位置的方格是以点亮还是未点亮,确定颜色,绘制方格。
  • painter.end方法结束绘制

总结

进度条的更新实际上是对控件的重新绘制

paintEvent在什么时候被调用?

paintEvent 是一个 Qt 事件处理函数,它在 需要重新绘制组件时 被自动调用。Qt 会在以下几种情况下触发 paintEvent

  1. 窗口或控件首次显示时
    • 当窗口或控件第一次显示时,paintEvent 会被触发,负责绘制控件的初始状态。
  2. 控件大小发生变化时
    • 如果控件的大小发生了变化(例如,窗口调整大小),paintEvent 会被调用来重新绘制控件,以适应新的尺寸。
  3. 调用 update()repaint()
    • 当你调用 update()repaint() 方法时,Qt 会标记该控件为“需要重绘”,并会在下一个事件循环中触发 paintEvent
  4. 其他因素
    • 如果控件的内容发生了变化,或者某些部分被遮挡并随后暴露出来,Qt 会重新触发 paintEvent 来重新绘制这些区域。例如,当窗口被部分遮挡后再显示出来时,Qt 会调用 paintEvent 来刷新被遮挡的部分。

在上面的代码中,每次更新进度条的状态时,调用self.update,重新绘制控件。

如果不适用self.update,而是直接调用paintEvent绘制进度条可以吗?

直接调用 paintEvent 来重绘进度条并不是一种推荐的做法。原因在于,Qt 的事件机制是自动管理绘制过程的,而直接调用 paintEvent 会绕过事件系统,可能导致一些问题。下面我将详细解释原因和影响。

  1. Qt 的绘制机制
  • 在 Qt 中,paintEvent 由事件循环自动管理,通常不需要直接调用。当你调用 self.update() 时,Qt 会将控件标记为“需要重绘”,并在合适的时机调用 paintEvent
  • self.update() 会触发一个绘制事件,但不会立即调用 paintEvent,而是将重绘请求加入事件队列,稍后由 Qt 的事件循环处理。这样做的好处是,Qt 会合理地合并多个绘制请求,避免频繁的重复重绘,提高性能。
  1. 直接调用 paintEvent 的问题
  • 如果你直接调用 paintEvent,就会绕过 Qt 的事件循环和绘制机制。paintEvent 通常是由系统在适当时机(如控件需要重绘时)自动触发的。
  • 直接调用paintEvent可能会导致:
    • 绘制不一致:因为 Qt 的绘制机制已经负责了缓存和重绘的时机,直接调用 paintEvent 可能会与其他重绘请求冲突,导致绘制不稳定。
    • 不符合最佳实践:Qt 推荐使用 update() 来请求重绘,因为它利用了 Qt 内部的绘制优化策略。如果你直接调用 paintEvent,可能会破坏这些优化。

通过消息队列传递更新消息

进度条的更新实际上需要与编解码器的编解码进度相关,因为编解码器吃算力,因此不建议将编解码器放在主线程中运行,所以对编解码器开了子线程运行,消息的互传需要通过消息队列来进行。关于线程问题,我们将在下一期内容时更详细地讲解。

祝你变得更强!

因为语音编解码器功能模块的实现较为复杂,而且也增加了一些新的UI设计,因此知识点与代码都无法在一篇文章中全部呈现,但将代码分散在不同的文章里又让一些基础比较薄弱的同学难以快速上手,因此,如若对此模块感兴趣的人比较多,笔者将在这三篇文章的基础上,单独开一篇新的博文,梳理代码的布局以及如何在自己的机器上跑起来,让新手小白也能复制即用。


http://www.ppmy.cn/embedded/149322.html

相关文章

5G -- 网络安全

网络安全威胁 域外:运营商网络域外关键安全威胁 一、空口安全威胁 用户数据、信息窃听/篡改DDos攻击拒绝用户接入非授权终端违法接入网络伪基站触发终端降维攻击空口恶意干扰 二、Internet安全威胁 用户数据传输泄露、篡改仿冒网络应用拒绝特定服务Internet侧DDos攻击&…

深度解析:Maven 和 Gradle 的使用比较及常见仓库推荐

Maven 和 Gradle 是 Java 项目中最常用的构建工具。它们各有优势&#xff0c;适用于不同的场景。本文将对两者进行详细的对比&#xff0c;并推荐一些常用的 Maven 和 Gradle 仓库&#xff0c;帮助开发者高效管理依赖。 一、Maven 和 Gradle 的使用比较 1.1 基本介绍 Maven 基…

抖去推碰一碰系统技术源码/open SDK转发技术开发

抖去推碰一碰系统技术源码/open SDK转发技术开发 碰一碰智能系统#碰碰卡系统#碰一碰系统#碰一碰系统技术源头开发 碰碰卡智能营销系统开发是一种集成了人工智能和NFC技术的工具&#xff0c;碰碰卡智能营销系统通过整合数据分析、客户关系管理、自动化营销活动、多渠道整合和个…

opencv中的各种滤波器简介

在 OpenCV 中&#xff0c;滤波器是图像处理中的重要工具&#xff0c;用于对图像进行平滑、去噪、边缘检测等操作。以下是几种常见滤波器的简单介绍。 1. 均值滤波 (Mean Filter) 功能&#xff1a; 对图像进行平滑处理&#xff0c;减少噪声。 应用场景&#xff1a; 去除图像…

MIT实验笔记冲刺2 实验部分

目录 实现trace调用&#xff08;系统调用跟踪&#xff08;中等&#xff09;&#xff09; 攻击 xv6&#xff08;中等&#xff09; 下面就是实验的部分&#xff0c;Lab2中的实验有两个。一个是syscall implementations&#xff0c;另一个则是利用未抹除的内存内容来读取上一个内…

最优二叉搜索树【东北大学oj数据结构10-4】C++

题面 最优二叉搜索树是由 n 个键和 n1 个虚拟键构造的二叉搜索树&#xff0c;以最小化搜索操作的成本期望值。 给定一个序列 Kk1​,k2​,...,kn​&#xff0c;其中 n 个不同的键按排序顺序 &#xff0c;我们希望构造一个二叉搜索树。 对于每个关键 ki​&#xff0c;我们有一个…

CSS(四)display和float

display display 属性用于控制元素的显示类型&#xff0c;用的 display 值包括&#xff1a; block&#xff1a;块级元素 使元素成为块级元素&#xff0c;占据一整行&#xff0c;前后有换行宽度默认为父容器的 100%&#xff0c;可以设置宽高&#xff0c;支持 margin、padding、…

设计模式详解(建造者模式)

1、简述 建造者模式&#xff08;Builder Pattern&#xff09;是一种创建型设计模式&#xff0c;它通过将对象的构造过程与表示分离&#xff0c;使得相同的构造过程可以创建不同的表示。建造者模式尤其适用于创建复杂对象的场景。 2、什么是建造者模式&#xff1f; 建造者模式…