PyQt 启动后进度跃升至 100%

2022-01-12 00:00:00 python selenium pyqt pyqt5 qprogressbar

问题描述

当我在 doWork 方法中运行代码时,通过单击 button1,进度条按预期工作.

但是,当我将列表从其他方法(即 btn2btn3)传递给 doWork 方法时,进度条只是跳跃启动后达到 100%.

从 PyQt5 导入 QtCore、QtGui、QtWidgets从 PyQt5.QtWidgets 导入 *从 PyQt5.QtCore 导入 *从 PyQt5.QtGui 导入 *导入系统从硒导入网络驱动程序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']):进度 = 0浏览器 = webdriver.Firefox()链接 = lst对于链接中的链接:browser.get(链接)进度 += 100/len(链接)self.progressChanged.emit(进度)浏览器.close()类小部件(QtWidgets.QWidget):def __init__(self, *args, **kwargs):QtWidgets.QWidget.__init__(self, *args, **kwargs)放置 = QtWidgets.QHBoxLayout(self)progressBar = QtWidgets.QProgressBar()进度条.setRange(0, 100)button1 = QtWidgets.QPushButton("Start1")button2 = QtWidgets.QPushButton("Start2")button3 = QtWidgets.QPushButton("Start3")放置.addWidget(进度条)放置.addWidget(button1)放置.addWidget(按钮2)放置.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.lst2 = ['http://www.somesite.com/page4','http://www.somesite.com/page5','http://www.somesite.com/page6']self.worker.doWork(self.lst2)def btn3(自我):self.lst3 = ['http://www.somesite.com/page7','http://www.somesite.com/page8','http://www.somesite.com/page9']self.worker.doWork(self.lst3)如果 __name__ == '__main__':应用程序 = QtWidgets.QApplication(sys.argv)w = 小部件()w.show()sys.exit(app.exec_())

解决方案

看来你没有看懂我的逻辑以前的解决方案,我会详细说明过程:

[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 是一个线程处理程序,因此该类的对象允许我们在主线程以外的称为 GUI 线程的线程上执行任务.

    李>
  2. 您正在创建一个具有 doWork 方法的 SeleniumWorker 对象,这是一个不应在 GUI 线程中执行的阻塞任务,而这将通过之前的 QThread 来实现.

  3. 由于函数必须在另一个线程中执行,具有该方法的对象必须移动到另一个线程.

  4. 另一个线程中的对象的信号连接到GUI线程中的QProgressBar,这个连接必须使用标志QtCore.Qt.QueuedConnection.

  5. 当线程启动时,它会调用 doWork 函数,因为 self.worker 对象在另一个线程中,所以该函数也将在另一个线程中执行.

<小时>

在下一部分显示的代码中,您在线程尚未启动时调用 doWork 函数,因此它将在主线程中执行.

def btn2(self):...# 主线程self.worker.doWork(self.lst2)

<小时>

传递 url 的一种方法是通过 setter 方法,然后启动线程.线程启动时会调用doWork,当doWork执行时会发出progressChanged信号.

通过以上所有内容,我们获得以下内容:

导入系统从 PyQt5 导入 QtCore、QtGui、QtWidgets从硒导入网络驱动程序SeleniumWorker 类(QtCore.QObject):progressChanged = QtCore.pyqtSignal(int)开始 = QtCore.pyqtSignal()完成 = QtCore.pyqtSignal()def setUrls(self, urls=['http://www.somesite.com/','http://www.somesite.com/page2','http://www.somesite.com/page3']):self.urls = 网址定义做工作(自我):self.started.emit()进度 = 0self.progressChanged.emit(进度)浏览器 = webdriver.Firefox()链接 = self.urls对于链接中的链接:browser.get(链接)进度 += 100/len(链接)self.progressChanged.emit(进度)浏览器.close()self.finished.emit()类小部件(QtWidgets.QWidget):def __init__(self, *args, **kwargs):QtWidgets.QWidget.__init__(self, *args, **kwargs)放置 = QtWidgets.QHBoxLayout(self)self.progressBar = QtWidgets.QProgressBar()self.progressBar.setRange(0, 100)button1 = QtWidgets.QPushButton("Start1")button2 = QtWidgets.QPushButton("Start2")button3 = QtWidgets.QPushButton("Start3")放置.addWidget(self.progressBar)放置.addWidget(button1)放置.addWidget(按钮2)放置.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)如果 self.thread.isRunning():self.thread.quit()self.thread.wait()def button_setEnable(self, enable):对于self.findChildren(QtWidgets.QPushButton)中的btn:btn.setEnabled(启用)def btn1(自我):self.worker.setUrls()self.thread.start()def btn2(自我):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(自我):lst3 = ['http://www.somesite.com/page7','http://www.somesite.com/page8','http://www.somesite.com/page9']self.worker.setUrls(lst3)self.thread.start()如果 __name__ == '__main__':应用程序 = QtWidgets.QApplication(sys.argv)w = 小部件()w.show()sys.exit(app.exec_())

注意:我添加了在运行时禁用按钮的功能,因为线程在工作时不会再次启动是合适的.p>

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_())

解决方案

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.

相关文章