У меня есть процесс, работающий в QThread, который генерирует некоторые данные, которые я хочу сохранить в файл. Прежде чем сделать это, мне нужно убедиться, что у меня есть надлежащие разрешения для записи в файл (это в Windows), и я бы очень хотел использовать QtWidgets.QMessageBox
с опциями «Повторить попытку» и «Отмена». Это легко сделать, например, с помощью (типичная проблема в моем случае заключается в том, что у пользователя есть файл xlsx, открытый в Excel)
reply = QtWidgets.QMessageBox.warning(
self, "PermissionError",
"Could not open file \"%s\". Try closing the file if it is open." % filename,
QtWidgets.QMessageBox.StandardButton.Retry,
QtWidgets.QMessageBox.StandardButton.Cancel
)
но это работает только в основном (GUI) потоке, а не в отдельном QThread. Каков хороший способ сделать это из QThread?
Я сделал несколько тестов с сигналом от QThread, отправленным в слот в потоке графического интерфейса, который открывает диалог. Но мне трудно сообщить QThread результат проверки разрешений, и логика быстро становится беспорядочной.
Из моего опыта подход к подклассифицировать QThread не годится для этих случаев.
Простое решение — использовать подход рабочий (QThread)-поток, поскольку у исполнителя может быть несколько задач, таких как выполнение тяжелых вычислений и сохранение файла. Таким образом, GUI может запустить первую задачу, затем worker, который живет в другом потоке, делает запрос на подтверждение сигналами в GUI, затем GUI в соответствии с полученным результатом может вызвать другую задачу.
Примером вышеизложенного является следующее:
from PySide2 import QtCore, QtGui, QtWidgets
class Worker(QtCore.QObject):
requestSignal = QtCore.Signal(str)
def __init__(self, parent=None):
super(Worker, self).__init__(parent)
self.data = None
@QtCore.Slot()
def process(self):
print("start process")
# emulate heavy task
QtCore.QThread.sleep(5)
self.data = "Foo"
print("end process")
self.requestSignal.emit("filename.xlsx")
@QtCore.Slot(bool)
def save_data(self, result):
print(result)
print(self.data)
class Widget(QtWidgets.QWidget):
sendResult = QtCore.Signal(bool)
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
self.m_button = QtWidgets.QPushButton(
"Press me", clicked=self.start_process
)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(self.m_button)
self.m_thread = QtCore.QThread(self)
self.m_thread.start()
self.m_worker = Worker()
self.m_worker.moveToThread(self.m_thread)
self.m_worker.requestSignal.connect(self.on_request)
self.sendResult.connect(self.m_worker.save_data)
@QtCore.Slot()
def start_process(self):
# launch process
QtCore.QTimer.singleShot(0, self.m_worker.process)
self.m_button.setDisabled(True)
@QtCore.Slot(str)
def on_request(self, filename):
reply = QtWidgets.QMessageBox.warning(
self,
"PermissionError",
'Could not open file "%s". Try closing the file if it is open.'
% filename,
QtWidgets.QMessageBox.Retry,
QtWidgets.QMessageBox.Cancel,
)
result = reply == QtWidgets.QMessageBox.Retry
print(result)
self.sendResult.emit(result)
self.m_button.setDisabled(False)
def closeEvent(self, event):
super(Widget, self).closeEvent(event)
self.m_thread.quit()
self.m_thread.wait()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.show()
sys.exit(app.exec_())
«подклассификация QThread не подходит для этих случаев» - какой подход вы бы предложили вместо этого?
@ФилипС. Как я указываю в своем ответе: используйте рабочий (QObject), который живет в другом потоке
О, теперь я вижу разницу. Спасибо!
Это похоже на решение, к которому я склонялся, но не смог разобраться в логике. Вы проделали хорошую работу, так что я попробую!