PyQt5之进度条:QProgressBar

Posted Persus

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PyQt5之进度条:QProgressBar相关的知识,希望对你有一定的参考价值。

PyQt5之进度条:QProgressBar

在软件中,在处理特别冗长的任务时,如果没有相关的进度信息,这个等待的过程会比较考验用户的耐心,根据相关理论,进度条可以缓解用户在等待过程中的焦虑,所以,当程序的响应速度无法再提升时,可以选择增加进度条指明程序的处理进度。

在PyQt5中,进度条控件为QProgressBar,它以动画的形式显示程序的处理进度。

QProgressBar

QProgressBar控件提供了水平或者垂直的进度条,可以通过设置进度条的最小值、最大值和当前值显示进度,最小值和最大值默认分别为0和99。

常用方法

  • setRange(): 设置进度条的取值范围(最小值和最大值)
  • setMinimum(): 设置进度条的最小值
  • setFont(): 设置文本字体
  • setMaximum(): 设置进度条的最大值
  • setValue(): 设置进度条的值
  • reset(): 让进度条重新回到开始位置
  • setOrientation(): 设置进度条方向(水平: Qt.Horizontal, 垂直: Qt.Vertical)
  • setTextVisible(): 设置进度条的文本是否可见
  • setTextDirection(): 设置文本方向,只对垂直进度条有效
  • setInvertedAppearance(): 设置进度条的方向(True/False: 正反方向)
  • setFormat(): 设置文本字符串的格式(%p, 百分比显示,这是默认情况, %v: 当前进度, %m :总步数)

实例程序如下

import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QProgressBar
from PyQt5.QtCore import QBasicTimer
from PyQt5.QtGui import QFont

