Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PyQt QProgressDialog displays as an empty, white window

I have this simple program where I want to have a modal, non-blocking progress window (using a QProgressDialog) that is remotely updated. SIZE simply controls the maximum value of the QProgressDialog. However, if I set it to have a value of 4 or less, the window looks like this during the entire duration of the action:

enter image description here

In other words, the window is completely white and displays no text nor progress bar. If I set the value of SIZE to 5 or more, the display works correctly, but only after the 2-3 first iterations:

enter image description here

and later

enter image description here

import sys, time
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

SIZE = 5

def doGenerate(setValue):
    for x2 in range(SIZE):
        time.sleep(1)
        setValue(x2 + 1)
    print('Done')


class MainMenu(QMainWindow):
    def __init__(self):
        super().__init__()

        self.genAudioButton = QPushButton('Generate', self)
        self.genAudioButton.clicked.connect(self.generate)

        self.setCentralWidget(self.genAudioButton)
        self.show()

    def generate(self):
        try:
            progress = QProgressDialog('Work in progress', '', 0, SIZE, self)
            progress.setWindowTitle("Generating files...")
            progress.setWindowModality(Qt.WindowModal)
            progress.show()
            progress.setValue(0)
            doGenerate(progress.setValue)
        except Exception as e:
            errBox = QMessageBox()
            errBox.setWindowTitle('Error')
            errBox.setText('Error: ' + str(e))
            errBox.addButton(QMessageBox.Ok)
            errBox.exec()
            return

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = MainMenu()
    ret = app.exec_()
    sys.exit(ret)

What is causing this and how can I fix it?

Additionally, is there a way to completely remove the cancel button, instead of having an empty button that still cancels the action? The PyQt4 docs (I am using PyQt5) indicate that an empty string should achieve this result, and the C++ docs for Qt5 indicate the same, but that clearly doesn't work here. I haven't found standalone documentation for PyQt5.

like image 846
pie3636 Avatar asked Jan 30 '23 00:01

pie3636


1 Answers

The GUI implements a mainloop through app.exec_(), this loop is used to perform tasks such as checking events, signals, calling some functions, etc. so if we interrupt the loop we can get unexpected behavior like the one you observe. in your case sleep() is a blocking function that should not be used, Qt offers alternatives to it, and one of them is to use a QEventLoop with a QTimer:

def doGenerate(setValue):
    for x2 in range(SIZE):
        loop = QEventLoop()
        QTimer.singleShot(1000, loop.quit)
        loop.exec_()
        setValue(x2 + 1)
    print('Done')

If you want the cancel button not to show, you must pass None:

progress = QProgressDialog('Work in progress', None, 0, SIZE, self)

If you want to use gTTS you must do it through threads, Qt offers several ways to implement it, in this case I will use QThreadPool with QRunnable. We will use the QMetaObject.invokeMethod to update the values of the GUI since Qt prohibits the update of the GUI from another thread that is not from the main thread.

import sys, time
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from gtts import gTTS


class GTTSRunnable(QRunnable):
    def __init__(self, data, progress):
        QRunnable.__init__(self)
        self.data = data
        self.w = progress

    def run(self):
        for i, val in enumerate(self.data):
            text, filename = val
            tts = gTTS(text=text, lang='en')
            tts.save(filename)
            QMetaObject.invokeMethod(self.w, "setValue",
                Qt.QueuedConnection, Q_ARG(int, i+1))
            QThread.msleep(10)

class MainMenu(QMainWindow):
    def __init__(self):
        super().__init__()
        self.genAudioButton = QPushButton('Generate', self)
        self.genAudioButton.clicked.connect(self.generate)
        self.setCentralWidget(self.genAudioButton)
        self.show()

    def generate(self):
        try:
            info = [("hello", "1.mp4"), ("how are you?", "2.mp4"), ("StackOverFlow", "3.mp4")]
            self.progress = QProgressDialog('Work in progress', '', 0, len(info), self)
            self.progress.setWindowTitle("Generating files...")
            self.progress.setWindowModality(Qt.WindowModal)
            self.progress.show()
            self.progress.setValue(0)
            self.doGenerate(info)
        except Exception as e:
            errBox = QMessageBox()
            errBox.setWindowTitle('Error')
            errBox.setText('Error: ' + str(e))
            errBox.addButton(QMessageBox.Ok)
            errBox.exec()
            return

    def doGenerate(self, data):
        self.runnable = GTTSRunnable(data, self.progress)
        QThreadPool.globalInstance().start(self.runnable)

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = MainMenu()
    ret = app.exec_()
    sys.exit(ret)
like image 79
eyllanesc Avatar answered Feb 11 '23 16:02

eyllanesc