Utilisation de QThread pour afficher QProgressDialog dans PyQt5 [duplicate]

Sep 17 2020

J'utilise PyQt5 pour écrire une application qui gère les commandes clients. Lors de la création ou de la suppression d'une commande, je souhaite afficher une boîte de dialogue de progression de style marqee pour indiquer que l'application fonctionne. J'ai visité de nombreux articles où la réponse impliquait l'utilisation de QThread.J'ai essayé de l'implémenter mais il semble que je manque quelque chose. Ceci est ma classe de filetage.

class Worker(QThread):
    finished = Signal()

def run(self):
    self.x = QProgressDialog("Please wait..",None,0,0)
    self.x.show()

def stop(self):
    self.x.close()

Dans l' init de la fenêtre principale, je crée self.worker = Worker ()

Maintenant, le code pour supprimer une entrée est par exemple:

msg = MsgBox("yn", "Delete Order", "Are you sure you want to delete this order?") # Wrapper for the QMessageBox
if msg == 16384:
    self.worker.start()   ## start the worker thread, hoping to start the progress dialog
    session.delete(order) ##delete order from db
    session.commit()      ##commit to db
    self.change_view("Active", 8) ##func. clean up the table.
    self.worker.finished.emit()   ##emit the finished signal to close the progress dialog

Le résultat est qu'aucune boîte de dialogue de progression ne s'affiche. L'interface graphique se fige pendant une seconde ou deux, puis l'entrée est supprimée sans qu'aucune boîte de dialogue de progression ne soit affichée.

Désolé, mon code est assez long, donc je ne pouvais pas tout inclure ici, je voulais juste voir si quelque chose ne va vraiment pas.

Réponses

1 musicamante Sep 17 2020 at 21:30

Il y a deux problèmes principaux avec votre code:

  1. Les éléments de l'interface graphique (tout ce qui est hérité ou lié à une sous-classe QWidget) doivent être créés et accessibles uniquement à partir du thread Qt principal.
  2. en supposant que ce qui prend un certain temps, ce sont les opérations de suppression / validation, ce sont ces opérations qui doivent aller dans le thread tout en affichant la boîte de dialogue de progression du thread principal, et non l'inverse. Considérez également queQThread y a déjà un finished()signal et vous ne devez pas l'écraser.

Voici un exemple basé sur votre code:

class Worker(QThread):
    def __init__(self, session, order):
        super.__init__()
        self.session = session
        self.order = order

    def run(self):
        self.session.delete(self.order)
        self.session.commit()


class Whatever(QMainWindow):
    def __init__(self):
        super().__init__()
        # ...
        self.progressDialog = QProgressDialog("Please wait..", None, 0, 0, self)

    def deleteOrder(self, session, order):
        msg = MsgBox("yn", "Delete Order", 
            "Are you sure you want to delete this order?")
        if msg == MsgBox.Yes: # you should prefer QMessageBox flags
            self.worker = Worker(session, order)
            self.worker.started(self.progressDialog.show())
            self.worker.finished(self.deleteCompleted)
            self.worker.start()

    def deleteCompleted(self):
        self.progressDialog.hide()
        self.change_view("Active", 8)

Étant donné que la boîte de dialogue de progression doit rester ouverte pendant le traitement, vous devez également empêcher l'utilisateur de la fermer. Pour ce faire, vous pouvez y installer un filtre d'événement et vous assurer que tout événement de fermeture est accepté; De plus, puisque QProgressDialog hérite de QDialog, la Escclé doit être filtrée, sinon elle ne fermera pas la boîte de dialogue, mais la rejetterait et la masquerait.

class Whatever(QMainWindow):
    def __init__(self):
        super().__init__()
        # ...
        self.progressDialog = QProgressDialog("Please wait..", None, 0, 0, self)
        self.progressDialog.installEventFilter(self)

    def eventFilter(self, source, event):
        if source == self.progressDialog:
            # check for both the CloseEvent *and* the escape key press
            if event.type() == QEvent.Close or event == QKeySequence.Cancel:
                event.accept()
                return True
        return super().eventFilter(source, event)