erreur msgbox lors du threading, bloc d'interface graphique [fermé]
J'ai un problème lors de l'exécution de l'interface graphique suivante. cela fonctionne normalement s'il n'y a pas de msgbox, mais quand il y a une mesbox, il se bloque. toute idée pourquoi l'interface graphique se bloque quand il y a un message Je vous remercie
from PyQt5 import QtCore, QtGui, QtWidgets
import threading
import time
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
MainWindow.resize(800, 600)
self.centralwidget = QtWidgets.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.verticalLayout = QtWidgets.QVBoxLayout(self.centralwidget)
self.verticalLayout.setObjectName("verticalLayout")
self.pushButton = QtWidgets.QPushButton(self.centralwidget)
self.pushButton.setObjectName("pushButton")
self.verticalLayout.addWidget(self.pushButton)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtWidgets.QMenuBar(MainWindow)
self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 26))
self.menubar.setObjectName("menubar")
MainWindow.setMenuBar(self.menubar)
self.statusbar = QtWidgets.QStatusBar(MainWindow)
self.statusbar.setObjectName("statusbar")
MainWindow.setStatusBar(self.statusbar)
self.retranslateUi(MainWindow)
QtCore.QMetaObject.connectSlotsByName(MainWindow)
self.pushButton.pressed.connect(self.threadingc)
def calculation(self):
for i in range(10):
time.sleep(1)
print(i)
msg = QtWidgets.QMessageBox()
msg.setInformativeText('Finish')
msg.exec_()
def threadingc(self):
x=threading.Thread(target=self.calculation)
x.start()
def retranslateUi(self, MainWindow):
_translate = QtCore.QCoreApplication.translate
MainWindow.setWindowTitle(_translate("MainWindow", "MainWindow"))
self.pushButton.setText(_translate("MainWindow", "PushButton"))
import sys
if __name__ == "__main__":
app = QtWidgets.QApplication(sys.argv)
MainWindow = QtWidgets.QMainWindow()
ui = Ui_MainWindow()
ui.setupUi(MainWindow)
MainWindow.show()
sys.exit(app.exec_())
Réponses
Tout accès aux éléments de l'interface utilisateur n'est autorisé qu'à partir du thread principal de Qt. Notez que l' accès signifie non seulement la lecture / écriture des propriétés du widget, mais aussi la création; toute tentative de le faire à partir d'autres threads entraîne des problèmes graphiques ou un comportement incohérent dans le meilleur des cas, et un crash dans le pire des cas (et le plus courant).
La seule manière correcte de le faire est d'utiliser un QThread avec (éventuellement) des signaux personnalisés: cela permet à Qt de mettre correctement en file d'attente les signaux et de réagir à eux lorsqu'il peut réellement les traiter.
Ce qui suit est une situation très simple qui ne nécessite pas la création d'une sous-classe QThread, mais considérez que c'est juste à des fins éducatives .
class Ui_MainWindow(object):
# ...
def calculation(self):
for i in range(10):
time.sleep(1)
print(i)
def showMessage(self):
msg = QtWidgets.QMessageBox()
msg.setInformativeText('Finish')
msg.exec_()
self.pushButton.setEnabled(True)
def threadingc(self):
self.pushButton.setEnabled(False)
self.thread = QtCore.QThread()
# override the `run` function with ours; this ensures that the function
# will be executed in the new thread
self.thread.run = self.calculation
self.thread.finished.connect(self.showMessage)
self.thread.start()
Veuillez considérer les aspects importants suivants:
- J'ai dû désactiver le bouton poussoir, sinon il serait possible de créer un nouveau thread alors que le précédent était toujours en cours d'exécution; cela créera un problème, car l'écrasement
self.threadamènera Python à essayer de ramasser ( supprimer ) le thread précédent pendant son exécution, ce qui est une très mauvaise chose; - une solution possible à cela est de créer le thread avec un parent, ce qui est généralement fait avec un simple
QThread(self), mais ce n'est pas possible dans votre cas car les objets Qt ne peuvent accepter que d'autres objets Qt comme parent, alors que dans votre casselfserait uneUi_MainWindowinstance (qui est un objet python de base); - le point ci-dessus est un problème important, car vous essayez d'implémenter votre programme à partir d'un
pyuicfichier généré, ce qui ne devrait jamais être fait: ces fichiers sont destinés à être laissés tels quels sans aucune modification manuelle, et utilisés uniquement comme modules importés ; en savoir plus sur ce sujet sur les directives officielles sur l' utilisation de Designer ; notez également qu'essayer d'imiter le comportement de ces fichiers est inutile, car cela conduit normalement à une grande confusion sur la structure des objets; - vous pourriez théoriquement ajouter une référence à un objet qt (par exemple, en ajoutant
self.mainWindow = MainWindowdans lasetupUi()fonction) et créer le thread avec cette référence (thread = QThread(self.mainWindow)), ou ajouter le thread à une liste persistante (self.threads = [], encore une fois dans lesetupUi()), mais en raison de ce qui précède point que je vous déconseille fortement de le faire;
Enfin, une implémentation plus correcte de votre code vous obligerait à générer à nouveau le fichier ui, à le laisser tel quel et à faire quelque chose comme l'exemple suivant; notez que j'ai ajouté une implémentation d'exception très basique qui montre également comment interagir correctement avec les signaux personnalisés.
from PyQt5 import QtCore, QtGui, QtWidgets
from mainwindow import Ui_MainWindow
import time
class Calculation(QtCore.QThread):
error = QtCore.pyqtSignal(object)
def run(self):
for i in range(10):
time.sleep(1)
print(i)
try:
10 / 0
except Exception as e:
self.error.emit(e)
break
class MainWindow(QtWidgets.QMainWindow, Ui_MainWindow):
def __init__(self):
super().__init__()
self.setupUi(self)
self.pushButton.pressed.connect(self.threadingc)
def showMessage(self):
msg = QtWidgets.QMessageBox()
msg.setInformativeText('Finish')
msg.exec_()
def threadingc(self):
# create the thread with the main window as a parent, this is possible
# since QMainWindow also inherits from QObject, and this also ensures
# that python will not delete it if you want to start another thread
thread = Calculation(self)
thread.finished.connect(self.showMessage)
thread.error.connect(self.showError)
thread.start()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
mainWindow = MainWindow()
mainWindow.show()
sys.exit(app.exec_())
Dans le cas ci-dessus, le fichier ui a été traité à l'aide de la commande suivante (en supposant que l'interface utilisateur s'appelle "mainwindow.ui", évidemment):
pyuic mainwindow.ui -o mainwindow.py