Python PyQt 进度条忙指示

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/19442443/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-19 13:45:39  来源:igfitidea点击:

Busy indication with PyQt progress bar

pythonpyqtpyqt4progress

提问by user2420437

I'm trying to write a script which will display a busy indication while performing the task. And when the task is over, the progress bar will fill to the end showing that 100% task has been completed. I just want the progress bar to show a task is going on.But when I start the task, busy indication stops.It seems to me that the indication and the task can not continue together. Please help me. Here's mycode:

我正在尝试编写一个脚本,该脚本将在执行任务时显示忙碌指示。当任务结束时,进度条会填充到最后,显示任务已完成 100%。我只是想让进度条显示一个任务正在进行。但是当我开始任务时,忙指示停止了。在我看来,指示和任务不能一起继续。请帮我。这是我的代码:

from PyQt4 import QtCore, QtGui
from time import sleep
try:
    _fromUtf8 = QtCore.QString.fromUtf8
except AttributeError:
    def _fromUtf8(s):
        return s

try:
    _encoding = QtGui.QApplication.UnicodeUTF8
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig, _encoding)
except AttributeError:
    def _translate(context, text, disambig):
        return QtGui.QApplication.translate(context, text, disambig)

class Ui_MainWindow(object):
    def setupUi(self, MainWindow):
        MainWindow.setObjectName(_fromUtf8("MainWindow"))
        MainWindow.resize(344, 159)
        self.centralwidget = QtGui.QWidget(MainWindow)
        self.centralwidget.setObjectName(_fromUtf8("centralwidget"))
        self.pb = QtGui.QProgressBar(self.centralwidget)
        self.pb.setGeometry(QtCore.QRect(20, 20, 301, 31))
        self.pb.setProperty("value", 0)
        self.pb.setObjectName(_fromUtf8("pb"))
        self.btn = QtGui.QPushButton(self.centralwidget)
        self.btn.setGeometry(QtCore.QRect(20, 70, 98, 27))
        self.btn.setObjectName(_fromUtf8("btn"))
        MainWindow.setCentralWidget(self.centralwidget)
        self.menubar = QtGui.QMenuBar(MainWindow)
        self.menubar.setGeometry(QtCore.QRect(0, 0, 344, 25))
        self.menubar.setObjectName(_fromUtf8("menubar"))
        MainWindow.setMenuBar(self.menubar)
        self.statusbar = QtGui.QStatusBar(MainWindow)
        self.statusbar.setObjectName(_fromUtf8("statusbar"))
        MainWindow.setStatusBar(self.statusbar)

        self.retranslateUi(MainWindow)
        QtCore.QObject.connect(self.btn, QtCore.SIGNAL(_fromUtf8("clicked()")), self.action)
        QtCore.QMetaObject.connectSlotsByName(MainWindow)            

    def action(self):
        self.pb.setRange(0, 0)
        sleep(3) # Here I want to run a command.For example: os.system('copy something')
        self.pb.setRange(0, 100)
        self.pb.setValue(100)
        QtGui.qApp.processEvents()

    def retranslateUi(self, MainWindow):
        MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow", None))
        self.btn.setText(_translate("MainWindow", "Start", None))


if __name__ == "__main__":
    import sys
    app = QtGui.QApplication(sys.argv)
    MainWindow = QtGui.QMainWindow()
    ui = Ui_MainWindow()
    ui.setupUi(MainWindow)
    MainWindow.show()
    sys.exit(app.exec_())

采纳答案by Yoann Quenach de Quivillic

First, it's a bad idea to directly edit the code created with QtDesigner. You may have seen the line # WARNING! All changes made in this file will be lost!at the top of the document. For such a simple widget, you're better off with manual coding.

首先,直接编辑用 QtDesigner 创建的代码是个坏主意。您可能已经看到# WARNING! All changes made in this file will be lost!文档顶部的一行。对于这样一个简单的小部件,最好使用手动编码。

Secondly, take a closer look at what the actionslot actually does.

其次,仔细看看action插槽的实际作用。

def action(self):
    self.pb.setRange(0, 0) # Un
    sleep(3) # <-- Your slot blocks HERE
    self.pb.setRange(0, 100)
    self.pb.setValue(100)
    QtGui.qApp.processEvents()

There is no reason for your progressBar to update its value while your slot is blocked in sleep. When actionis called, the slot thread sleeps for 3 sec then sets the progress bar to a full 100.

当您的插槽被阻止时,您的 progressBar 没有理由更新其值sleep。当action被调用时,槽线程休眠 3 秒,然后将进度条设置为完整的 100。

You can't expect the progressBar to magically update itself while your task is running. If you have no idea how long it will take and you can't subdivise it in steps, you should consider using a pulsedProgressBar instead (see example 1 below). If you can easily get the progression of your task (say copying n files), you should update the value of your progressBar accordingly.

您不能指望progressBar 在您的任务运行时神奇地自我更新。如果您不知道需要多长时间并且无法按步骤细分,则应考虑改用脉冲ProgressBar(请参见下面的示例 1)。如果您可以轻松获得任务的进度(例如复制 n 个文件),则应相应地更新 progressBar 的值。

