QProgressDialog показывает только после того, как закончил долго работающий код
У меня есть программа, разработанная в соответствии с моделью MVC. На мой взгляд, есть open
кнопка, где я могу открыть файлы. Файлы анализируются, и с содержимым файла выполняется большой расчет.
Теперь я хочу отобразить загрузчик, чтобы указать, что пользователь должен ждать. Я совершенно новичок в Python и Qt. Я использую PyQt5 (Qt 5.6.2) с Python 3.6.2.
Я добавил showLoader()
метод к openFiles()
метод моего взгляда:
class MainWindow(QtWidgets.QMainWindow):
def __init__(self, controller, parent = None):
# initializing the window
def showOpenDialog(self):
files, filters = QtWidgets.QFileDialog.getOpenFileNames(self, 'Open file(s)', '',
"Raw files (*.rw.dat);;Data files (*.dat)" +
";;Text files (*.txt);;All files (*)")
self.showLoader("Loading file(s)")
self.doSomeStuffWithTheFiles(files)
self.hideLoader()
def showLoader(self, text):
self._progress = QtWidgets.QProgressDialog(text, "Abort", 0, 0, self);
self._progress.setWindowModality(QtCore.Qt.WindowModal);
Появится загрузчик, но он появится после загрузки файла. Даже не сразу после загрузки файла, но это займет дополнительные 1-2 секунды после того, как все будет сделано (включая некоторые перерисовки окна)
Я много читал о потоках, поэтому я предполагаю, что анализ файла блокирует загрузчик прогресса, что имеет смысл. Я прочитал, что я должен добавить QProgressDialog
в слот (я действительно не знаю, что это такое), но это не помогает мне, потому что я хочу QProgressDialog
отображаться после QFileDialog
,
Я также читал кое-что о добавлении QtWidgets.QApplication.processEvents()
перекрасить окно, но это не сработало для меня (или я использовал его неправильно).
Итак, мои вопросы:
- Как мне отобразить
QProgressDialog
когда я звонюshowLoader()
метод? - Должен ли я выполнять свои вычисления и анализ файла в другом потоке, и если нужно, как мне это сделать?
- Если бы я хотел показать больше информации в
QProgressDialog
Как обновить текст и прогресс, как мне это сделать?
Дальнейший вопрос
Решение, указанное @ekhumoro, работает отлично. Я вижу загрузчик и файлы анализируются правильно. Моя проблема сейчас в том, что обновление моего существующего MainWindow
не работает.
После выполнения кода я вижу, что всплывает небольшое окно, но оно мгновенно исчезает. (У меня была такая проблема, и речь шла о сборщике мусора в C++ на фоне Qt. Но в моем понимании компоновка должна сохранять ссылку на ParsedDataWidget
так что это не имеет смысла для меня.) Также ParsedDataWidget
это виджет, который должен быть добавлен в layout
"встроенный" и не отображается как "окно".
# a class that handles the data parsing of each file and creates an
# object that contains all the data with some methods...
class DataParser
def __init__(self, data):
# handle the data
# displaying the parsed data in a fancy way
class ParsedDataWidget(QtWidgets.QWidget)
def __init__(self, data):
# create some UI
# the worker class just like @ekhumoro wrote it (stripped down to
# relevant code)
class Worker(QtCore.QObject):
def run(self):
self._stop = False
for count, file in enumerate(self._files, 1):
# parse the data in the DataParser and create an object
# of the files data
data = DataParser(file)
# works fine, the data is parsed correctly
print(data)
# does not work
window.addParsedData(data)
self.loaded.emit(count, file)
if self._stop:
break
self.finished.emit()
# the window class like mentioned before (or like @ekhumoro wrote it)
class Window(QtWidgets.QWidget):
def __init__(self):
self._data_container = QtWidgets.QWidget()
layout = QtWidgets.QVBoxLayout()
self._data_container.setLayout(layout)
def addParsedData(data):
data_widget = ParsedDataWidget(data)
layout = self._data_container.layout()
layout.addWidget(data_widget)
Так что мне нужно сделать, чтобы получить addParsedData
способ работы?
редактировать
Я пытался некоторые изменения кода. Если я заменю ParsedDataWidget
с QLabel
Я получаю следующий результат:
Если я закрою окно, Python падает.
Решение
Проведя дальнейшее исследование, я обнаружил, что моя проблема: вы не должны использовать потоки с PyQt, вы должны использовать SIGNALS
вместо ( написано здесь)
Поэтому я изменил код работника, я добавил другой SIGNAL
называется finishedParsing
который испускается, если загрузка завершена. это SIGNAL
держит DataParser
, Может выглядеть так:
class Worker(QtCore.QObject):
finishedParsing = QtCore.pyqtSignal(DataParser)
def run(self):
self._stop = False
for count, file in enumerate(self._files, 1):
# parse the data in the DataParser and create an object
# of the files data
data = DataParser(file)
# emit a signal to let the window know that this data is
# ready to use
self.finishedParsing.emit(data)
self.loaded.emit(count, file)
if self._stop:
break
self.finished.emit()
class Window(QtWidgets.QWidget):
def showOpenDialog(self):
if files and not self.thread.isRunning():
# do the opening stuff like written before
self.worker = Worker(files)
#...
self.worker.finishedParsing.connect(self.addParsedData)
Это работает сейчас!
1 ответ
Ниже приведен пример, который реализует то, что вы просили. В реальном использовании QThread.sleep
строка должна быть заменена вызовом функции, которая обрабатывает каждый файл. Это можно определить как метод Worker
класс, или передал в качестве аргумента его __init__
,
import sys, os
from PyQt5 import QtCore, QtWidgets
class Worker(QtCore.QObject):
loaded = QtCore.pyqtSignal(int, str)
finished = QtCore.pyqtSignal()
def __init__(self, files):
super().__init__()
self._files = files
def run(self):
self._stop = False
for count, file in enumerate(self._files, 1):
QtCore.QThread.sleep(2) # process file...
self.loaded.emit(count, file)
if self._stop:
break
self.finished.emit()
def stop(self):
self._stop = True
class Window(QtWidgets.QWidget):
def __init__(self):
super(Window, self).__init__()
self.button = QtWidgets.QPushButton('Choose Files')
self.button.clicked.connect(self.showOpenDialog)
layout = QtWidgets.QVBoxLayout(self)
layout.addWidget(self.button)
self.thread = QtCore.QThread()
def showOpenDialog(self):
files, filters = QtWidgets.QFileDialog.getOpenFileNames(
self, 'Open file(s)', '',
'Raw files (*.rw.dat);;Data files (*.dat)'
';;Text files (*.txt);;All files (*)',
'All files (*)')
if files and not self.thread.isRunning():
self.worker = Worker(files)
self.worker.moveToThread(self.thread)
self.worker.finished.connect(self.thread.quit)
self.thread.started.connect(self.worker.run)
self.thread.finished.connect(self.worker.deleteLater)
self.showProgress(
'Loading file(s)...', len(files), self.worker.stop)
self.worker.loaded.connect(self.updateProgress)
self.thread.start()
def updateProgress(self, count, file):
if not self.progress.wasCanceled():
self.progress.setLabelText(
'Loaded: %s' % os.path.basename(file))
self.progress.setValue(count)
else:
QtWidgets.QMessageBox.warning(
self, 'Load Files', 'Loading Aborted!')
def showProgress(self, text, length, handler):
self.progress = QtWidgets.QProgressDialog(
text, "Abort", 0, length, self)
self.progress.setWindowModality(QtCore.Qt.WindowModal)
self.progress.canceled.connect(
handler, type=QtCore.Qt.DirectConnection)
self.progress.forceShow()
if __name__ == '__main__':
app = QtWidgets.QApplication(sys.argv)
window = Window()
window.setGeometry(600, 100, 100, 50)
window.show()
sys.exit(app.exec_())