Python pyqt 多线程脉冲进度条
Python pyqt pulsing progress bar with multithreading
初学者请多多包涵。我在 pyqt 中实现进度条时遇到了问题,我看到的所有示例都没有真正解释如何正确实现它,因此 example and this example 我有点让它工作但它仍然挂起。我有这个代码:
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(750, 450, 400, 200)
self.setFixedSize(self.size())
btn1 = QtGui.QPushButton("Convert", self)
btn1.move(210,171)
btn1.clicked.connect(self.progbar)
def progbar (self):
self.prog_win = QDialog()
self.prog_win.resize(400, 100)
self.prog_win.setFixedSize(self.prog_win.size())
self.prog_win.setWindowTitle("Processing request")
self.lbl = QLabel(self.prog_win)
self.lbl.setText("Please Wait. . .")
self.lbl.move(15,18)
self.progressBar = QtGui.QProgressBar(self.prog_win)
self.progressBar.resize(410, 25)
self.progressBar.move(15, 40)
self.progressBar.setRange(0,1)
self.myLongTask = TaskThread()
#I think this is where I am wrong
#because all of the answers here is very specific
#or just not for beginners
self.prog_win.show()
self.myLongTask.taskFinished.connect(self.onStart)
self.output_settings()
def onStart(self):
self.progressBar.setRange(0,0)
self.myLongTask.start()
def output_convert(self):
#very long process to convert a txt file to excel
#My Thread
class TaskThread(QtCore.QThread):
taskFinished = QtCore.pyqtSignal()
def run(self):
time.sleep(3)
self.taskFinished.emit()
def run():
app = QtGui.QApplication(sys.argv)
GUI = Window()
app.exec_()
run()
此处的所有示例和帖子都对理解进度条的实现非常有帮助,但是由于所有示例都针对特定问题提供了具体的答案,所以我无法理解标准 pyqt 应用程序中进度条的实现。你们至少能指出我正确的方向吗?将不胜感激。
这是一个非常基本的进度条,仅使用最低限度所需的内容。
明智的做法是通读整个示例。
import sys
import time
from PyQt5.QtWidgets import (QApplication, QDialog,
QProgressBar, QPushButton)
TIME_LIMIT = 100
class Actions(QDialog):
"""
Simple dialog that consists of a Progress Bar and a Button.
Clicking on the button results in the start of a timer and
updates the progress bar.
"""
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('Progress Bar')
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 25)
self.progress.setMaximum(100)
self.button = QPushButton('Start', self)
self.button.move(0, 30)
self.show()
self.button.clicked.connect(self.onButtonClick)
def onButtonClick(self):
count = 0
while count < TIME_LIMIT:
count += 1
time.sleep(1)
self.progress.setValue(count)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
sys.exit(app.exec_())
进度条先这样导入from PyQt5.QtWidgets import QProgressBar
然后它像 QtWidgets
中的任何其他小部件一样被初始化
第self.progress.setGeometry(0, 0, 300, 25)
行方法定义了x,y
对话框中的位置和进度条的宽度和高度。
然后我们使用 .move()
将按钮向下移动 30px
,这样两个小部件之间就会有 5px
的间隙。
这里self.progress.setValue(count)
用来更新进度。使用 .setMaximum()
设置最大值也会自动为您计算值。例如,如果最大值设置为 50,那么由于 TIME_LIMIT
是 100,它将从 0% 跳到 2% 再到 4%,而不是每秒从 0 跳到 1% 到 2。您还可以使用 .setMinimum()
设置最小值,强制进度条从给定值开始。
执行该程序将产生与此类似的 GUI。
如您所见,在计数器满足 TIME_LIMIT
条件之前,GUI 肯定会冻结并且没有响应。这是因为 time.sleep
导致 OS 认为程序已陷入无限循环。
QThread
那么我们如何克服这个问题呢?我们可以使用PyQt5提供的线程class。
import sys
import time
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import (QApplication, QDialog,
QProgressBar, QPushButton)
TIME_LIMIT = 100
class External(QThread):
"""
Runs a counter thread.
"""
countChanged = pyqtSignal(int)
def run(self):
count = 0
while count < TIME_LIMIT:
count +=1
time.sleep(1)
self.countChanged.emit(count)
class Actions(QDialog):
"""
Simple dialog that consists of a Progress Bar and a Button.
Clicking on the button results in the start of a timer and
updates the progress bar.
"""
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('Progress Bar')
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 25)
self.progress.setMaximum(100)
self.button = QPushButton('Start', self)
self.button.move(0, 30)
self.show()
self.button.clicked.connect(self.onButtonClick)
def onButtonClick(self):
self.calc = External()
self.calc.countChanged.connect(self.onCountChanged)
self.calc.start()
def onCountChanged(self, value):
self.progress.setValue(value)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
sys.exit(app.exec_())
让我们分解这些修改。
from PyQt5.QtCore import QThread, pyqtSignal
这一行导入了Qthread
,这是一个PyQt5
实现,用于在后台划分和运行程序的某些部分(例如:函数,classes) (也称为多线程)。这些部分也称为线程。默认情况下,所有 PyQt5
程序都有一个主线程,其他(工作线程)用于将额外耗时和处理密集型任务卸载到后台,同时仍保持主程序运行。
第二个导入pyqtSignal
用于在工作线程和主线程之间发送数据(信号)。在这种情况下,我们将使用它来告诉主线程更新进度条。
现在我们已将计数器的 while 循环移动到一个名为 External
的单独 class 中。
class External(QThread):
"""
Runs a counter thread.
"""
countChanged = pyqtSignal(int)
def run(self):
count = 0
while count < TIME_LIMIT:
count +=1
time.sleep(1)
self.countChanged.emit(count)
通过子classing QThread
,我们实质上是将External
转换为class,可以在单独的线程中运行。线程也可以随时启动或停止,这增加了它的好处。
这里countChanged
是当前进度,pyqtSignal(int)
告诉工作线程发送的信号是int
类型。而 self.countChanged.emit(count)
只是将信号发送到主线程中的任何连接(通常它也可以用于与其他工作线程通信)。
def onButtonClick(self):
self.calc = External()
self.calc.countChanged.connect(self.onCountChanged)
self.calc.start()
def onCountChanged(self, value):
self.progress.setValue(value)
单击按钮时,self.onButtonClick
将 运行 并启动线程。线程以 .start()
开始。还应该注意的是,我们将之前创建的信号 self.calc.countChanged
连接到用于更新进度条值的方法。每次更新 External::run::count
时,int
值也会发送到 onCountChanged
。
这是进行这些更改后 GUI 的样子。
它也应该感觉更灵敏并且不会冻结。
我自己的问题的答案。如果你能理解线程和通过 classes 传递变量的概念,这并不难。我的第一个错误实际上是缺乏关于 Worker
线程的知识,第二个是我认为一旦你声明 Thread
就意味着你只需要调用它就可以 运行函数在主 class 中,所以我正在搜索你将如何实现它,但我认为是错误的。
解决方案
所有 hard/Long 进程 应该 在 def run
下的子 classed QThread
中并且应该在 def run
中调用您的 class Window(QtGui.QMainWindow):
或主循环,这就是我的代码现在的样子
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(750, 450, 400, 200)
self.setFixedSize(self.size())
btn1 = QtGui.QPushButton("Convert", self)
btn1.move(210,171)
btn1.clicked.connect(self.progbar)
def progbar (self):
self.prog_win = QDialog()
self.prog_win.resize(400, 100)
self.prog_win.setFixedSize(self.prog_win.size())
self.prog_win.setWindowTitle("Processing request")
self.lbl = QLabel(self.prog_win)
self.lbl.setText("Please Wait. . .")
self.lbl.move(15,18)
self.progressBar = QtGui.QProgressBar(self.prog_win)
self.progressBar.resize(410, 25)
self.progressBar.move(15, 40)
self.progressBar.setRange(0,1)
self.myLongTask = TaskThread(var = DataYouWantToPass) #initializing and passing data to QThread
self.prog_win.show()
self.onStart() #Start your very very long computation/process
self.myLongTask.taskFinished.connect(self.onFinished) #this won't be read until QThread send a signal i think
def onStart(self):
self.progressBar.setRange(0,0)
self.myLongTask.start()
#added this function to close the progress bar
def onFinished(self):
self.progressBar.setRange(0,1)
self.prog_win.close()
#My Thread
class TaskThread(QtCore.QThread):
taskFinished = QtCore.pyqtSignal()
#I also added this so that I can pass data between classes
def __init__(self, var, parent=None):
QThread.__init__(self, parent)
self.var = var
def run(self):
#very long process to convert a txt file to excel
def run():
app = QtGui.QApplication(sys.argv)
GUI = Window()
app.exec_()
run()
如果这个答案中的某些内容是错误的,请纠正我,因为这将有助于更好地理解它,或者可能是一些该做的和不该做的
初学者请多多包涵。我在 pyqt 中实现进度条时遇到了问题,我看到的所有示例都没有真正解释如何正确实现它,因此 example and this example 我有点让它工作但它仍然挂起。我有这个代码:
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(750, 450, 400, 200)
self.setFixedSize(self.size())
btn1 = QtGui.QPushButton("Convert", self)
btn1.move(210,171)
btn1.clicked.connect(self.progbar)
def progbar (self):
self.prog_win = QDialog()
self.prog_win.resize(400, 100)
self.prog_win.setFixedSize(self.prog_win.size())
self.prog_win.setWindowTitle("Processing request")
self.lbl = QLabel(self.prog_win)
self.lbl.setText("Please Wait. . .")
self.lbl.move(15,18)
self.progressBar = QtGui.QProgressBar(self.prog_win)
self.progressBar.resize(410, 25)
self.progressBar.move(15, 40)
self.progressBar.setRange(0,1)
self.myLongTask = TaskThread()
#I think this is where I am wrong
#because all of the answers here is very specific
#or just not for beginners
self.prog_win.show()
self.myLongTask.taskFinished.connect(self.onStart)
self.output_settings()
def onStart(self):
self.progressBar.setRange(0,0)
self.myLongTask.start()
def output_convert(self):
#very long process to convert a txt file to excel
#My Thread
class TaskThread(QtCore.QThread):
taskFinished = QtCore.pyqtSignal()
def run(self):
time.sleep(3)
self.taskFinished.emit()
def run():
app = QtGui.QApplication(sys.argv)
GUI = Window()
app.exec_()
run()
此处的所有示例和帖子都对理解进度条的实现非常有帮助,但是由于所有示例都针对特定问题提供了具体的答案,所以我无法理解标准 pyqt 应用程序中进度条的实现。你们至少能指出我正确的方向吗?将不胜感激。
这是一个非常基本的进度条,仅使用最低限度所需的内容。
明智的做法是通读整个示例。
import sys
import time
from PyQt5.QtWidgets import (QApplication, QDialog,
QProgressBar, QPushButton)
TIME_LIMIT = 100
class Actions(QDialog):
"""
Simple dialog that consists of a Progress Bar and a Button.
Clicking on the button results in the start of a timer and
updates the progress bar.
"""
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('Progress Bar')
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 25)
self.progress.setMaximum(100)
self.button = QPushButton('Start', self)
self.button.move(0, 30)
self.show()
self.button.clicked.connect(self.onButtonClick)
def onButtonClick(self):
count = 0
while count < TIME_LIMIT:
count += 1
time.sleep(1)
self.progress.setValue(count)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
sys.exit(app.exec_())
进度条先这样导入from PyQt5.QtWidgets import QProgressBar
然后它像 QtWidgets
第self.progress.setGeometry(0, 0, 300, 25)
行方法定义了x,y
对话框中的位置和进度条的宽度和高度。
然后我们使用 .move()
将按钮向下移动 30px
,这样两个小部件之间就会有 5px
的间隙。
这里self.progress.setValue(count)
用来更新进度。使用 .setMaximum()
设置最大值也会自动为您计算值。例如,如果最大值设置为 50,那么由于 TIME_LIMIT
是 100,它将从 0% 跳到 2% 再到 4%,而不是每秒从 0 跳到 1% 到 2。您还可以使用 .setMinimum()
设置最小值,强制进度条从给定值开始。
执行该程序将产生与此类似的 GUI。
如您所见,在计数器满足 TIME_LIMIT
条件之前,GUI 肯定会冻结并且没有响应。这是因为 time.sleep
导致 OS 认为程序已陷入无限循环。
QThread
那么我们如何克服这个问题呢?我们可以使用PyQt5提供的线程class。
import sys
import time
from PyQt5.QtCore import QThread, pyqtSignal
from PyQt5.QtWidgets import (QApplication, QDialog,
QProgressBar, QPushButton)
TIME_LIMIT = 100
class External(QThread):
"""
Runs a counter thread.
"""
countChanged = pyqtSignal(int)
def run(self):
count = 0
while count < TIME_LIMIT:
count +=1
time.sleep(1)
self.countChanged.emit(count)
class Actions(QDialog):
"""
Simple dialog that consists of a Progress Bar and a Button.
Clicking on the button results in the start of a timer and
updates the progress bar.
"""
def __init__(self):
super().__init__()
self.initUI()
def initUI(self):
self.setWindowTitle('Progress Bar')
self.progress = QProgressBar(self)
self.progress.setGeometry(0, 0, 300, 25)
self.progress.setMaximum(100)
self.button = QPushButton('Start', self)
self.button.move(0, 30)
self.show()
self.button.clicked.connect(self.onButtonClick)
def onButtonClick(self):
self.calc = External()
self.calc.countChanged.connect(self.onCountChanged)
self.calc.start()
def onCountChanged(self, value):
self.progress.setValue(value)
if __name__ == "__main__":
app = QApplication(sys.argv)
window = Actions()
sys.exit(app.exec_())
让我们分解这些修改。
from PyQt5.QtCore import QThread, pyqtSignal
这一行导入了Qthread
,这是一个PyQt5
实现,用于在后台划分和运行程序的某些部分(例如:函数,classes) (也称为多线程)。这些部分也称为线程。默认情况下,所有 PyQt5
程序都有一个主线程,其他(工作线程)用于将额外耗时和处理密集型任务卸载到后台,同时仍保持主程序运行。
第二个导入pyqtSignal
用于在工作线程和主线程之间发送数据(信号)。在这种情况下,我们将使用它来告诉主线程更新进度条。
现在我们已将计数器的 while 循环移动到一个名为 External
的单独 class 中。
class External(QThread):
"""
Runs a counter thread.
"""
countChanged = pyqtSignal(int)
def run(self):
count = 0
while count < TIME_LIMIT:
count +=1
time.sleep(1)
self.countChanged.emit(count)
通过子classing QThread
,我们实质上是将External
转换为class,可以在单独的线程中运行。线程也可以随时启动或停止,这增加了它的好处。
这里countChanged
是当前进度,pyqtSignal(int)
告诉工作线程发送的信号是int
类型。而 self.countChanged.emit(count)
只是将信号发送到主线程中的任何连接(通常它也可以用于与其他工作线程通信)。
def onButtonClick(self):
self.calc = External()
self.calc.countChanged.connect(self.onCountChanged)
self.calc.start()
def onCountChanged(self, value):
self.progress.setValue(value)
单击按钮时,self.onButtonClick
将 运行 并启动线程。线程以 .start()
开始。还应该注意的是,我们将之前创建的信号 self.calc.countChanged
连接到用于更新进度条值的方法。每次更新 External::run::count
时,int
值也会发送到 onCountChanged
。
这是进行这些更改后 GUI 的样子。
它也应该感觉更灵敏并且不会冻结。
我自己的问题的答案。如果你能理解线程和通过 classes 传递变量的概念,这并不难。我的第一个错误实际上是缺乏关于 Worker
线程的知识,第二个是我认为一旦你声明 Thread
就意味着你只需要调用它就可以 运行函数在主 class 中,所以我正在搜索你将如何实现它,但我认为是错误的。
解决方案
所有 hard/Long 进程 应该 在 def run
下的子 classed QThread
中并且应该在 def run
中调用您的 class Window(QtGui.QMainWindow):
或主循环,这就是我的代码现在的样子
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.setGeometry(750, 450, 400, 200)
self.setFixedSize(self.size())
btn1 = QtGui.QPushButton("Convert", self)
btn1.move(210,171)
btn1.clicked.connect(self.progbar)
def progbar (self):
self.prog_win = QDialog()
self.prog_win.resize(400, 100)
self.prog_win.setFixedSize(self.prog_win.size())
self.prog_win.setWindowTitle("Processing request")
self.lbl = QLabel(self.prog_win)
self.lbl.setText("Please Wait. . .")
self.lbl.move(15,18)
self.progressBar = QtGui.QProgressBar(self.prog_win)
self.progressBar.resize(410, 25)
self.progressBar.move(15, 40)
self.progressBar.setRange(0,1)
self.myLongTask = TaskThread(var = DataYouWantToPass) #initializing and passing data to QThread
self.prog_win.show()
self.onStart() #Start your very very long computation/process
self.myLongTask.taskFinished.connect(self.onFinished) #this won't be read until QThread send a signal i think
def onStart(self):
self.progressBar.setRange(0,0)
self.myLongTask.start()
#added this function to close the progress bar
def onFinished(self):
self.progressBar.setRange(0,1)
self.prog_win.close()
#My Thread
class TaskThread(QtCore.QThread):
taskFinished = QtCore.pyqtSignal()
#I also added this so that I can pass data between classes
def __init__(self, var, parent=None):
QThread.__init__(self, parent)
self.var = var
def run(self):
#very long process to convert a txt file to excel
def run():
app = QtGui.QApplication(sys.argv)
GUI = Window()
app.exec_()
run()
如果这个答案中的某些内容是错误的,请纠正我,因为这将有助于更好地理解它,或者可能是一些该做的和不该做的