Either way, you should use QThreadto get a non-blocking behaviour, and signalsto communicate between your thread(s) and your main application.

无论哪种方式,您都应该使用QThread来获得非阻塞行为,并signals在您的线程和主应用程序之间进行通信。

  • The main application starts the QThread implementing the long-running task.
  • The QThread notifies the task progression (if available) or completion to the main application
  • 主应用程序启动执行长时间运行任务的 QThread。
  • QThread 通知主应用程序的任务进展(如果可用)或完成


Example 1 - Pulse ProgressBar:

示例 1 - Pulse ProgressBar:

If minimum and maximum are both set to 0, the progress bar will show a busy indicator instead of a percentage of steps.

如果最小值和最大值都设置为 0,则进度条将显示忙指示符而不是步数百分比。

class MyCustomWidget(QtGui.QWidget):

    def __init__(self, parent=None):
        super(MyCustomWidget, self).__init__(parent)
        layout = QtGui.QVBoxLayout(self)

        # Create a progress bar and a button and add them to the main layout
        self.progressBar = QtGui.QProgressBar(self)
        self.progressBar.setRange(0,1)
        layout.addWidget(self.progressBar)
        button = QtGui.QPushButton("Start", self)
        layout.addWidget(button)      

        button.clicked.connect(self.onStart)

        self.myLongTask = TaskThread()
        self.myLongTask.taskFinished.connect(self.onFinished)

    def onStart(self): 
        self.progressBar.setRange(0,0)
        self.myLongTask.start()

    def onFinished(self):
        # Stop the pulsation
        self.progressBar.setRange(0,1)


class TaskThread(QtCore.QThread):
    taskFinished = QtCore.pyqtSignal()
    def run(self):
        time.sleep(3)
        self.taskFinished.emit()  

Example 2 - Classic ProgressBar:

示例 2 - 经典进度条:

class MyCustomWidget(QtGui.QWidget):

    def __init__(self, parent=None):
        super(MyCustomWidget, self).__init__(parent)
        layout = QtGui.QVBoxLayout(self)       

        self.progressBar = QtGui.QProgressBar(self)
        self.progressBar.setRange(0,100)
        button = QtGui.QPushButton("Start", self)
        layout.addWidget(self.progressBar)
        layout.addWidget(button)

        button.clicked.connect(self.onStart)

        self.myLongTask = TaskThread()
        self.myLongTask.notifyProgress.connect(self.onProgress)


    def onStart(self):
        self.myLongTask.start()

    def onProgress(self, i):
        self.progressBar.setValue(i)


class TaskThread(QtCore.QThread):
    notifyProgress = QtCore.pyqtSignal(int)
    def run(self):
        for i in range(101):
            self.notifyProgress.emit(i)
            time.sleep(0.1)

回答by user2420437

Finally I got what I wanted, though little bit editing needed . I just added this line to onFinished(): self.progressBar.setValue(1) to confirm 100% task completion. Here's the code:

最后我得到了我想要的东西,虽然需要一点点编辑。我刚刚将此行添加到 onFinished(): self.progressBar.setValue(1) 以确认 100% 任务完成。这是代码:

from PyQt4 import QtCore, QtGui
from time import sleep
import sys, os

class MyCustomWidget(QtGui.QWidget):

    def __init__(self, parent=None):
        super(MyCustomWidget, self).__init__(parent)
        layout = QtGui.QVBoxLayout(self)

        # Create a progress bar and a button and add them to the main layout
        self.progressBar = QtGui.QProgressBar(self)
        self.progressBar.setRange(0,1)
        layout.addWidget(self.progressBar)
        button = QtGui.QPushButton("Start", self)
        layout.addWidget(button)      

        button.clicked.connect(self.onStart)

        self.myLongTask = TaskThread()
        self.myLongTask.taskFinished.connect(self.onFinished)

    def onStart(self): 
        self.progressBar.setRange(0,0)
        self.myLongTask.start()

    def onFinished(self):
        # Stop the pulsation
        self.progressBar.setRange(0,1)
        self.progressBar.setValue(1)


class TaskThread(QtCore.QThread):
    taskFinished = QtCore.pyqtSignal()
    def run(self):
        os.system('sudo apt-get install leafpad')
        self.taskFinished.emit() 

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    window = MyCustomWidget()
    window.resize(640, 480)
    window.show()
    sys.exit(app.exec_())

回答by Arturo Morales Rangel

apply this:

应用这个:

progressbar.setMinimum(0)
progressbar.setMaximum(0)
progressbar.setValue(0)

This setting will have a busy appearance, You do not need to add it to any function, it can be in the class constructor if you want

这个设置会有一个很忙的样子,你不需要把它添加到任何函数中,如果你愿意,它可以在类构造函数中

回答by daegontaven

A bit late, but I've written detailed documentation on this very issue since a lot of people seem to face this problem.

有点晚了,但我已经写了关于这个问题的详细文档,因为很多人似乎都面临这个问题。

Introduction to Progress Bars

进度条简介