Я программирую настольное приложение для небольшого местного отеля, используя Python.
Как это часто бывает, в фоновом режиме у меня также есть сложный процесс/метод. В это время индикатор выполнения должен появиться и заполниться. Индикатор выполнения должен блокировать дальнейший ввод и служить информацией для пользователя: «Данные/задачи обрабатываются».
Моя проблема:
Я не могу добиться и того, и другого: запустить heavy_load_method
в фоновом режиме и одновременно показать и заполнить панель прогресса.
Я предполагаю, что я неправильно реализую свой индикатор выполнения для многопоточности/многопроцессорности.
Поскольку моя неудача, возможно, связана с моей текущей реализацией MVP, некоторые фрагменты кода:
# main.py
# importing all modules (presenter, model and all views/formulars (including the progressbar view/form)
if __name__ == "__main__":
app = QApplication(sys.argv)
mainwindow = Mainwindow()
progressbar = ProgressBarPopup()
model = Model()
...
presenter = Presenter(model, mainwindow, progressbar, ...)
sys.exit(app.exec_())
# presenter.py
# imports modules required for data manipulation
class Presenter:
def __init__(self, model, mainwindow, progressbar, ...)
self.model = model
self.mainwindow = mainwindow
self.progressbar = progressbar
...
def heavy_load_method(self):
# does operations like:
# - getting (minor) amounts of data from form/view (related view/form method calls)
# - processes already cached `self.data` (via Presenter method calls)
# - calling and calculating data from the model/database
# - calling functions from imported moduls like "myfilemanagement.py"
# - ...
# MyProgressbar.py
from PyQt5.QtWidgets import QProgressBar, QDialog, QVBoxLayout, QLabel
from PyQt5.QtCore import Qt, QTimer
class ProgressBarPopup(QDialog):
def __init__(self):
super().__init__()
self.setWindowTitle("Progressbar")
self.setWindowFlags(Qt.Window | Qt.FramelessWindowHint)
self.setFixedSize(300, 100)
self.setWindowModality(Qt.ApplicationModal)
layout = QVBoxLayout()
self.label = QLabel("Processing...", self)
self.label.setAlignment(Qt.AlignCenter)
layout.addWidget(self.label)
self.progressbar = QProgressBar(self)
self.progressbar.setRange(0, 99)
layout.addWidget(self.progressbar)
self.setLayout(layout)
# Progressbar visual update timer
self.step = 0
self.timer = QTimer(self)
self.timer.timeout.connect(self.update_progress)
# Progressbar exit/close timer
self.close_timer = QTimer(self)
self.close_timer.timeout.connect(self.close_progressbar)
def close_progressbar(self):
self.close_timer.stop()
self.close()
def update_progress(self):
self.step += 1
if self.step <= 98:
self.progressbar.setValue(self.step)
if self.step > 98:
self.timer.stop()
self.label.setText("This takes longer then expected.")
def setup_progressbar_and_show(self):
self.step = 0
self.progressbar.setValue(self.step)
self.timer.start(70)
self.close_timer.start(10000)
self.label.setText("Processing...")
self.show()
Я попробовал multithread
или multiprocess
метод heavy_load_method
. Мои предыдущие попытки не увенчались успехом, потому что я не смог multithread
/multiprocess
индикатор выполнения.
@AKX, я полагаю, нет? Я буду смотреть в него. Я подозревал, что неправильно реализовал индикатор выполнения (индикатор выполнения передается ведущему, поэтому он, вероятно, не становится внезапно многопоточным во время выполнения).
@AKX Судя по вашей ссылке: мне нужно создать еще один класс, унаследованный от QThread, который получит почти полную копию Presenter? (heavy_duty_method требуется доступ к модели/базе данных и т. д.)
Шаблоны MVC/MVP часто не нужны при использовании Qt, но если «глобальный менеджер» (контроллер/презентатор) действительно требуется структурой программы, обычно лучше использовать его как подкласс QObject (позволяющий правильному сигналу/слоту). функции), и это часто достигается за счет использования самого QApplication, поскольку обычно это общий объект и всегда существует только один его экземпляр. Любую «тяжелую» обработку затем следует делегировать отдельному объекту (обычно QThread), который никогда не будет напрямую обращаться к пользовательскому интерфейсу, а будет работать только с данными и в конечном итоге сигнализировать о результатах.
@musicamante Вы предполагаете, что мой класс Presenter должен наследовать от QApplication? Я создал класс Worker, очень похожий на stackoverflow.com/a/63940295/51685. (Скопированы необходимые модули и методы, инициализированы (конструктор) необходимыми данными. run(self) вызывает «heavy_duty_method»). Вылетает self.worker.started(self.progressDialog.show())
=> TypeError: native Qt signal is not callable
. В настоящее время я читаю QThread.started. Что может вызвать эту ошибку?
Ошибка очевидна: сигналы нельзя вызвать, вам нужно подключиться к сигналу, а не вызвать его. Кроме того, в соединениях сигналов вы передаете ссылку на функцию, а не вызываете ее. Перейдите на self.worker.started.connect(self.progressDialog.show)
, затем узнайте больше о сигналах и слотах, например, начиная с этого.
@musicamante Если шаблоны MVC/MVP не нужны для (в моем случае) настольного инструмента бронирования: какие шаблоны обычно применяются? Я выбрал MVC/MVP, потому что мне нужна была структура, которой можно было бы следовать, и которую было бы легко расширить.
@АндреасМ. Шаблоны программирования — это общие концептуальные структуры: от нас не требуется следовать ни одной из них, и выбор одной из них не означает, что мы должны строго следовать ее «известной» структуре. Это абстракции, концепции упрощения и, по сути, просто рекомендации. Хотя это правда, что MVC/MVP легче следовать и расширять, верно также и то, что слепое следование таким шаблонам «потому что так следует делать» — ужасный выбор. Шаблоны следует применять тогда, когда они имеют смысл, и пока их внедрение и поддержка выгодны, что не соответствует общим правилам.
@АндреасМ. Например, известное руководство по калькулятору Realpython.com является прекрасным примером: оно ясно показывает, как MVC может быть достигнуто с помощью PyQt, но также забывает объяснить, почему такой шаблон совершенно бессмысленен для этого базового примера; Интересно, что многие вопросы здесь, по SO, были закрыты из-за этого поста, потому что реальная цель поста была неправильно понята как общее правило, которому нужно следовать, а не как руководство, которое следовало понять, прежде чем его применять с самого начала.
@musicamante Кажется, я понял MVC и MVP. Однако примеры не были написаны с использованием pyqt5. У меня также был опыт работы с многопоточностью (или даже с многопроцессорностью), даже если это было много лет назад. На тот момент не было необходимости (по крайней мере, я этого не помню) выделять в отдельный класс(ы). Вам просто нужно было убедиться, что расчеты на самом деле на 100% независимы друг от друга. Кроме того, нагрузка/вычисления были разделены таким образом, чтобы каждая отдельная часть требовала примерно одинакового вычислительного времени. Поскольку такое разделение (поток/процесс) было невозможно с помощью pyqt5, был создан этот пост.
@musicamante Мои навыки программирования получены в основном из «высшего профессионального колледжа информатики» и изучения математики (немного Java, в основном Python). В идеале я бы сходил на лекцию или прочитал книгу, чтобы восполнить недостаток современных навыков программирования. К сожалению, полной работы по этому поводу я не нашел. Поэтому я благодарен за любую помощь. Особенно если он очень компетентный и явно выше моего уровня. Я прочитаю обе ваши ссылки полностью (даже если предполагаю, что уже знаю, НО: вижу, что не знал). Прошу прощения за качество текущего вопроса.
Лучше создать пакетную функцию, обрабатывающую небольшой объем данных. Вы можете вызывать эти функции, создавая поток для каждой из них, управляя их количеством с помощью очереди (для запуска потоков rn) и списка (например, для запуска потоков, когда у вас будет больше памяти). Вы будете знать, когда каждая из функций была остановлена (завершена) из основного потока пользовательского интерфейса, и для каждой завершенной пакетной функции вам нужно добавить процент уже обработанных данных в строку состояния.
Проверьте функцию nn_labeling
здесь (потоковая обработка): https://github.com/ISPlatonov/Dataset-generation/blob/python/backend/HandSegmentor.py
Индикатор выполнения: https://github.com/ISPlatonov/Dataset-generation/blob/python/frontend/step%202/threshold.qml
Этот проект основан на PySide6 (PyQt с другой лицензией).
Меньший объем данных — это заголовок счета, а также некоторые другие настройки, относящиеся к счету. Его можно настроить, поэтому я загружаю его через self.invoice_view.get_Invoice_Header()` в файл heavy_load_method
. Я не думаю, что это является причиной моей проблемы. Возможно, мне следовало сформулировать вопрос точнее (я думал, что это будет достаточно точно).
Кажется, моя проблема в том, что мой индикатор выполнения не является многопоточным.
Это не обязательно, так как вы можете установить значение индикатора выполнения на значение qt, которое будет обновляться при отправке сигнала.
Мне немного стыдно, что это заняло так много времени, но:
Даже если вы интуитивно полагаете, что вам нужно использовать QThread для индикатора выполнения (поскольку это объект pyqt5), на самом деле все происходит наоборот. heavy_duty_method
необходимо отделить от презентатора и поместить в отдельный подкласс QThread. На основе этого во время выполнения создается объект, который можно интегрировать и запустить, как показано ниже.
# presenter.py
# imports modules required for data manipulation and 'Create_Heavy_Duty_Thread' class
class Presenter:
def __init__(self, model, mainwindow, progressbar, ...)
self.model = model
self.mainwindow = mainwindow
self.progressbar = progressbar
...
def prepare_for_load(self)
if conditions_are_met:
self.worker = Create_Heavy_Duty_Thread(self.model, invoice_dict, bookings, isClassic)
self.worker.started.connect(self.progressbar.setup_progressbar_and_show)
self.worker.finished.connect(self.progressbar.close_progressbar)
self.worker.start()
class Create_Heavy_Duty_Thread(QThread):
def __init__(self, model, invoice_dict, bookings, isClassic):
super().__init__()
self.model = model
self.invoice_dict = invoice_dict
self.bookings = bookings
self.isClassic = isClassic
... # all methods needed to perform heavy_duty_method
def heavy_duty_method(self):
# heavy duty is done here
...
def run(self):
self.heavy_duty_method()
У вас есть причина не использовать
QProgressDialog
? Также смотрите stackoverflow.com/a/63940295/51685