Outils pour utilisateurs

Outils du site


pyqt5_throbber

Mettre en place un throbber (image gif animée pour activité en attente)

[PyQt5]

Objectif

Dans un environnement graphique, il y a souvent des activités qui demandent un “certain temps” (plusieurs secondes, quelquefois plus), qui nécessitent de signaler à l'utilisateur que cette activité est en train de se dérouler (et donc que le programme n'est pas planté).

On peut, bien sûr utiliser une barre de progression (QProgressBar) dans sa configuration “chenille”, mais ici on va utiliser une image gif animée. On rencontre souvent cette solution sous forme de roue qui tourne.

Pour créer le throbber qu'on veut, on peut utiliser ce site web: http://www.ajaxload.info/.

On va ici étudier 2 cas: soit on met le throbber dans la page graphique en cours, soit on le met dans une petite fenêtre surgissante qui apparait le temps du déroulement de l'activité et disparait ensuite. L'animation de l'image ne doit être visible que pendant le déroulement de l'activité.

Bien entendu, pour que l'activité en question ne bloque pas le graphique, il faut qu'elle se déroule dans un thread ou dans un processus. Mais il faut qu'on puisse signaler au graphique que le programme ainsi lancé est terminé, de façon à arrêter le throbber: c'est l'intérêt d'utiliser les classes de PyQt (QThread ou QProcess) qui pourront envoyer un signal de fin au graphique. Solution alternative: utiliser un héritage multiple: threading+QObject.

Throbber dans la page graphique normale

Ici, on va mettre le throbber dans un QLabel qui sera placé dans la page graphique en cours.

L'activité est simulée par une tempo de 5 secondes située dans un thread (classe Calcul dans le code).

On a mis des options qui sont peut-être inutiles dans d'autres cas: couleur de fond jaune et bordure rouge du QLabel: pour s'en passer, il suffit de ne pas mettre les lignes en question.

Voilà la copie d'écran de l'exemple ci-dessous en pleine activité:

Et voilà le code largement documenté:

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Python 3
 
import sys, os
import time
 
from PyQt5.QtCore import (Qt, QSize, pyqtSlot, pyqtSignal, QThread)
from PyQt5.QtGui import (QMovie)
from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QLabel, 
                             QGridLayout)
 
#############################################################################
class Calcul(QThread):
 
    # crée un nouveau signal pour indiquer la fin du thread
    finduthread = pyqtSignal()
 
    #========================================================================
    def __init__(self, parent=None):
        super(Calcul, self).__init__(parent)
 
    #========================================================================
    def run(self):
        # tempo de 5 secondes pour l'exemple
        time.sleep(5)
        # émet le signal de fin du thread
        self.finduthread.emit()
 
#############################################################################
class Fenetre(QWidget):
 
    #========================================================================
    def __init__(self, parent=None):
        super(Fenetre, self).__init__(parent)
        self.resize(400, 300)
 
        # crée le bouton qui lancera le thread
        self.bouton = QPushButton("Lancer le programme!", self)
        self.bouton.clicked.connect(self.lancementprogramme)
 
        # crée un movie avec le throbber sous forme d'image "gif" animée
        imagif = os.path.abspath("throbbergif.gif")
        self.movie = QMovie(imagif)
        self.movie.setScaledSize(QSize(60, 60)) # dimension 60x60
 
        # crée du QLabel pour intégrer le throbber
        self.label = QLabel()
        # redimenssionne 80x80
        self.label.resize(QSize(80, 80))
        # interdit le redimenssionnement à la souris
        self.label.setFixedSize(self.label.width(), self.label.height()) 
        # option: met un fond jaune et un cadre rouge
        self.label.setStyleSheet("background-color:yellow; border: 1px solid  #ff0000 ;") 
        # le contenu du QLabel sera au milieu (hor. et vert.)
        self.label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
 
        # positionne les widgets dans la fenêtre
        posit = QGridLayout()
        posit.addWidget(self.bouton, 0, 0, 1, 3)
        posit.addWidget(self.label, 1, 1, 1, 1)
        self.setLayout(posit)
 
        self.thread = None # signale qu'il n'y a aucun thread en cours
 
    #========================================================================
    @pyqtSlot()
    def lancementprogramme(self):
        """slot exécuté lors du clic sur le bouton: lance le thread 
        """
        if self.thread != None:
            return # = il y a déjà un thread en cours: on ne fait rien
 
        # affiche le throbber
        self.label.setMovie(self.movie) # affecte le movie au QLabel
        self.movie.start() # lance l'animation du throbber
 
        # lance le thread
        self.thread = Calcul()
        self.thread.finduthread.connect(self.findeprogramme)
        self.thread.start()
 
    #========================================================================
    @pyqtSlot()
    def findeprogramme(self):
        """slot exécuté lors de la clôture du thread 
        """
        # arrête l'affichage du throbber
        self.movie.stop() # arrête l'animation
        self.label.clear() # efface le contenu du QLabel
 
        # traite la fin du thread
        self.thread.finduthread.disconnect() # retire le lien avec la méthode
        self.thread = None # signale qu'il n'y a plus de thread en cours
 
#############################################################################
if __name__ == "__main__":
    app = QApplication(sys.argv)
    fen = Fenetre()
    fen.show()
    sys.exit(app.exec_())