class MyClass(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.resize(500, 300)
        # 载入进度条控件
        self.pgb = QProgressBar(self)
        self.pgb.move(50, 50)
        self.pgb.resize(250, 20)
        self.pgb.setStyleSheet("QProgressBar  border: 2px solid grey; border-radius: 5px; color: rgb(20,20,20);  background-color: #FFFFFF; text-align: center;QProgressBar::chunk background-color: rgb(100,200,200); border-radius: 10px; margin: 0.1px;  width: 1px;")
        ## 其中 width 是设置进度条每一步的宽度
        ## margin 设置两步之间的间隔
        #设置字体
        font = QFont()
        font.setBold(True)
        font.setWeight(30)
        self.pgb.setFont(font)
        # 设置一个值表示进度条的当前进度
        self.pv = 0
        # 申明一个时钟控件
        self.timer1 = QBasicTimer()

        # 设置进度条的范围
        self.pgb.setMinimum(0)
        self.pgb.setMaximum(100)
        self.pgb.setValue(self.pv)
        ## 设置进度条文字格式
        self.pgb.setFormat('Loaded  %p%'.format(self.pgb.value()-self.pgb.minimum()))
        # 加载pushbutton1
        self.btn_start = QPushButton("begin", self)
        self.btn_start.move(50, 100)
        self.btn_start.clicked.connect(self.myTimerState)
        # 加载 pushbutton 2
        self.btn_update = QPushButton("update", self)
        self.btn_update.move(150, 100)
        self.btn_update.clicked.connect(self.update_event)
        
    def myTimerState(self):
        if self.timer1.isActive():
            self.timer1.stop()
            self.btn_start.setText("begin")
        else:
            self.timer1.start(100, self)
            self.btn_start.setText("stop")

    def timerEvent(self, e):
        if self.pv == 100:
            self.timer1.stop()
            self.btn_start.setText("Finish")
        else:
            self.pv += 1
            self.pgb.setValue(self.pv)

    def update_event(self):
        if self.timer1.isActive():
            self.timer1.stop()
        self.btn_start.setText("begin")
        self.pv = 0
        self.pgb.setValue(self.pv)
        
if __name__ == "__main__":
    app = QApplication(sys.argv)
    mytask = MyClass()
    mytask.show()
    app.exec_()

效果如下:

设置进度条颜色为渐变色

## 添加模块函数
from PyQt5.QtGui import QLinearGradient

self.pgb.setStyleSheet("QProgressBar  border: 2px solid grey; border-radius: 5px; background-color: #FFFFFF; text-align: center;QProgressBar::chunk background:QLinearGradient(x1:0,y1:0,x2:2,y2:0,stop:0 #666699,stop:1  #DB7093); ")

其中可以调整x2:2 的数值,比如改为x2:1会使得渐变色变化速度更快且最终的颜色最深。
效果如下:


End

Pyqt5进度条QProgressBar的使用/多线程更新/按钮美化/图片编码/开机自启动

前言

诚如标题所见,我在使用Pyqt5进行开发时,先后遇到了上面几个问题。本篇博客就用来记录遇到问题/解决问题的过程,希望能给遇到相同问题的读者一些参考。

项目背景

我的项目是构建一个可视化的交互界面,通过界面上的按钮可调用后台的爬虫程序。因此,需要通过添加一个进度条来反映当前的爬取进度。

进度条

Pyqt5设有进度条控件QProgressBar,官方文档提供了一个按钮驱动定时器加载进度条的例子。本次应用和官方例程略有不同。

进度条创建与样式设定

首先创建一个进度条,设定位置,并用QSS设定样式。

self.pb = QtWidgets.QProgressBar(self.centralwidget)
self.pb.setGeometry(QtCore.QRect(230, 690, 1021, 41))
self.pb.setStyleSheet("QProgressBar border: 2px solid grey; border-radius: 5px; background-color: #FFFFFF; text-align:center; font-size:20px")

这里的样式主要是对进度条外框进行修改,默认情况进度字体显示在进度条右侧,设置后将字体居中在进度条内,进度条则使用默认情况的绿色进度条,自带了动态加载光效,效果如图所示。

之后,设置进度条的范围[0,100],并将进度条在默认情况下进行隐藏。

self.pb.setRange(0, 100)
self.pb.hide() 

进度条更新

使用pyqt5独特的信号与槽函数可进行进度条的更新。进度条设置函数setvalue()
由于进度条总长度是未知的,因此首先在进度条开始更新之前,需要先获取总任务量的数据,然后将完成任务量/总任务量,映射到[0,100]的区间内进行更新。
此外,还需要获取一个信号来标记是否结束,如果结束,则立刻将进度条设置为100%,同时弹出提示信息(本程序是弹出一个提示框)

定义两个信号:
progressBarValue:用来回传当前换算后的进度数值
signal_done:用来回传完成标记(由于pyqtSignal无法回传bool型数据,采用int型来进行区分。0表示未完成,1表示完成)

对应两个槽函数:
callback:接收progressBarValue信号
callback_done:接收signal_done信号

更新逻辑:初始进度条为隐藏状态,点击按钮,进度条进行显示,并设定初始值为0。当所有链接获取完之后,进度条开始逐渐更新(每间隔十个数据进行一次进度条更新)。若全部爬取完成(signal_done发送1信号),进度条填满,并弹出提示框。
核心代码如下:

 # 两个参数初始化
 self.pb.setValue(0)  # 设置进度条为0
 self.is_done = 0  # 设置完成标记 完成/未完成 1/0
 self.progressBarValue.connect(self.callback)
 self.signal_done.connect(self.callback_done)
 
# 回传进度条参数
def callback(self, i):
    self.pb.setValue(i)

# 回传结束信号
def callback_done(self, i):
    self.is_done = i
    if self.is_done == 1:
        self.messageDialog1()

progressBarValue = pyqtSignal(int)  # 更新进度条
signal_done = pyqtSignal(int)  # 是否结束信号

Linklist_sum = self.pro_name.get_crawler_link()
length = len(Linklist_sum)
for start in range(0, length, 10):
	self.pro_name.run_crawlertask(start, Linklist_sum)
	self.progressBarValue.emit(int(start / length * 100))  # 发送进度条的值信号
self.signal_done.emit(1)  # 发送结束信号

def messageDialog1(self):
    msg_box = QMessageBox(QMessageBox.Information, '通知', '信息爬取已结束')
    self.pb.setValue(100)  # 如果爬取成功
    msg_box.exec_()

多线程更新

直接将进度条更新的程序段和要调用的程序段放在一起会出现一个问题。当调用程序段运行时,qt界面会卡住不动,造成“假死”现象。
因此,要解决这个问题,就要引入多线程。将后台程序放入到一个子线程中运行,同时将数值传递给主线程,在主线程中进行UI的更新。
修改后的进度条更新程序段如下:

# 封装调用子线程执行程序name
def run_py(self, name):
    # 两个参数初始化
    self.pb.setValue(0)  # 设置进度条为0
    self.is_done = 0  # 设置完成标记 完成/未完成 1/0
    self.thread_1 = Runthread(pro_name=name)
    self.thread_1.progressBarValue.connect(self.callback)
    self.thread_1.signal_done.connect(self.callback_done)
    self.thread_1.start()

# 回传进度条参数
def callback(self, i):
    self.pb.setValue(i)

# 回传结束信号
def callback_done(self, i):
    self.is_done = i
    if self.is_done == 1:
        self.messageDialog1()

# Runthread子线程
class Runthread(QThread):
    progressBarValue = pyqtSignal(int)  # 更新进度条
    signal_done = pyqtSignal(int)  # 是否结束信号

    def __init__(self, pro_name):
        super(Runthread, self).__init__()
        self.pro_name = pro_name

    def run(self):
        Linklist_sum = self.pro_name.get_crawler_link()
        length = len(Linklist_sum)
        for start in range(0, length, 10):
            self.pro_name.run_crawlertask(start, Linklist_sum)
            self.progressBarValue.emit(int(start / length * 100))  # 发送进度条的值信号
        self.signal_done.emit(1)  # 发送结束信号

按钮美化

甲方要求我做一个科技风格的按钮,然而没给我设计贴图,于是我采用QSS的qlineargradient实现渐变填充,先看效果。
常规状态:

鼠标悬浮状态:边框变红

鼠标按下状态:字体下沉

相关代码:

font = QtGui.QFont()
font.setFamily("方正粗黑宋简体")
font.setPointSize(18)
self.pushButton.setFont(font)
self.pushButton.setObjectName("pushButton")
self.pushButton.setStyleSheet("QPushButtonbackground:qlineargradient(spread:reflect, x1:0, y1:1, x2:0, y2:0, "
                             "stop:0 #0a4a83, stop:0.5 #186e99, stop:1 #239cd8);"
                             "border:2px solid qlineargradient(spread:pad, x1:1, y1:1, x2:1, y2:0,"
                             "stop:0 #002aff, stop:0.5 #00aeff, stop:1 #00e6fc); border-radius:1px; "
                             "color:#d0f2f5 "
                             "QPushButton:hover:!pressed border:1px solid #f8878f;"
                             "QPushButton:pressed padding-left:6px;padding-top:6px;border:1px solid #f8878f;")

另外提供另一种美化示例:

相关代码

self.pushButton.setStyleSheet("QPushButtonbackground:qlineargradient(spread:pad, x1:0, y1:1, x2:0, y2:0, "
                              "stop:0 #00ffff, stop:0.5 #1D2B56, stop:1 "
                              "#00ffff);border:0px;border-radius:1px;color:white;")

选择框美化

顺带一提选择框美化,我的界面风格是暗黑系的,有一个开源的QSS风格qdarkstyle可以直接套用。

import qdarkstyle

self.comboBox = QtWidgets.QComboBox(self.centralwidget)
self.comboBox.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())  # 设置暗黑风格

