PyQt QProgressDialog显示为空的白色窗口

时间:2017-12-19 02:58:09

标签: python pyqt modal-dialog pyqt5 nonblocking

我有这个简单的程序,我希望有一个远程更新的模态,非阻塞进度窗口(使用QProgressDialog)。 SIZE只是控制QProgressDialog的最大值。但是,如果我将其设置为值为4或更小,则窗口在整个操作期间看起来像这样:

enter image description here

换句话说,窗口是完全白色的,不显示文本和进度条。如果我将SIZE的值设置为5或更大,则显示正常,但仅在2-3次首次迭代之后:

enter image description here

以后

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)

造成这种情况的原因是什么?如何解决?

另外,有没有办法彻底删除取消按钮,而不是让仍然取消操作的空按钮? PyQt4文档(我使用PyQt5)表明空字符串应该达到这个结果,而Qt5的C ++文档表明相同,但这显然不起作用。我还没找到PyQt5的独立文档。

4 个答案:

答案 0 :(得分:2)

GUI通过app.exec_()实现一个mainloop,这个循环用于执行诸如检查事件,信号,调用某些函数等任务。所以如果我们中断循环,我们会得到意想不到的行为,就像你观摩。在你的情况下sleep()是一个不应该使用的阻止函数,Qt提供了替代它,其中一个是使用QEventLoopQTimer

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

如果您不想显示取消按钮,则必须传递无:

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

如果你想使用gTTS,你必须通过线程来做,Qt提供了几种实现它的方法,在这种情况下,我将QThreadPoolQRunnable一起使用。我们将使用QMetaObject.invokeMethod来更新GUI的值,因为Qt禁止从另一个非主线程的线程更新GUI。

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)

答案 1 :(得分:1)

这对于将Quamash / asyncio用于异步应用程序的任何人都可能有用。

以@eyllanesc为例,在执行程序中分派CPU绑定的任务,并删除对Gtts的依赖。

出于我的目的,我也不知道CPU绑定需要多长时间,因此我将进度对话框的最小值和最大值都设置为零。只需对进度条进行动画处理,直到完成任务,效果会很好。但是,执行此操作时必须手动调用cancel()方法,因为进度对话框无法知道何时完成。这是在附加到将来的回调中完成的。

def main():

    import sys
    import time
    import quamash
    import asyncio
    import concurrent
    import logging
    import random
    import PyQt5

    # Integrate event loops
    app = PyQt5.QtWidgets.QApplication(sys.argv)
    loop = quamash.QEventLoop(app)
    asyncio.set_event_loop(loop)
    loop.set_debug(False)  # optional

    # Config logging
    logging.basicConfig(level=logging.DEBUG)
    logging.getLogger('quamash').setLevel(logging.ERROR)

    # Print exception before crash!
    def except_hook(cls, exception, traceback):
        sys.__excepthook__(cls, exception, traceback)
    sys.excepthook = except_hook

    class MainWindow(PyQt5.QtWidgets.QMainWindow):
        def __init__(self):
            super().__init__()
            self.exitRequest = asyncio.Event()
            self.genAudioButton = PyQt5.QtWidgets.QPushButton('Generate', self)
            self.genAudioButton.clicked.connect(self.generate)
            self.setCentralWidget(self.genAudioButton)
            self.show()

        def generate(self):
            self.progress = PyQt5.QtWidgets.QProgressDialog('Work in progress...', None, 0, 0, self)
            self.progress.setWindowTitle("Calculation")
            self.progress.setWindowModality(PyQt5.QtCore.Qt.WindowModal)
            self.progress.show()
            self.progress.setValue(0)
            # As the loop to run the coroutine
            loop = asyncio.get_event_loop()
            loop.create_task(self.doGenerate())

        def closeEvent(self, event):
            """ Called when the windows closes.
            """
            self.exitRequest.set()

        def cpuBound(self):
            """ Just wait 2s or raise an exception 50% of the time to test error handling.
            """
            # %50 change of raising an exception
            time.sleep(1.0)
            if random.random() < 0.5:
                time.sleep(1.0)
            else:
                raise RuntimeError(
                    ("If the CPU bound task fails you can raise "
                     "an exception that can be caught and displayed"
                     " like this!")
                )

        def onComplete(self, future):
            """ Callback which contains the future that has completed.
            """

            # Dismiss the progress popup widget before we (possibly)
            # display a popup with an error message.
            self.progress.cancel()

            # Check if we got a result or an exception!
            try:
                result = future.result()
            except Exception as e:
                errBox = PyQt5.QtWidgets.QMessageBox()
                errBox.setWindowTitle('Error')
                errBox.setText('Error: ' + str(e))
                errBox.addButton(PyQt5.QtWidgets.QMessageBox.Ok)
                errBox.exec()  

        async def doGenerate(self):
            """ The coroutine that is added to the event loop when the button is pressed.
            """
            loop = asyncio.get_event_loop()
            with concurrent.futures.ThreadPoolExecutor() as pool:
                future = loop.run_in_executor(pool, self.cpuBound)
                # This call back handles the result or possible exception
                future.add_done_callback(self.onComplete)
                # Block here until complete
                result = await future

    # Startup application
    _window = MainWindow()
    _window.show()
    with loop:
        loop.run_until_complete(_window.exitRequest.wait())

if __name__ == '__main__':
    main()

答案 2 :(得分:1)

几乎完全是导致我来到这里的问题。空白的白色对话框,然后突然正确显示,但好像发生了2或3次迭代。

对我来说解决方案毫无意义...

progress = QProgressDialog('Work in progress', '', 0, SIZE, self)
progress.setWindowTitle("Generating files...")
progress.setWindowModality(Qt.WindowModal)
progress.setValue(0)
progress.setValue(1)
progress.setValue(0)

第一个setValue几乎提供了空白对话框,而后两个执行了前两个迭代,因此第一个实际迭代具有正确显示的对话框以供更新...

答案 3 :(得分:0)

在我设置刷新 QProgressDialog 的值后,我能够通过调用 QtGui.QApplication.processEvents() 来解决同样的问题

progress.setValue(i)
QApplication.processEvents()