Throbber dans une fenêtre surgissante

Ici, on va mettre le throbber dans une fenêtre héritée d'un QLabel (classe ThrobberWindow).

On a ici ajouté quelques astuces purement cosmétiques: un cadre simplifié pour la fenêtre et un fond jaune pour attirer l’œil: on peut bien sûr s'en passer!

Voilà la copie d'écran de l'exemple ci-dessous en pleine activité:

Et voilà le code largement documenté:

#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Python 3
 
import sys, os
import time
 
from PyQt5.QtCore import (Qt, QSize, pyqtSlot, pyqtSignal, QThread)
from PyQt5.QtGui import (QMovie)
from PyQt5.QtWidgets import (QApplication, QWidget, QPushButton, QLabel, 
                             QGridLayout)
 
#############################################################################
class Calcul(QThread):
 
    # crée un nouveau signal pour indiquer la fin du thread
    finduthread = pyqtSignal()
 
    #========================================================================
    def __init__(self, parent=None):
        super(Calcul, self).__init__(parent)
 
    #========================================================================
    def run(self):
        # tempo de 5 secondes pour l'exemple
        time.sleep(5)
        # émet le signal de fin du thread
        self.finduthread.emit()
 
#############################################################################
class ThrobberWindow(QLabel):
    """petite fenêtre qui affiche un throbber (image gif animée) pour 
       signaler une activité en cours et faire patienter l'utilisateur
    """
 
    #========================================================================
    def __init__(self, parent=None):
        super(ThrobberWindow, self).__init__(parent)
 
        # image gif animée du throbber
        imagif = os.path.abspath("throbbergif.gif")
 
        # configure la fenêtre
        self.setWindowTitle(" ") # supprime le titre de la fenêtre
        self.resize(160, 160) # met une fenêtre carrée
        self.setFixedSize(self.width(), self.height()) # pas de redimenssionnement
        self.setStyleSheet("background-color:yellow;") # met un fond jaune
        # le contenu du QLabel sera au milieu (hor. et vert.)
        self.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
 
        # met seulement un cadre autour de la fenêtre du throbber
        # NB: mais on peut encore la déplacer à la souris!
        newflags = Qt.Dialog
        newflags |= Qt.CustomizeWindowHint  # permet la personnalisation
        newflags &= ~Qt.WindowCloseButtonHint  # pas de case de fermeture
        newflags &= ~Qt.WindowSystemMenuHint  # pas de menu de fenêtre
        newflags &= ~Qt.WindowContextHelpButtonHint # pas de case "?"
        self.setWindowFlags(newflags)
 
        # crée un movie avec le throbber sous forme d'image "gif" animée
        self.movie = QMovie(imagif)
        self.movie.setScaledSize(QSize(60, 60)) # fixe dimension 60x60
 
        self.setMovie(self.movie) # affecte le movie au QLabel
        self.movie.start() # lance l'animation du throbber
 
    #========================================================================
    def closeEvent(self, event):
        """méthode exécutée lors de la fermeture de la fenêtre
        """
        self.movie.stop() # arrête l'animation
        self.clear() # efface le contenu du QLabel
        event.accept() # accepte la fermeture
 
#############################################################################
class Fenetre(QWidget):
    """fenêtre pour tester le throbber
    """
 
    #========================================================================
    def __init__(self, parent=None):
        super(Fenetre, self).__init__(parent)
 
        self.resize(400, 300)
 
        # créer le bouton
        self.bouton = QPushButton("Lancer le programme!", self)
        self.bouton.clicked.connect(self.lancementprogramme)
 
        # positionne le widget dans la fenêtre
        posit = QGridLayout()
        posit.addWidget(self.bouton, 0, 0)
        self.setLayout(posit)
 
        # pour signaler qu'il n'y a aucun thread en cours
        self.thread = None 
 
    #========================================================================
    @pyqtSlot()
    def lancementprogramme(self):
        """slot exécuté lors du clic sur le bouton: lance le thread
        """
        if self.thread != None:
            return # il y a déjà un thread en cours: on ne fait rien
 
        # crée le throbber
        self.throbber = ThrobberWindow()
        # option: met la fenêtre du throbber en mode "modal"
        self.throbber.setWindowModality(Qt.ApplicationModal)
        # affiche la petite fenêtre du throbber
        self.throbber.show()
 
        # lance le thread
        self.thread = Calcul()
        self.thread.finduthread.connect(self.findeprogramme)
        self.thread.start()
 
    #========================================================================
    @pyqtSlot()
    def findeprogramme(self):
        """slot exécuté lors de la clôture du thread
        """
        # ferme la fenêtre du throbber
        self.throbber.close() 
 
        # traite la fin du thread
        self.thread.finduthread.disconnect() # retire le lien avec la méthode
        self.thread = None # indique qu'il n'y a plus de thread en cours
 
#############################################################################
if __name__ == "__main__":
    app = QApplication(sys.argv)
    fen = Fenetre()
    fen.show()
    sys.exit(app.exec_())


Amusez-vous bien!

pyqt5_throbber.txt · Dernière modification: 2015/06/10 05:31 de tyrtamos

Outils de la page