Python Forum
[PyQt] Generate Progress Bar while executing a task
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
[PyQt] Generate Progress Bar while executing a task
#1
Hi All,

I want to popup a progress bar, while executing a task parallelly. Progress bar should display (as a popup) while clicking some button (from another widget) internally this button leads to execute some task. While executing this task parallelly progress bar popup should display the progress of this task and once task done (executed) and then progress bar pop up should disappear automatically.

I have got some thing from the google, but progress bar itself taking some time and once after disappear this progress bar and then only actual task started executing. Which I really do not want.

Any help/suggestion on this would really help.

Regards,
maiya
Reply
#2
It might be better to show the progress bar when needed, e.g. in the status bar. Here's an example

import sys
from PyQt5 import QtGui, QtWidgets


class Window(QtWidgets.QMainWindow):

    def __init__(self):
        super(Window, self).__init__()
        self.setGeometry(50, 50, 500, 100)
        self.setWindowTitle("ProgressBar")
        self.setWindowIcon(QtGui.QIcon.fromTheme('python3'))
        self.statusBar().showMessage("Ready", 0)

        self.btn = QtWidgets.QPushButton("Download",self)
        self.btn.move(10,10)
        self.btn.clicked.connect(self.download)
        self.progress_bar = QtWidgets.QProgressBar()
        self.statusBar().addPermanentWidget(self.progress_bar)
        self.progress_bar.hide()


    def download(self):
        self.completed = 0
        self.progress_bar.setFixedSize(self.geometry().width() - 120, 16)
        self.progress_bar.show()
        self.statusBar().showMessage("downloading ...", 0)

        while self.completed < 100:
            self.completed += 0.00005
            self.progress_bar.setValue(int(self.completed))
            
            if self.progress_bar.value() == 100:
                self.statusBar().showMessage("completed", 0)
                self.progress_bar.hide()
    
if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    win = Window()
    win.show()
    sys.exit(app.exec_())
Reply
#3
(Jul-12-2022, 02:27 PM)Axel_Erfurt Wrote: It might be better to show the progress bar when needed, e.g. in the status bar. Here's an example

import sys
from PyQt5 import QtGui, QtWidgets


class Window(QtWidgets.QMainWindow):

    def __init__(self):
        super(Window, self).__init__()
        self.setGeometry(50, 50, 500, 100)
        self.setWindowTitle("ProgressBar")
        self.setWindowIcon(QtGui.QIcon.fromTheme('python3'))
        self.statusBar().showMessage("Ready", 0)

        self.btn = QtWidgets.QPushButton("Download",self)
        self.btn.move(10,10)
        self.btn.clicked.connect(self.download)
        self.progress_bar = QtWidgets.QProgressBar()
        self.statusBar().addPermanentWidget(self.progress_bar)
        self.progress_bar.hide()


    def download(self):
        self.completed = 0
        self.progress_bar.setFixedSize(self.geometry().width() - 120, 16)
        self.progress_bar.show()
        self.statusBar().showMessage("downloading ...", 0)

        while self.completed < 100:
            self.completed += 0.00005
            self.progress_bar.setValue(int(self.completed))
            
            if self.progress_bar.value() == 100:
                self.statusBar().showMessage("completed", 0)
                self.progress_bar.hide()
    
if __name__ == '__main__':
    app = QtWidgets.QApplication(sys.argv)
    win = Window()
    win.show()
    sys.exit(app.exec_())
I do not need this download button here on this widget, but from the another widget. As soon as I press this download button from the another widget, it should start download a file, however at the same time it should popup a progress bar as an indication to the user that screen is not hang, but it is downloading..

Here in this example, what are you downloading?, which I could not see (or probably it is missing here). It is just a download button, but actually no downloading happening..

I want the real downloading and parallelly the progress bar too.

Any help? Thanks.

Regards,
maiya
Reply
#4
(Jul-12-2022, 08:54 PM)maiya Wrote: Here in this example, what are you downloading?

Nothing, it's just an example to show progressbar.

Here's an old example to make a real download with progressbar.

PyQt5_Downloader.py


If you want an extra popup for the progress bar you will have to put in a lot of effort to make it work.
Reply
#5
(Jul-12-2022, 02:27 PM)Axel_Erfurt Wrote: It might be better to show the progress bar when needed, e.g. in the status bar. Here's an example

    def download(self):
        self.completed = 0
        self.progress_bar.setFixedSize(self.geometry().width() - 120, 16)
        self.progress_bar.show()
        self.statusBar().showMessage("downloading ...", 0)

        while self.completed < 100:
            self.completed += 0.00005
            self.progress_bar.setValue(int(self.completed))
            
            if self.progress_bar.value() == 100:
                self.statusBar().showMessage("completed", 0)
                self.progress_bar.hide()
         # nothing will happen until we reach here

