PyQt progress jumps to 100

2019-01-15 17:47发布

问题:

When I run the code in the in the doWork method, by clicking the button1, the progress bar works as expected.

However, when I pass the list to the doWork method from other methods (i.e. btn2, btn3), the progress bar just jumps to 100% after it starts.

from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
import sys
from selenium import webdriver

class SeleniumWorker(QtCore.QObject):
    progressChanged = QtCore.pyqtSignal(int)
    def doWork(self, lst=['http://www.somesite.com/',
        'http://www.somesite.com/page2',
        'http://www.somesite.com/page3']):
        progress = 0
        browser = webdriver.Firefox()
        links = lst
        for link in links:
            browser.get(link)
            progress += 100 / len(links)
            self.progressChanged.emit(progress)
        browser.close()

class Widget(QtWidgets.QWidget):
    def __init__(self, *args, **kwargs):
        QtWidgets.QWidget.__init__(self, *args, **kwargs)
        lay = QtWidgets.QHBoxLayout(self)
        progressBar = QtWidgets.QProgressBar()
        progressBar.setRange(0, 100)
        button1 = QtWidgets.QPushButton("Start1")
        button2 = QtWidgets.QPushButton("Start2")
        button3 = QtWidgets.QPushButton("Start3")
        lay.addWidget(progressBar)
        lay.addWidget(button1)
        lay.addWidget(button2)
        lay.addWidget(button3)
        self.thread = QtCore.QThread()
        self.worker = SeleniumWorker()
        self.worker.moveToThread(self.thread)
        self.thread.started.connect(self.worker.doWork)
        button1.clicked.connect(self.thread.start)
        button2.clicked.connect(self.btn2)
        button3.clicked.connect(self.btn3)
        self.worker.progressChanged.connect(progressBar.setValue)


    def btn2(self):
        self.lst2 = ['http://www.somesite.com/page4',
        'http://www.somesite.com/page5',
        'http://www.somesite.com/page6']
        self.worker.doWork(self.lst2)

    def btn3(self):
        self.lst3 = ['http://www.somesite.com/page7',
        'http://www.somesite.com/page8',
        'http://www.somesite.com/page9']
        self.worker.doWork(self.lst3)


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    w = Widget()
    w.show()
    sys.exit(app.exec_())

回答1:

It seems that you have not understood the logic of my previous solution, I will detail the procedure:

[1] self.thread = QtCore.QThread()
[2] self.worker = SeleniumWorker()
[3] self.worker.moveToThread(self.thread)
[4] self.worker.progressChanged.connect(progressBar.setValue, QtCore.Qt.QueuedConnection)
[5] self.thread.started.connect(self.worker.doWork)
  1. QThread is a thread handler, so an object of that class allows us to execute a task on a thread other than the main thread called the GUI thread.

  2. You are creating a SeleniumWorker object that has the doWork method, which is a blocking task that should not be executed in the GUI thread, and that will be achieved with the previous QThread.

  3. Since the function must be executed in another thread, the object that has that method must move to that other thread.

  4. The signal of the object that is in the other thread is connected to the QProgressBar that is in the GUI thread, this connection must be made with the flag QtCore.Qt.QueuedConnection.

  5. When the thread is started it will call the doWork function and since the self.worker object is in another thread then the function will also be executed in that other thread.


In your case in the code that is shown in the next part you are calling the doWork function when the thread has not yet started so it will be executed in the main thread.

def btn2(self):
    ...
    # main thread
    self.worker.doWork(self.lst2)

One way to pass the urls is through a setter method, and then start the thread. When the thread starts, it will call doWork, and when the doWork is executed, the progressChanged signal will be emited.

With all the above we obtain the following:

import sys

from PyQt5 import QtCore, QtGui, QtWidgets
from selenium import webdriver

class SeleniumWorker(QtCore.QObject):
    progressChanged = QtCore.pyqtSignal(int)
    started = QtCore.pyqtSignal()
    finished = QtCore.pyqtSignal()
    def setUrls(self, urls=['http://www.somesite.com/',
    'http://www.somesite.com/page2',
    'http://www.somesite.com/page3']):
        self.urls = urls

    def doWork(self):
        self.started.emit()
        progress = 0
        self.progressChanged.emit(progress)
        browser = webdriver.Firefox()
        links = self.urls
        for link in links:
            browser.get(link)
            progress += 100 / len(links)
            self.progressChanged.emit(progress)
        browser.close()
        self.finished.emit()

class Widget(QtWidgets.QWidget):
    def __init__(self, *args, **kwargs):
        QtWidgets.QWidget.__init__(self, *args, **kwargs)
        lay = QtWidgets.QHBoxLayout(self)
        self.progressBar = QtWidgets.QProgressBar()
        self.progressBar.setRange(0, 100)
        button1 = QtWidgets.QPushButton("Start1")
        button2 = QtWidgets.QPushButton("Start2")
        button3 = QtWidgets.QPushButton("Start3")
        lay.addWidget(self.progressBar)
        lay.addWidget(button1)
        lay.addWidget(button2)
        lay.addWidget(button3)
        self.thread = QtCore.QThread()
        self.worker = SeleniumWorker()
        self.worker.moveToThread(self.thread)
        self.worker.progressChanged.connect(self.progressBar.setValue, QtCore.Qt.QueuedConnection)
        self.thread.started.connect(self.worker.doWork)
        button1.clicked.connect(self.btn1)
        button2.clicked.connect(self.btn2)
        button3.clicked.connect(self.btn3)
        self.worker.finished.connect(self.on_finished)
        self.worker.started.connect(lambda: self.buttons_setEnable(False))

    def on_finished(self):
        self.buttons_setEnable(True)
        if self.thread.isRunning():
            self.thread.quit()
            self.thread.wait()

    def buttons_setEnable(self, enable):
        for btn in self.findChildren(QtWidgets.QPushButton):
            btn.setEnabled(enable)

    def btn1(self):
        self.worker.setUrls()
        self.thread.start()

    def btn2(self):
        lst2 = ['http://www.somesite.com/page4',
        'http://www.somesite.com/page5',
        'http://www.somesite.com/page6']
        self.worker.setUrls(lst2)
        self.thread.start()

    def btn3(self):
        lst3 = ['http://www.somesite.com/page7',
        'http://www.somesite.com/page8',
        'http://www.somesite.com/page9']
        self.worker.setUrls(lst3)
        self.thread.start()


if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    w = Widget()
    w.show()
    sys.exit(app.exec_())

Note: I have added the functionality of disabling the buttons while it is running as it is appropriate that the thread does not start again while it is working.



回答2:

In my opinion, you have very fast program. Slow down a little.

QThread.msleep(500)

. ...

    for link in links:
        browser.get(link)
        progress += 100 / len(links)
        self.progressChanged.emit(progress)

        QThread.msleep(500)                  # !!!