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:
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:
and later
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.
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)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With