This example won't work unfortunately. In PyQt updates (adding widgets, changing things) only happen when you return to the event loop (by returning from your Python code). If you run the example, you'll notice that after pressing the button nothing happens, then *everything* happens at once: once the loop finishes, Qt gets to processing all the events you've created in one burst.

You can fudge this by calling QApplication.processEvents() in your loop -- although this wouldn't help for the real case with downloading a file as that's a single continuous process. The correct way to do this would be [using a separate thread, probably using QThreadPool](https://www.pythonguis.com/tutorials/mul...hreadpool/) that leaves the UI free to update. You can emit the progress from the worker using signals and then send that to any progressbar you want (popup or in the statusbar).
Reply
#6
Or you could use a QTimer to periodically call a function to update the progress bar.

If you can't measure progress and are using a progress bar as a busy indicator set the minimum and maximum value to zero.
Reply
#7
import sys
from PyQt5.QtWidgets import (QWidget, QPushButton, QLineEdit, QProgressBar, QApplication, 
                            QVBoxLayout, QHBoxLayout, QLabel, QFileDialog)
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import QThread, pyqtSignal, QSettings, QStandardPaths

import requests
import subprocess
################################################


################################################
class Downloader(QWidget):
    def __init__(self, *args, **kwargs):
        super(Downloader, self).__init__(*args, **kwargs)
        
        self.settings = QSettings("MyDownloader", "MyDownloader")
        self.setWindowTitle("Downloader")

        self.setWindowIcon(QIcon.fromTheme("download"))
        layout = QVBoxLayout(self)
        hlayout = QHBoxLayout()
        
        self.dpath = ""
        print(self.dpath)
        self.readSettings()
        self.url = ""
        self.fname = ""

        self.setFixedSize(600, 170)

        # Download Button
        self.pushButton = QPushButton(self, maximumWidth=100)
        self.pushButton.setToolTip('<b>Download</b>')
        self.pushButton.setText("Download")
        self.pushButton.setIcon(QIcon.fromTheme("download"))
        hlayout.addWidget(self.pushButton)
        self.pushButton.clicked.connect(self.on_pushButton_clicked)
        
        # Cancel Button
        self.cancelButton = QPushButton(self, maximumWidth=100)
        self.cancelButton.setText("Cancel")
        self.cancelButton.setIcon(QIcon.fromTheme("cancel"))
        hlayout.addWidget(self.cancelButton)
        self.cancelButton.setEnabled(False)
        self.cancelButton.clicked.connect(self.on_cancelButton_clicked)
        
        # Settings Button
        self.settingsButton = QPushButton(self, maximumWidth=32)
        self.settingsButton.setToolTip("choose Download Folder")
        self.settingsButton.setIcon(QIcon.fromTheme("folder"))
        hlayout.addWidget(self.settingsButton)
        self.settingsButton.clicked.connect(self.on_settingsButton_clicked)
        
        # Space to align Buttons left
        empty = QWidget()
        hlayout.addWidget(empty)
        
        # Bar
        self.progressBar = QProgressBar(self, minimumWidth=300)
        self.progressBar.setFixedHeight(14)
        self.progressBar.setValue(0)
        self.progressBar.hide()
        
        # Url Field
        self.urlfield = QLineEdit()
        self.urlfield.setPlaceholderText("URL")
        self.urlfield.textChanged.connect(self.extractFilename)
        layout.addWidget(self.urlfield)
        
        # Name Field
        self.namefield = QLineEdit()
        self.namefield.setPlaceholderText("Filename")
        layout.addWidget(self.namefield)
        
        layout.addWidget(self.progressBar)
        
        # StatusBar
        self.lbl = QLabel("status")
        layout.addLayout(hlayout)
        layout.addWidget(self.lbl)
        
        self.lbl.setText(f"Ready - Download Path: {self.dpath}")
        
        self.clip = QApplication.clipboard()
        if self.clip.text().startswith("http"):
            self.urlfield.setText(self.clip.text())
            
    def on_settingsButton_clicked(self):
        path = QFileDialog.getExistingDirectory(self, "Select Folder", self.dpath)
        if path:
            self.dpath = path
            self.settings.setValue("folder", self.dpath)
            self.lbl.setText(f"changed Download Path to: {self.dpath}")
        else:
            return
    
    def writeSettings(self):
        self.settings.setValue("folder", self.dpath)
        
    def readSettings(self):
        if self.settings.contains("folder"):
            self.dpath = self.settings.value("folder")
        else:
            self.dpath = QStandardPaths.standardLocations(QStandardPaths.MoviesLocation)[0]
        
    def extractFilename(self):
        t = self.urlfield.text().split('/')[-1]
        self.namefield.setText(f"{self.dpath}/{t}")

    def on_pushButton_clicked(self):
        if self.urlfield.text().startswith("http") or self.urlfield.text().startswith("ftp"):
            the_url = self.urlfield.text()
            the_filesize = requests.get(the_url, stream=True).headers['Content-Length']
            the_filepath = self.namefield.text()
            the_fileobj = open(the_filepath, 'wb')
            #### Create a download thread
            self.downloadThread = downloadThread(the_url, the_filesize, the_fileobj, buffer=10240)
            self.downloadThread.download_proess_signal.connect(self.set_progressbar_value)
            self.downloadThread.start()
            self.lbl.setText("Download started ...")
            self.progressBar.show()
            self.cancelButton.setEnabled(True)

    # Setting progress bar
    def set_progressbar_value(self, value):
        self.progressBar.setValue(value)
        if value == 100:
            self.lbl.setText("Download success!")
            self.sendMessage()
            self.progressBar.hide()

    def on_cancelButton_clicked(self):
        self.downloadThread.terminate()
        self.lbl.setText("Download cancelled")
        self.cancelButton.setEnabled(False)
        
    def sendMessage(self):
        title = 'Downloader'
        message = 'Download success!'
        icon = "info"
        timeout = 5000
        msg = ["notify-send", "-i", icon, title, message, '-t', str(timeout)]
        subprocess.Popen(msg)


##################################################################
#Download thread
##################################################################
class downloadThread(QThread):
    download_proess_signal = pyqtSignal(int)                        #Create signal

    def __init__(self, url, filesize, fileobj, buffer):
        super(downloadThread, self).__init__()
        self.url = url
        self.filesize = filesize
        self.fileobj = fileobj
        self.buffer = buffer


    def run(self):
        try:
            rsp = requests.get(self.url, stream=True)                #Streaming download mode
            offset = 0
            for chunk in rsp.iter_content(chunk_size=self.buffer):
                if not chunk: break
                self.fileobj.seek(offset)                            #Setting Pointer Position
                self.fileobj.write(chunk)                            #write file
                offset = offset + len(chunk)
                proess = offset / int(self.filesize) * 100
                self.download_proess_signal.emit(int(proess))        #Sending signal
            #######################################################################
            self.fileobj.close()    #Close file
            self.exit(0)            #Close thread


        except Exception as e:
            print(e)

####################################
#Program entry
####################################
if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = Downloader()
    w.show()
    sys.exit(app.exec_())
Reply
#8
(Aug-08-2022, 06:38 PM)deanhystad Wrote: Or you could use a QTimer to periodically call a function to update the progress bar.
If you can't measure progress and are using a progress bar as a busy indicator set the minimum and maximum value to zero.

QTimer has the same issue actually, if you're downloading in the GUI thread, the timer events will be blocked (and won't occur) until your method returns, e.g.

