IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

PyQt Python Discussion :

Python3 et PyQt5, Besoin d'aide pour barre de progression et QThread


Sujet :

PyQt Python

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre averti
    Homme Profil pro
    Enseignant
    Inscrit en
    Septembre 2017
    Messages
    30
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 59
    Localisation : France, Calvados (Basse Normandie)

    Informations professionnelles :
    Activité : Enseignant
    Secteur : Enseignement

    Informations forums :
    Inscription : Septembre 2017
    Messages : 30
    Par défaut Python3 et PyQt5, Besoin d'aide pour barre de progression et QThread
    Bonjour ,

    Dans mon application j'aimerais créer une progressbar qui s'accorde avec le traitement en cours.

    Il va sans doute falloir que j'utilise les Thread (QThread) mais je ne sais pas du tout comment m'y prendre, un bon coup de main serait le bienvenu.

    Sous GNU/Linux, le traitement avec pdfkit se fait assez rapidement (donc la barre de progression n'est pas indispensable, mais ce serait pas mal quand même). Sous windows par contre le traitement prend un peu plus de temps et la barre de progression, là, est indispensable car l'interface se fige quelques secondes pendant le traitement, c'est vraiment embêtant !

    Voilà un extrait de mon code (pour la barre de progression et le traitement, j'ai mis en place, pour l'instant, quelque chose de franchement très moche !) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    import os, sys, tempfile, pdfkit
     
    import time # Essai
     
    # Classe recherchant le chemin de wkhtmltopdf.exe sous windows
    from config_apsc.capture_chemin_wkhtmltopdf_exe import CaptureWkhtmltopdfWindows
     
    ###########################################################
    # ESSAI
    ###########################################################
    from PyQt5.QtWidgets import (QProgressBar, QApplication)
    from PyQt5.QtCore import (QEventLoop, QTimer)
    ###########################################################
     
    class Html_vers_pdf_fiche_prof():
        def __init__(self, niv_de_classe, nbre_seances, titre_prog, num_seq, incitation, demande, notions_abordees, \
                    consignes, contraintes, entree_principal_programme, questionnement, que_vont_app_les_elev, \
                    l_champ_pratiqu_plast, l_question_dens, l_vocabulaire, l_questions, nbre_ref_art, l_ref_art, \
                    l_comp_et_pts_chiffres, observations, prolongements_possibles, marge_gauche, marge_haute, \
                    marge_droite, marge_basse, police_normaux_caracteres, police_petits_caracteres, chemin_nom_pdf_final):
            super(Html_vers_pdf_fiche_prof, self).__init__()
     
    	# Traitement des variables, du code html ...
     
            # Tous les morceaux html sont additionnés pour en faire un seul
            self.html_total = html_entete + html_tab_ligne_0 + html_tab_ligne_1 + html_tab_ligne_2 + html_tab_ligne_3 + html_tab_ligne_4 + html_tab_ligne_5 + html_tab_ligne_6 + html_tab_ligne_7 + html_tab_ligne_8 + html_tab_ligne_9 + html_tab_ligne_10 + html_tab_ligne_11 + html_tab_ligne_12 + html_tab_ligne_13 + html_tab_ligne_14 + html_tab_ligne_15 + html_tab_ligne_16 + html_tab_ligne_17 + html_tab_ligne_18 + html_tab_ligne_19 + html_tab_ligne_20 + html_tab_ligne_21 + html_tab_ligne_22 + html_tab_ligne_23 + html_tab_ligne_24 + html_tab_ligne_25 + html_tab_ligne_26 + html_tab_ligne_27 + html_body_html_final
     
    	# ...
     
            # Utilisation de pdfkit (donc de wkthmltopdf ---> html vers pdf)
            options = {'encoding': 'UTF-8', 'page-size': 'A4', 'orientation':'Portrait', 'footer-right': '[page]', 'footer-line':'', 'footer-font-size':'10', 'footer-spacing':'2', 'footer-left': niv_de_classe+'ème  '+titre_prog+'  '+'SEQ'+num_seq, 'header-center': 'Séquence réalisée par '+nom_prof+", professeur d'arts plastiques au "+college+', en utilisant '+logiciel_nom+'.', 'header-spacing':'1', 'header-font-size': '7', 'margin-top': str(marge_haute)+'mm', 'margin-left': str(marge_gauche)+'mm', 'margin-right': str(marge_droite)+'mm', 'margin-bottom': str(marge_basse)+'mm'}
     
    	# Pas de barre de progression, pour l'instant pour le traitement sous windows
            if sys.platform == "win32" : 
                # Appel de la classe pour rechercher
                # whhtmltopdf sous windows              
                capt = CaptureWkhtmltopdfWindows()
                # Recherche du 1er chemin dans la liste 
                chem_wkhtmltopdf = capt.capture()
                # Config
                config = pdfkit.configuration(wkhtmltopdf=chem_wkhtmltopdf)
                # Exécution
                pdfkit.from_string(self.html_total, chemin_nom_pdf_final, configuration=config, options=options)
            elif sys.platform == 'linux' :
                ####################
                # ESSAI
                ####################
                # ProgressBar
                progress = QProgressBar()
                progress.setGeometry(200, 400, 600, 30)
                progress.setWindowTitle("Traitement ...")
                #
                secondes = 15
                timer = 0 
                while (timer < secondes) :
                    timer = timer + 1
                    time.sleep(1)
                    progress.setValue((100*timer)/secondes)
                    progress.show()
                    QApplication.processEvents()
                #
                # Exécution    
                pdfkit.from_string(self.html_total, chemin_nom_pdf_final, options=options)
    En espérant que vous pourrez m'aider.

    a+

  2. #2
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    L'utilisation d'un thread avec le graphique répond à un problème assez général: le graphique doit toujours garder la main. Cela veut dire que dès qu'on a une tâche longue, on la fait dans un thread pour que le graphique ne se fige pas.

    Il y a tout de même un principe à respecter: le thread lui-même ne doit pas toucher directement au graphique! La conséquence est qu'il faut utiliser QThread (le thread de Qt) car, contrairement au module threading, il pourra envoyer des signaux pour la mise à jour du graphique pendant l'exécution et à la fin du thread!

    Cela pose tout de même un problème pour le type de widget de progression: l'exécution de la tâche longue dans le thread peut-elle ou non envoyer un signal de progression? Cela suppose que cette exécution se fait en plusieurs "blocs" et qu'on connait au départ le nombre total de ces "blocs" (progression=100%).

    Si ce n'est pas le cas, on peut utiliser 2 solutions pour signaler à l'utilisateur que l'exécution est en cours (et que le programme n'est pas planté!):
    - transformer la barre de progression en "chenille" (il suffit de donner une plage de 0,0)
    - utiliser plutôt un "throbber", c'est à dire une roue qui tourne pendant l'exécution du thread

    Voilà un exemple de code commenté pour montrer le principe d'une exécution longue dont on peut montrer la progression dans une barre graphique (Python 3, PyQt5):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
     
    import sys
    import time
    from PyQt5 import (QtWidgets, QtCore)
     
    #############################################################################
    class Operationlongue(QtCore.QThread):
     
        # création des nouveaux signaux
        info = QtCore.pyqtSignal(int) # signal pour informer d'une progression
        fini = QtCore.pyqtSignal(bool, list) # signal pour la fin du thread
     
        #========================================================================
        def __init__(self, parent=None):
            super().__init__(parent)
     
            self.stop = False # drapeau pour exécuter une demande d'arrêt
     
        #========================================================================
        def run(self):
            """partie qui s'exécute en tâche de fond
            """
            for i in range(0, 101):
                if self.stop:
                    break # arrêt anticipé demandé
                time.sleep(0.05)
                self.info.emit(i) # envoi du signal de progression d'exécution
            # fin du thread: émission du signal de fin avec transmission de données
            self.fini.emit(self.stop, [1,2,3]) # envoi du signal de fin d'exécution
     
        #========================================================================
        def arreter(self):
            """pour arrêter avant la fin normale d'exécution du thread
            """
            self.stop = True
     
    #############################################################################
    class Fenetre(QtWidgets.QWidget):
     
        #========================================================================
        def __init__(self, parent=None):
            super(Fenetre,self).__init__(parent)
     
            # bouton de lancement du thread
            self.depart = QtWidgets.QPushButton("Départ", self)
            self.depart.clicked.connect(self.lancement)
     
            # bouton d'arrêt anticipé du thread
            self.arret = QtWidgets.QPushButton(u"Arrêt", self)
            self.arret.clicked.connect(self.arreter)
     
            # barre de progression
            self.barre = QtWidgets.QProgressBar(self)
            self.barre.setRange(0, 100)
            self.barre.setValue(0)
     
            # positionne les widgets dans la fenêtre
            posit = QtWidgets.QGridLayout()
            posit.addWidget(self.depart, 0, 0)
            posit.addWidget(self.arret, 1, 0)
            posit.addWidget(self.barre, 2, 0)
            self.setLayout(posit)
     
            # initialisation variable d'instance de classe
            self.operationlongue = None
     
        #========================================================================
        @QtCore.pyqtSlot(bool)
        def lancement(self, ok=False):
            """lancement de l'opération longue dans le thread
            """
            if self.operationlongue==None or not self.operationlongue.isRunning():
                # initialise la barre de progression
                self.barre.reset()
                self.barre.setRange(0, 100)
                self.barre.setValue(0)
                # initialise l'opération longue dans le thread
                self.operationlongue = Operationlongue()
                # prépare la réception du signal de progression
                self.operationlongue.info.connect(self.progression)
                # prépare la réception du signal de fin
                self.operationlongue.fini.connect(self.stop)
                # lance le thread (mais ne l'attend pas avec .join!!!)
                self.operationlongue.start()
     
        #========================================================================
        @QtCore.pyqtSlot(int)
        def progression(self, i):
            """lancé à chaque réception d'info de progression émis par le thread
            """
            self.barre.setValue(i)
            QtCore.QCoreApplication.processEvents() # force le rafraichissement
     
        #========================================================================
        @QtCore.pyqtSlot(bool)
        def arreter(self, ok=False):
            """pour arrêter avant la fin
            """
            if self.operationlongue!=None and self.operationlongue.isRunning():
                self.operationlongue.arreter()
     
        #========================================================================
        @QtCore.pyqtSlot(bool, list)
        def stop(self, fin_anormale=False, liste=()):
            """Lancé quand le thread se termine
            """
            if fin_anormale:
                # fin anticipée demandée
                QtWidgets.QMessageBox.information(self,
                    "Opération longue",
                    "Arrêt demandé avant la fin!")
            else:
                # fin normale
                self.barre.setValue(100)
                QtWidgets.QMessageBox.information(self,
                    "Opération longue",
                    "Fin normale!")
                # récupération des infos transmises par signal à la fin du thread
                print(liste)
     
        #========================================================================
        def closeEvent(self, event):
            """lancé à la fermeture de la fenêtre quelqu'en soit la méthode
            """
            # si le thread est en cours d'eécution, on l'arrête (brutalement!)
            if self.operationlongue!=None and self.operationlongue.isRunning():
                self.operationlongue.terminate()
            # et on accepte la fermeture de la fenêtre dans tous les cas
            event.accept()
     
    #############################################################################
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        fen = Fenetre()
        fen.setAttribute(QtCore.Qt.WA_DeleteOnClose)
        fen.show()
        sys.exit(app.exec_())

  3. #3
    Expert confirmé

    Homme Profil pro
    Inscrit en
    Octobre 2008
    Messages
    4 307
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2008
    Messages : 4 307
    Par défaut
    Salut,

    Citation Envoyé par tyrtamos Voir le message
    ... La conséquence est qu'il faut utiliser QThread (le thread de Qt) car, contrairement au module threading, il pourra envoyer des signaux pour la mise à jour du graphique pendant l'exécution et à la fin du thread!
    Non, il n'y a aucun problème avec un thread Python et les signaux Qt.

    Mais il faut bien utiliser un signal Qt, avec un simple callback ça n'ira pas car la fonction appelée s'exécutera dans le thread.

  4. #4
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Bonjour VinsS

    Citation Envoyé par VinsS Voir le message
    Non, il n'y a aucun problème avec un thread Python et les signaux Qt.
    Je ne vois pas comment envoyer un signal Qt à partir d'une classe héritant seulement de threading: peux-tu me montrer?

    La seule façon que je vois pour faire ça est d'avoir une classe héritant en même temps de threading ET de QObject. Le signal pourra être émis grâce à QObject.

    A noter qu'un des avantages de QThread sur threading est qu'on peut arrêter un thread actif avec terminate. Même s'il vaut mieux éviter ça, ça fonctionne.

  5. #5
    Membre averti
    Homme Profil pro
    Enseignant
    Inscrit en
    Septembre 2017
    Messages
    30
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 59
    Localisation : France, Calvados (Basse Normandie)

    Informations professionnelles :
    Activité : Enseignant
    Secteur : Enseignement

    Informations forums :
    Inscription : Septembre 2017
    Messages : 30
    Par défaut
    Salut tyrtamos,

    Merci pour la réponse .

    Si je suis ton exemple, l'exécution de pdfkit (---> pdfkit.from_string(self.html_total, chemin_nom_pdf_final, options=options)) se retrouverait dans la fonction run ?

    a+

  6. #6
    Expert confirmé
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 486
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Var (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Retraité

    Informations forums :
    Inscription : Décembre 2007
    Messages : 4 486
    Billets dans le blog
    6
    Par défaut
    Citation Envoyé par ekdmekdm Voir le message
    Si je suis ton exemple, l'exécution de pdfkit (---> pdfkit.from_string(self.html_total, chemin_nom_pdf_final, options=options)) se retrouverait dans la fonction run ?
    Oui, puisque c'est la tâche longue qui, dans ta solution, fige le graphique. Et ce qui se trouvera dans la méthode run se déroulera en tâche de fond.

    Reste à savoir si tu peux saisir une info de progression pendant l'exécution du pdfkit, ou si tu veux simplement indiquer que le calcul est en cours.

  7. #7
    Expert confirmé

    Homme Profil pro
    Inscrit en
    Octobre 2008
    Messages
    4 307
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : Belgique

    Informations forums :
    Inscription : Octobre 2008
    Messages : 4 307
    Par défaut
    J'ai un exemple concret.

    Pour mon appli Qarte j'ai besoin de télécharger de gros fichiers sans figer l'interface et en mettant à jour la progression du téléchargement.

    Python propose urllib.urlretrieve malheureusement cette fonction demande un callback pour communiquer les infos de progression, ce qui n'est pas utilisable dans un thread.

    Donc j'ai créé ce code qui est lancé dans un thread et qui se sert de signaux Qt
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
     
    # -*- coding: utf-8 -*-
     
    # loader.py
    # This file is part of qarte-4
    #    
    # Author: Vincent Vande Vyvre <vincent.vandevyvre@oqapy.eu>
    # Copyright: 2011-2018 Vincent Vande Vyvre
    # Licence: GPL3
    # Home page: https://launchpad.net/qarte
     
     
    import contextlib
    import urllib.request
    import time
     
    from urllib.error import URLError
     
    from PyQt5.QtCore import QObject, pyqtSignal
     
    class Loader(QObject):
        loadingProgress = pyqtSignal(int, int, int)
        loadingFinished = pyqtSignal(str)
        def __init__(self):
            super().__init__()
            self.is_alive = False
     
        def config_loader(self, url, filename):
            self.url = url
            self.filename = filename
            self.kill_request = False
            self.last_error = ''
            try:
                with contextlib.closing(urllib.request.urlopen(url, None)) as fp:
                    headers = fp.info()
                    if "content-length" in headers:
                        self.size = int(headers["Content-Length"])
                    else:
                        self.size = 0
            except URLError as why:
                self.size = why
     
            return self.size
     
        def download_mp4(self):
            """A hacked version of urllib.request.urlretrieve
     
            """
            self.is_alive = True
            size = -1
            try:
                with contextlib.closing(urllib.request.urlopen(self.url, None)) as fp:
                    bs = 1024*16
                    percent = 1
                    blocknum = 0
                    amount = 0
                    blocks_percent = 0
                    begin_at = time.time()
                    if self.size:
                        blocks_percent = int(self.size / bs / 100)
     
                    with open(self.filename, 'wb') as target:
                        while True:
                            block = fp.read(bs)
                            if not block or self.kill_request:
                                break
     
                            target.write(block)
                            blocknum += 1
                            amount += bs
                            if blocknum == blocks_percent:
                                elapsed = time.time() - begin_at
                                speed = int(amount / elapsed)
                                remain = int(self.size - amount)
                                self.loadingProgress.emit(percent, speed, remain)
                                percent += 1
                                blocknum = 0
            except URLError as why:
                self.last_error = why
                self.filename = ''
     
            self.is_alive = False
            self.loadingFinished.emit(self.filename)
    Avec en plus les avantages de connaître la taille du fichier d'avance et de pouvoir stopper le téléchargement sans faire un kill sur le pid du thread

Discussions similaires

  1. besoin d'aide pour une requête
    Par Damien69 dans le forum Langage SQL
    Réponses: 11
    Dernier message: 31/03/2004, 15h38
  2. besoin d'aide pour le composant DBComboBox
    Par jane2002 dans le forum Bases de données
    Réponses: 8
    Dernier message: 28/02/2004, 19h01
  3. [Kylix] besoin d'aide pour installer kylix3
    Par Sph@x dans le forum EDI
    Réponses: 3
    Dernier message: 11/02/2004, 13h53
  4. [TP]besoin d'aide pour commandes inconnues
    Par Upal dans le forum Turbo Pascal
    Réponses: 15
    Dernier message: 03/10/2002, 10h48
  5. Besoin d'aide pour l'I.A. d'un puissance 4
    Par Anonymous dans le forum C
    Réponses: 2
    Dernier message: 25/04/2002, 17h05

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo