PyQt QProgressDialog 显示为空的白色 window
PyQt QProgressDialog displays as an empty, white window
我有一个简单的程序,我想在其中获得一个远程更新的模式、非阻塞进度 window(使用 QProgressDialog)。 SIZE
简单的控制QProgressDialog的最大值。但是,如果我将其设置为 4 或更小的值,则 window 在整个操作期间看起来像这样:
换句话说,window 完全是白色的,不显示任何文本和进度条。如果我将 SIZE
的值设置为 5 或更多,显示将正常工作,但仅在 2-3 次第一次迭代之后:
以后
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 的独立文档。
GUI通过app.exec_()
实现了一个主循环,这个循环用于执行检查事件、信号、调用一些函数等任务,所以如果我们中断循环,我们会得到意想不到的行为,比如你观察的一个。在您的情况下 sleep()
是不应使用的阻塞函数,Qt 提供了替代方法,其中之一是使用 QEventLoop
和 QTimer
:
def doGenerate(setValue):
for x2 in range(SIZE):
loop = QEventLoop()
QTimer.singleShot(1000, loop.quit)
loop.exec_()
setValue(x2 + 1)
print('Done')
想要取消按钮不显示,必须通过None:
progress = QProgressDialog('Work in progress', None, 0, SIZE, self)
如果你想使用gTTS
你必须通过线程来完成,Qt提供了几种实现它的方法,在这种情况下我将使用QThreadPool
和QRunnable
。我们将使用 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"), ("Whosebug", "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)
这可能对任何使用 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 或 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 给出空白对话框,接下来的两个执行前两次迭代,所以第一个真正的迭代有一个正确显示的对话框要更新...
在设置值以刷新 QProgressDialog
后,我可以通过调用 QtGui.QApplication.processEvents()
来解决同样的问题
progress.setValue(i)
QApplication.processEvents()
我有一个简单的程序,我想在其中获得一个远程更新的模式、非阻塞进度 window(使用 QProgressDialog)。 SIZE
简单的控制QProgressDialog的最大值。但是,如果我将其设置为 4 或更小的值,则 window 在整个操作期间看起来像这样:
换句话说,window 完全是白色的,不显示任何文本和进度条。如果我将 SIZE
的值设置为 5 或更多,显示将正常工作,但仅在 2-3 次第一次迭代之后:
以后
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 的独立文档。
GUI通过app.exec_()
实现了一个主循环,这个循环用于执行检查事件、信号、调用一些函数等任务,所以如果我们中断循环,我们会得到意想不到的行为,比如你观察的一个。在您的情况下 sleep()
是不应使用的阻塞函数,Qt 提供了替代方法,其中之一是使用 QEventLoop
和 QTimer
:
def doGenerate(setValue):
for x2 in range(SIZE):
loop = QEventLoop()
QTimer.singleShot(1000, loop.quit)
loop.exec_()
setValue(x2 + 1)
print('Done')
想要取消按钮不显示,必须通过None:
progress = QProgressDialog('Work in progress', None, 0, SIZE, self)
如果你想使用gTTS
你必须通过线程来完成,Qt提供了几种实现它的方法,在这种情况下我将使用QThreadPool
和QRunnable
。我们将使用 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"), ("Whosebug", "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)
这可能对任何使用 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 或 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 给出空白对话框,接下来的两个执行前两次迭代,所以第一个真正的迭代有一个正确显示的对话框要更新...
在设置值以刷新 QProgressDialog
后,我可以通过调用QtGui.QApplication.processEvents()
来解决同样的问题
progress.setValue(i)
QApplication.processEvents()