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

  1. #1
    Membre à l'essai
    Homme Profil pro
    Enseignant
    Inscrit en
    Septembre 2017
    Messages
    30
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 58
    Localisation : France, Calvados (Basse Normandie)

    Informations professionnelles :
    Activité : Enseignant
    Secteur : Enseignement

    Informations forums :
    Inscription : Septembre 2017
    Messages : 30
    Points : 17
    Points
    17
    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 éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    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 462
    Points : 9 249
    Points
    9 249
    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_())
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  3. #3
    Expert éminent

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

    Informations forums :
    Inscription : Octobre 2008
    Messages : 4 300
    Points : 6 780
    Points
    6 780
    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 éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    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 462
    Points : 9 249
    Points
    9 249
    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.
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

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

    Informations professionnelles :
    Activité : Enseignant
    Secteur : Enseignement

    Informations forums :
    Inscription : Septembre 2017
    Messages : 30
    Points : 17
    Points
    17
    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 éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    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 462
    Points : 9 249
    Points
    9 249
    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.
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  7. #7
    Membre à l'essai
    Homme Profil pro
    Enseignant
    Inscrit en
    Septembre 2017
    Messages
    30
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 58
    Localisation : France, Calvados (Basse Normandie)

    Informations professionnelles :
    Activité : Enseignant
    Secteur : Enseignement

    Informations forums :
    Inscription : Septembre 2017
    Messages : 30
    Points : 17
    Points
    17
    Par défaut
    Citation Envoyé par tyrtamos Voir le message
    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.
    Ah ok.

    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.
    La chenille aurait sans doute l'avantage d'être plus simple à mettre en place ... Je trouve tout de même que la progressbar ferait plus propre.

  8. #8
    Expert éminent

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

    Informations forums :
    Inscription : Octobre 2008
    Messages : 4 300
    Points : 6 780
    Points
    6 780
    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

  9. #9
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    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 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut
    Citation Envoyé par VinsS Voir le message
    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.
    Mais si c'est utilisable!
    - On utilise maintenant urllib.request.urlopen et la lecture bloc par bloc, et à chaque bloc lu, on exécute une méthode du thread, et c'est elle qui envoie le signal de progression au graphique.
    - quand j'utilisais urllib.urlretrieve, je déclarais comme callback (avec reporthook) la méthode du thread qui envoyait le signal de progression.

    Voilà un petit code qui télécharge un fichier du web avec progression (utilise urllib.request.urlopen):

    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
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
    # Python 3.5, PyQt 5.9
     
    import sys
    import urllib.request
     
    from PyQt5 import (QtWidgets, QtCore)
     
    #############################################################################
    class Abort(Exception):
        """classe d'exception crée pour l'arrêt du téléchargement avant la fin
        """
        pass
     
    #############################################################################
    class Telecharger(QtCore.QThread):
        """Thread de téléchargement
        """
        # crée de nouveaux signaux
        infotelech = QtCore.pyqtSignal(list) # signal pour la progression
        fintelech = QtCore.pyqtSignal(str) # signal pour la fin du thread
     
        #========================================================================
        def __init__(self, source, destination, taillebloc=8192, parent=None):
            super().__init__(parent)
     
            # prépare les données du téléchargement
            self.source = source
            self.destination = destination
            self.taillebloc = taillebloc # taille de chaque bloc téléchargé
     
            # pour arrêt avant la fin
            self.stop = False 
     
        #========================================================================
        def run(self):
            """lance le téléchargement en tâche de fond
            """
            try:
                with urllib.request.urlopen(self.source) as urlf, open(self.destination, 'wb') as locf:
     
                    # trouve la taille du fichier à télécharger
                    try:
                        taillefichier = int(urlf.headers['Content-Length'])
                    except Exception:
                        taillefichier = 0 # taille du fichier non disponible
     
                    tailletelech = 0 # taille téléchargée cumulée depuis le début
                    while True:
                        data = urlf.read(self.taillebloc) # lit un bloc de la taille prévue
                        if not data: # plus rien à télécharger
                            break
                        else:
                            locf.write(data) # écrit le bloc reçu sur disque
                            tailletelech += len(data) # calcule la taille cumulée"
                            # envoie l'info de progression
                            self.infobloc(tailletelech, taillefichier) 
     
                messagefin = "Téléchargement terminé"
            except Abort:
                messagefin = "Téléchargement avorté"
     
            # fin du thread (normale ou pas): émission du message de fin
            self.fintelech.emit(messagefin)
     
        #========================================================================
        def infobloc(self, tailletelech, taillefichier):
            """reçoit et emet les infos de progression du téléchargement
            """
            # nécessaire pour stopper le téléchargement avant la fin
            if self.stop:
                raise Abort
     
            # envoie les infos de progression à la fenêtre graphique
            self.infotelech.emit([tailletelech, taillefichier])
     
        #========================================================================
        def stoptelech(self):
            """Permet l'arrêt du téléchargement avant la fin
            """
            self.stop = True
     
    #############################################################################
    class Fenetre(QtWidgets.QWidget):
     
        #========================================================================
        def __init__(self, parent=None):
            super().__init__(parent)
     
            self.label1 = QtWidgets.QLabel("Adresse web du fichier à télécharger:", self)
            self.fichierweb = QtWidgets.QLineEdit(self)
     
            self.label2 = QtWidgets.QLabel("Adresse disque du fichier téléchargé:", self)
            self.fichier = QtWidgets.QLineEdit(self)
     
            self.label3 = QtWidgets.QLabel("Lancement/arrêt du téléchargement:", self)
     
            self.depart = QtWidgets.QPushButton("Départ", self)
            self.depart.clicked.connect(self.depart_m)
     
            self.stop = QtWidgets.QPushButton("Stop", self)
            self.stop.clicked.connect(self.stop_m)
     
            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.label1, 0, 0, 1, 2)
            posit.addWidget(self.fichierweb, 1, 0, 1, 2)
            posit.addWidget(self.label2, 2, 0, 1, 2)
            posit.addWidget(self.fichier, 3, 0, 1, 2)
            posit.addWidget(self.label3, 4, 0, 1, 2)
            posit.addWidget(self.depart, 5, 0)
            posit.addWidget(self.stop, 5, 1)
            posit.addWidget(self.barre, 6, 0, 1, 2)
            self.setLayout(posit)
     
            self.telech = None
     
        #========================================================================
        @QtCore.pyqtSlot()
        def depart_m(self):
            """lancement du téléchargement
            """
            if self.telech==None or not self.telech.isRunning():
     
                # initialise la barre de progression
                self.barre.reset()
                self.barre.setRange(0, 100)
                self.barre.setValue(0)
     
                # prépare les données du téléchargement
                source = self.fichierweb.text()
                destination = self.fichier.text()
                if destination=="":
                    destination = source.split('/')[-1]
     
                # démarre le téléchargement
                self.telech = Telecharger(source, destination)
                self.telech.infotelech.connect(self.infotelech_m)
                self.telech.fintelech.connect(self.fintelech_m)
                self.telech.start()
     
        #========================================================================
        @QtCore.pyqtSlot(list)
        def infotelech_m(self, msg):
            """Lancé à chaque réception d'info sur le téléchargement en cours
            """
            tailletelech, taillefichier = msg
     
            if taillefichier > 0:
                # on a la taille maxi: on peut mettre à jour la barre de progression
                p = int(tailletelech/taillefichier*100)
                self.barre.setValue(p)
                QtCore.QCoreApplication.processEvents() # force le rafraichissement
            else:
                # taille maxi inconnue: la barre sera une chenille sans progression
                if self.barre.maximum > 0:
                    self.barre.reset()
                    self.barre.setRange(0, 0)
     
        #========================================================================
        @QtCore.pyqtSlot(str)
        def fintelech_m(self, msg):
            """Lancé quand le thread se termine (normalement ou pas)
            """
            QtWidgets.QMessageBox.information(self,
                "Téléchargement",
                msg)
     
        #========================================================================
        @QtCore.pyqtSlot()
        def stop_m(self):
            """demande l'arrêt du téléchargement avant la fin
            """
            if self.telech!=None and self.telech.isRunning():
                self.telech.stoptelech()
     
        #========================================================================
        def closeEvent(self, event):
            """lancé à la fermeture de la fenêtre quelqu'en soit la méthode
            """
            # arrête un éventuel téléchargement en cours
            self.stop_m() 
            # et accepte l'arrêt de la fenêtre
            event.accept()
     
    #############################################################################
    if __name__ == "__main__":
        app = QtWidgets.QApplication(sys.argv)
        fen = Fenetre()
        fen.show()
        sys.exit(app.exec_())
    Pour un exemple d'application:
    source: "https://docs.python.org/3/archives/python-3.7.0-docs-pdf-a4.zip"
    destination: "python-3.7.0-docs-pdf-a4.zip"
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  10. #10
    Expert éminent

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

    Informations forums :
    Inscription : Octobre 2008
    Messages : 4 300
    Points : 6 780
    Points
    6 780
    Par défaut
    Je ne vois pas beaucoup de différence à mon code, sauf que dans mon appli le thread est créé par le moteur de l'appli elle-même. Ceci pour des raisons pratiques.

    Pour le reste, c'est toujours l'utilisation du signal Qt qui résout la question.

  11. #11
    Membre à l'essai
    Homme Profil pro
    Enseignant
    Inscrit en
    Septembre 2017
    Messages
    30
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 58
    Localisation : France, Calvados (Basse Normandie)

    Informations professionnelles :
    Activité : Enseignant
    Secteur : Enseignement

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

    Une question bête, pour avoir le mode chenille, c'est bien comme ceci ? :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    ...
    self.barre.setRange(0, 0)
    ...
    Si c'est bien le cas, ça ne fonctionne pas en mode chenille.

    a+

  12. #12
    Expert éminent
    Avatar de tyrtamos
    Homme Profil pro
    Retraité
    Inscrit en
    Décembre 2007
    Messages
    4 462
    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 462
    Points : 9 249
    Points
    9 249
    Billets dans le blog
    6
    Par défaut
    Bonjour,

    Voilà un petit code qui fait fonctionner le mode chenille:

    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
    #! /usr/bin/python3
    # -*- coding: utf-8 -*-
    # Python 3
     
    import sys
    from PyQt5 import (QtWidgets, QtCore)
     
    #############################################################################
    class ProgressBar(QtWidgets.QWidget):
     
        #========================================================================
        def __init__(self, parent=None):
            super().__init__(parent)
     
            self.setGeometry(300, 300, 250, 150)
            self.setWindowTitle('Chenille')
     
            # initialisation de la barre de progression
            self.pbar = QtWidgets.QProgressBar(self)
            self.pbar.setTextVisible(False)
            self.pbar.setMinimum(0)
     
            self.bouton = QtWidgets.QPushButton('Start', self)
            self.bouton.clicked.connect(self.onStart)
     
            posit = QtWidgets.QGridLayout()
            posit.addWidget(self.pbar, 0, 0)
            posit.addWidget(self.bouton, 1, 0)
            self.setLayout(posit)
     
            self.encours = False
     
        #========================================================================
        def onStart(self):
            if self.encours:
                self.encours = False
                self.bouton.setText('Start')
                self.pbar.setMaximum(100) # <=== arrêt de la chenille
            else:
                self.encours = True
                self.bouton.setText('Stop')
                self.pbar.setMaximum(0) # <=== chenille
     
    #############################################################################
    if __name__ == '__main__':
        app = QtWidgets.QApplication(sys.argv)
        fen = ProgressBar()
        fen.show()
        sys.exit(app.exec_())
    Le motif de la chenille est donné par le style demandé (par exemple le style "Fusion"), mais on peut le changer avec setStyleSheet.
    Voir par exemple sur le web:
    https://forum.qt.io/topic/53447/solv...n-qprogressbar
    http://blog.qt.io/blog/2007/06/12/st...nd-qscrollbar/

    A essayer l'un de ces exemples à mettre au lancement de la chenille (ligne 43):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    self.pbar.setStyleSheet("QProgressBar::chunk {background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0,stop: 0 #F10350,stop: 0.4999 #FF3320,stop: 0.5 #FF0019,stop: 1 #FF0000 );}")
    self.pbar.setStyleSheet("QProgressBar::chunk {background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0,stop: 0 #F10350,stop: 0.4999 #FF3320,stop: 0.5 #FF0019,stop: 1 #F0F150 );}")
    self.pbar.setStyleSheet("QProgressBar::chunk {background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0,stop: 0 #F10350,stop: 0.4999 #FF3320,stop: 0.5 #FF0019,stop: 1 #05F150 );}")
    self.pbar.setStyleSheet("QProgressBar::chunk {background: QLinearGradient( x1: 0, y1: 0, x2: 1, y2: 0,stop: 0 #FF0000,stop: 0.7 #FFFF00,stop: 1 #00B400 );}")
    self.pbar.setStyleSheet("QProgressBar::chunk:horizontal {background: qlineargradient(x1: 0, y1: 0.5, x2: 1, y2: 0.5, stop: 0 green, stop: 1 white);}")
    self.pbar.setStyleSheet("QProgressBar::chunk:horizontal {background: qlineargradient(x1: 0, y1: 0.5, x2: 1, y2: 0.5, stop: 0 green, stop: 1 white);margin-right: 2px; /* space */width: 10px;}")
    Un expert est une personne qui a fait toutes les erreurs qui peuvent être faites, dans un domaine étroit... (Niels Bohr)
    Mes recettes python: http://www.jpvweb.com

  13. #13
    Membre à l'essai
    Homme Profil pro
    Enseignant
    Inscrit en
    Septembre 2017
    Messages
    30
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 58
    Localisation : France, Calvados (Basse Normandie)

    Informations professionnelles :
    Activité : Enseignant
    Secteur : Enseignement

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

    Finalement je suis tombé sur une de tes pages concernant un throbber, j'avoue que ça me plaît bien.

    Une petite copie d'écran :

    Nom : throbber_001.png
Affichages : 1766
Taille : 9,6 Ko

    Par contre j'ai l'erreur suivante à la fermeture :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    Exception ignored in: <module 'threading' from '/usr/lib/python3.4/threading.py'>
    Traceback (most recent call last):
      File "/usr/lib/python3.4/threading.py", line 1289, in _shutdown
        assert tlock.locked()
    AssertionError:
    Pour l'instant après avoir modifié le fonctionnement de la fonction run (pour tester), je n'ai fait que modifier le style de la sorte : self.label.setStyleSheet("background-color:rgba(255, 255, 255, 0);") et j'ai rajouté self.close() à la fin de la fonction findeprogramme.

    a+

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