当然,也可以全局配置该风格。

app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())

但是我觉得该风格的按钮设计没上面自己设计的靓眼,因此局部应用了该风格。

图片编码

如果在pyqt中的界面使用了贴图,在用pyinstaller打包成exe文件后,如果贴图和exe文件不在同一路径下,则会无法显示。
为了解决这一问题,可以使用pyqt自带的Pyqrc将图片资源编码成二进制数据,从而能够一起打包进exe文件。

首先建立文件img.qrc
将用到的图片写进去,比如,我用到了四张png图片。

<RCC>
  <qresource prefix="png">
    <file>images/title_bg.png</file>
    <file>images/button_bg.png</file>
    <file>images/bg.png</file>
    <file>images/2.png</file>
  </qresource>
</RCC>

之后,在pycharm中配置Pyqrc,配置方法可以参见这篇博客PyCharm中配置与PyQT5相关的External tools
然后,就能在pycharm中快速使用pyqrc进行转换。

转换之后,会生成img_rc.py文件。
在引用贴图的py文件中,引入该文件即可。

import img_rc

再次进行打包,生成的exe即包含图片信息。

开机自启动

exe文件完成了,甲方又给我提了最后一条需求,要求能够设置开机自启动。
这里,我提供两种方法。

添加注册表方法