from PyQt5.QtWidgets import QApplication, QPushButton
from PyQt5.QtCore import QTimer
import time

STATE_A = "State A"
STATE_B = "STATE b"


class Window(QPushButton):

    def __init__(self):
        super().__init__()
        
        self.setText("State A")
        self.clicked.connect(self.thread_blocking_task)
       
        self.timer = QTimer()
        self.timer.timeout.connect(self.toggle_button_text)
        self.timer.start(100) # 100 msec intervals
        
    def toggle_button_text(self):
        self.setText(STATE_B if self.text() == STATE_A else STATE_A)
        
    def thread_blocking_task(self):
        time.sleep(5) # Sleep for 5 seconds.

app = QApplication([])
w = Window()
w.show()
app.exec()
The example by @Axel_Erfurt is the way to do it: moving the download into a separate thread so the GUI is not blocked.
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  How To Make A PyQt5 Progress Bar Run While Executing A Function rcwildabeast 1 175 Apr-26-2024, 02:18 AM
Last Post: menator01
  How can I measure progress and display it in the progress bar in the interface? Matgaret 2 5,964 Dec-11-2019, 03:30 PM
Last Post: Denni
  [Tkinter] Progress Bar While Sending an Email maxtimbo 3 4,132 Oct-09-2019, 09:13 PM
Last Post: woooee
  Progress Bar While Sending an Email maxtimbo 0 2,136 Oct-08-2019, 02:13 PM
Last Post: maxtimbo
  GUI Progress Bar Anysja 6 6,644 Aug-29-2018, 02:34 PM
Last Post: swetanjali
  [PyQt] cant import progress bar from another py file swipis 7 8,662 Dec-18-2016, 10:41 AM
Last Post: swipis

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020