Search code examples
pythonpyqtpyqt5qframe

Dynamic QFrame display


i'm trying to have PyQt5.QtWidgets.QFrame updating dynamically his color.

Here is the code ( watch run() method )

import sys 
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QFont
from time import sleep,time
from random import randint

class cadrillage(QWidget):  
    def __init__(self,grille_init,Largeur_grille, pause=0):
        """grille init vaudra 1 ou 0 : 0 pour une grille initialement toute blanche, 1 pour une grille randomisée"""

        super().__init__()
        self.Largeur_grille = Largeur_grille


    self.n_etape = 0
        self.cadri_init(grille_init)
        self.run(pause)


    def cadri_init(self,grille_init):
        """ définit la fenetre d'affichage et la grille initiale """
        t1 = time()
        self.setGeometry(890,150,1000,800)
        self.setWindowTitle("Grille à la {}_ème étape".format(self.n_etape))

        self.show()

        self.grid = QGridLayout()
        self.grid.setSpacing(0)    # pas d'espace entre les carrés
        self.setLayout(self.grid)

        if grille_init == 0:
            #définir grille_couleurs
            grille_couleurs=[0 for i in range(self.Largeur_grille) for j in range(self.Largeur_grille)]
            # 0 pour le blanc, 1 pour le noir

        if grille_init == 1:
            #définir grille_couleurs
            grille_couleurs=[randint(0,1) for i in range(self.Largeur_grille) for j in range(self.Largeur_grille)]


        self.positions = [(i,j) for i in range(self.Largeur_grille) for j in range(self.Largeur_grille)]

        self.grille_carre=[[None for i in range(self.Largeur_grille)] for j in range(self.Largeur_grille)]    

        for position,couleur in zip(self.positions,grille_couleurs):

            carre = QFrame(self)

            if couleur == 0:
                carre.setStyleSheet("QWidget { background-color: #ffffff}")
            if couleur == 1:
                carre.setStyleSheet("QWidget { background-color: #000000}")
            carre.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
            self.grid.addWidget(carre, *position)

            i,j = position
            self.grille_carre[i][j] = carre

        print(time()-t1)
        sleep(0.2)

    def run(self,pause):

        for k in range(3):

            a,b = randint(0,self.Largeur_grille-1),randint(0,self.Largeur_grille-1)
            self.grille_carre[a][b].setStyleSheet("QFrame{ background-color: #000000}")
        sleep(pause)

if __name__ =='__main__':
    app = QApplication(sys.argv)
    grille = cadrillage(0,10,0.5)
    sys.exit(app.exec_())

In run() method, i change the color of some QFrame Widgets (self.grille_carre is a list of list of QFrames), and i can see this changes works on the final display, but The whole display appears after the whole program runs and i would like to see each squares getting created.


Solution

  • Blocking tasks should not be used in the main thread since PyQt is executed in an event loop, this allows you to review other events such as the keyboard, the mouse, etc., if in changes you use blocking tasks like the sleep you make the GUI does not respond . What you want to do is a periodic task for this you must use a QTimer (the time set is in milliseconds).

    import sys 
    from PyQt5.QtWidgets import QWidget, QApplication, QGridLayout, QFrame, QSizePolicy
    from PyQt5.QtGui import QFont
    from PyQt5.QtCore import QTimer, QEventLoop
    from time import sleep,time
    from random import randint
    
    class cadrillage(QWidget):  
        def __init__(self,grille_init,Largeur_grille, pause=0):
            """grille init vaudra 1 ou 0 : 0 pour une grille initialement toute blanche, 1 pour une grille randomisée"""
    
            super().__init__()
            self.Largeur_grille = Largeur_grille
    
            self.n_etape = 0
            self.cadri_init(grille_init)
            self.timer = QTimer(self)
            self.timer.timeout.connect(self.run)
            self.counter = 0
            self.timer.start(pause)
    
        def cadri_init(self,grille_init):
            """ définit la fenetre d'affichage et la grille initiale """
            self.setGeometry(890,150,1000,800)
            self.setWindowTitle("Grille à la {}_ème étape".format(self.n_etape))
    
            self.show()
    
            self.grid = QGridLayout()
            self.grid.setSpacing(0)    # pas d'espace entre les carrés
            self.setLayout(self.grid)
    
            grille_couleurs=[0 if grille_init == 0 else randint(0,1) for i in range(self.Largeur_grille) for j in range(self.Largeur_grille)]
    
            self.positions = [(i,j) for i in range(self.Largeur_grille) for j in range(self.Largeur_grille)]
    
            self.grille_carre=[[None for i in range(self.Largeur_grille)] for j in range(self.Largeur_grille)]    
    
            for position,couleur in zip(self.positions,grille_couleurs):
                carre = QFrame(self)
                carre.setStyleSheet("QWidget {{ background-color: {}}}".format("#ffffff" if couleur == 0 else "#000000"))
                carre.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding)
                self.grid.addWidget(carre, *position)
                i,j = position
                self.grille_carre[i][j] = carre
    
        def run(self):
            self.counter += 1
            a,b = randint(0,self.Largeur_grille-1),randint(0,self.Largeur_grille-1)
            self.grille_carre[a][b].setStyleSheet("QFrame{ background-color: #000000}")
            if self.counter > 3:
                self.timer.stop()
    
    if __name__ =='__main__':
        app = QApplication(sys.argv)
        grille = cadrillage(0,10,500)
        sys.exit(app.exec_())