第一种方式稍微复杂一些,通过python程序,将生成的exe添加到系统的启动注册表内。

import win32api
import win32con
import sys
import os

sys.setrecursionlimit(1000000)
name = 'auto_run'
cur_path = os.getcwd()
path = cur_path + '\\\\' '你的程序名.exe'
KeyName = r'Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run'

try:
    key = win32api.RegOpenKey(win32con.HKEY_CURRENT_USER, KeyName, 0, win32con.KEY_ALL_ACCESS)
    win32api.RegSetValueEx(key, name, 0, win32con.REG_SZ, path)
    win32api.RegCloseKey(key)
except:
    print('error!')
print('success!')

运行之后输出success即添加成功。
打开系统注册表和任务管理器,可以看到添加的内容。

如果需要关闭,在任务管理器内设置禁用即可。

bat脚本方法

正常来说,上面那种方法能够实现开机自启动,但是如果exe有个功能是打开当前程序文件夹,该方法会出现问题。开机启动后,打开当前文件夹会诡异地定位到C盘的system32文件夹里。
因此,有了下面这个方式,不仅可以解决这个问题,并且更加方便。

Windows在C盘中提供了一个启动文件夹(win+R:输入shell:startup即可进入),程序放入该文件夹中后,开机就能自动启动程序。由于我的程序涉及打开当前文件夹的操作,因此不能直接将程序放进去,而是将程序的快捷方式放进去。

首先创建快捷方式,命名为"shortcut"。
然后创建bat脚本,输入

xcopy "shortcut.lnk" "C:\\Users\\%username%\\AppData\\Roaming\\Microsoft\\Windows\\Start Menu\\Programs\\Startup" /Y

这里使用xcopy命令进行快捷方式的复制,其中/Y表示目标存在同名文件的情况取消提示以确认要覆盖。xcopy的更多参数设置可参考批处理(bat)xcopy详解
另外,创建快捷方式后的扩展名lnk不会直接显示,但在写脚本中需要进行补充。
如果快捷方式中有中文,需要将bat脚本内容改成ANSI编码,修改方式可参考Bat批处理命令执行中文路径方法

以上是关于PyQt5之进度条:QProgressBar的主要内容,如果未能解决你的问题,请参考以下文章

python中pyqt5的进度条--python实战

PyQt5-Qt DesignerQProgressBar() 进度条

Python PyQt5 进度条没有进展

Pyqt5进度条QProgressBar的使用/多线程更新/按钮美化/图片编码/开机自启动

根据从导入包中打印的标准输出更新 PyQt 进度条(PyQt5)

python pyqt5进度条指示器[重复]