Partage
  • Partager sur Facebook
  • Partager sur Twitter

Boucle avec Tkinter

    16 octobre 2015 à 10:26:00

    Bonjour,

    Je commence à utiliser Tkinter mais j'ai vite remarqué que la fenêtre bloque lors d'une boucle. Je n'ai trouvé aucun tuto clair, quelqu'un en aurait-il un bien expliqué ?

    Merci d'avance.

    • Partager sur Facebook
    • Partager sur Twitter
      16 octobre 2015 à 11:11:30

      Pourrais-tu mettre ton code pour qu'on puisse avoir une idée du problème. Pour mettre du code sur le site, il faut utiliser les balises:

      • Partager sur Facebook
      • Partager sur Twitter
      Précepte: Le mieux est l'ennemi du bien
        16 octobre 2015 à 11:29:41

        Par exemple, avec ce code :

        from tkinter import *
        from tkinter.filedialog import *
        
        fenetre=Tk()
        label=Label(fenetre, text="Bonjour")
        label.pack()
        
        fch = askopenfilename(title="Ouvrir votre document",filetypes=[('log files','.LOG'),('txt files','.txt'),('all files','.*')])
        fichier=open(fch, 'r')
        
        a=0
        for i in range (0, 1000):
            a+=1
            print(a)
        
            
        fenetre.mainloop()


         

        Je demande d'ouvrir un fichier, et pendant la boucle la fenêtre avec afficher 'Bonjour' bug et je ne comprend pas pourquoi.

        -
        Edité par julienbrunet93600 16 octobre 2015 à 11:37:47

        • Partager sur Facebook
        • Partager sur Twitter
          16 octobre 2015 à 12:12:58

          Je ne rencontre pas de problème particulier. Lors de la demande de fichier, je n'ai effectivement pas accès à la première fenêtre mais c'est parce que celle qui demande le fichier a pris le dessus. Une fois le fichier choisi, la boucle s'exécute normalement.

          Qu'entends-tu par bug?

          • Partager sur Facebook
          • Partager sur Twitter
          Précepte: Le mieux est l'ennemi du bien
            16 octobre 2015 à 13:07:40

            Avec une boucle infini :

            from tkinter import *
            from tkinter.filedialog import *
            
            fenetre=Tk()
            label=Label(fenetre, text="Bonjour")
            label.pack()
            
            fch = askopenfilename(title="Ouvrir votre document",filetypes=[('log files','.LOG'),('txt files','.txt'),('all files','.*')])
            fichier=open(fch, 'r')
            
            a=0
            while (a==0):
                print ("ok")
            
                
            fenetre.mainloop()

            Pendant la boucle infinie, si tu essaye de sélectionner la fenêtre tu ne pourra pas la bouger et elle va freez. Il va ensuite y avoir un message d'erreur avec écrit :"pythonw.exe ne répond pas".

            • Partager sur Facebook
            • Partager sur Twitter
              16 octobre 2015 à 16:12:08

              Tu ne sembles pas réaliser que la fenêtre du GUI ne peut pas être mise à jour si ton programme ne lui en donne pas la possibilité. Ca n'arrive pas par magie. ;) En fait, l'appel à fenetre.mainloop() permet à tkinter de rentrer dans sa boucle principale. C'est une boucle infinie où en gros il va lire s'il y a des événements dans une queue, il va réagir à ces événements et il va éventuellement redessiner la fenêtre (ou une partie de celle-ci) si nécessaire. Par événements, j'entends: la souris a bougé, on a enfoncé une touche du clavier, on a enfoncé un bouton de la souris, ...

              Si tkinter ne peut entrer dans cette boucle principale, alors la fenêtre reçoit des événements (fournis par l'OS) mais le programme ne les traite pas. Donc ça freeze comme tu dis, et puis l'OS découvre que ce n'est pas normal et interrompt ton programme.

              Afin de pouvoir exécuter simultanément des impressions dans la console et laisser tkinter faire son boulot, tu as quelques options. La plus simple est de demander à tkinter d'appeler une fonction aussi souvent que possible avec after_idle.

              #!/usr/bin/env python3
              # -*- coding: utf-8 -*-
              
              from tkinter import *
              from tkinter.filedialog import *
               
              fenetre=Tk()
              label=Label(fenetre, text="Bonjour")
              label.pack()
               
              fch = askopenfilename(title="Ouvrir votre document",filetypes=[('log files','.LOG'),('txt files','.txt'),('all files','.*')])
              fichier=open(fch, 'r')
              
              def print_in_console():
                  print ("ok")
                  fenetre.after_idle(print_in_console)
              
              fenetre.after_idle(print_in_console)
              fenetre.mainloop()
              

              Sinon on peut aussi appelé after qui prend en premier argument un temps en ms après lequel tkinter appellera la fonction passée en 2ème argument.

              Note que dans la fonction print_in_console, on re-planifie un appel à soi-même. On ne fait pas une boucle infinie, sinon on obtient de nouveau le même problème, ç-à-d tkinter n'a plus la main.

              Cette manière de programmer s'appelle programmation événementielle. Ca demande de penser les choses différemment, car tu n'as plus la possibilité d'avoir le code qui s'exécute de manière linéaire. Tu prépares tout ce dont tu as besoin à l'avance, et tu entres dans la boucle principale. Ensuite tu demandes à tkinter de te donner la main pour certains événements, que ce soit en appelant une fonction toutes les X secondes comme on a fait, où pour réagir à l'utilisateur qui a cliqué sur un bouton, appuyé sur une touche ou autre chose... Mais on ne fait jamais sa propre petite boucle infinie sinon on casse tout. :)

              -
              Edité par Dan737 27 mars 2017 à 15:31:21

              • Partager sur Facebook
              • Partager sur Twitter
                19 octobre 2015 à 11:41:50

                Je commence à comprendre le fonctionnement, merci pour ta réponce précise.

                Par contre comment utiliser cette méthode avec une boucle finie ?  Par exemple en utilisant un for ou un while ?

                • Partager sur Facebook
                • Partager sur Twitter
                  19 octobre 2015 à 14:17:28

                  C'est ce que j'explique en fin de message. On ne peut pas faire une boucle infinie avec un for ou while. Si on veut exécuter du code en boucle, on met ce code dans un fonction, et on appelle cette fonction avec after_idle. A la fin de notre fonction, on s'appelle soi-même avec after en donnant un petit temps. Ca permet à tkinter de gérer les évènements.

                  from tkinter import *
                  
                  def increment_label_forever():
                      number = int(label['text'])
                      number += 1
                      label['text'] = number
                      # On aurait pu faire beaucoup mieux avec un IntVar
                      # mais c'est pas le sujet ici... 
                      root.after(10, increment_label_forever)
                  
                  root = Tk()
                  
                  label = Label(root, text="1")
                  label.pack()
                  
                  root.after_idle(increment_label_forever)
                  
                  root.mainloop()
                  

                  -
                  Edité par Dan737 27 mars 2017 à 15:33:25

                  • Partager sur Facebook
                  • Partager sur Twitter
                    19 octobre 2015 à 15:36:47

                    D'accord, mais est-ce possible d'utiliser cette méthode sur des boucles qui ne sont pas infinie ?
                    • Partager sur Facebook
                    • Partager sur Twitter
                      19 octobre 2015 à 17:32:43

                      Oui, bien entendu. Il suffit dans la fonction d'avoir une condition et de ne plus appeler after.

                      from tkinter import *
                       
                      def increment_label_forever():
                          number = int(label['text'])
                          number += 1
                          label['text'] = number
                          if number == 100:
                              # On s'arrête ici puisqu'on n'appelle plus after
                      
                              return
                          root.after(10, increment_label_forever)
                       
                      root = Tk()
                       
                      label = Label(root, text="1")
                      label.pack()
                       
                      root.after_idle(increment_label_forever)
                       
                      root.mainloop()
                      

                      -
                      Edité par Dan737 27 mars 2017 à 15:32:56

                      • Partager sur Facebook
                      • Partager sur Twitter
                      Anonyme
                        19 octobre 2015 à 17:41:24

                        Une boucle arrête d'être infinie lorsqu'elle rencontre une condition d'arrêt que toi tu imposes.

                        Je n'ai trouvé aucun tuto clair

                        Pourtant le tutoriel de Swinnen bien connu, exprime bien l'intérêt des temporisations sur des animations à l'aide de la méthode after...

                        • Partager sur Facebook
                        • Partager sur Twitter
                          20 octobre 2015 à 9:59:58

                          Ah oui effectivement j'aurai dû penser plus tôt au "return";)

                          Comment avec cette méthode peut-on gérer des listes ? Par exemple :

                          a=[]
                          b=[]
                          e=[]
                          
                          fch = askopenfilename(title="Ouvrir votre document",filetypes=[('log files','.LOG'),('txt files','.txt'),('all files','.*')])
                          
                          fichier=open(fch, 'r')
                          
                                
                          print("Extraction des données...")
                            
                            for ligne in fichier:
                              a=ligne.split()
                              e.append(ligne)
                              b.append(a[3])
                                


                          Je ne vois pas d'autre solutions que le "for" sauf que comme tu l'as dit, c'est interdit ici. Sachant que le fichier à ouvrir peut contenir en moyenne 100 000 lignes.

                          • Partager sur Facebook
                          • Partager sur Twitter
                            20 octobre 2015 à 20:47:25

                            Je n'ai jamais dit qu'on ne pouvait pas utiliser de boucles for ou while. Voilà ce que j'ai dit:

                            On ne peut pas faire une boucle infinie avec un for ou while

                            Note que je parle d'infinie. Dans ton cas, ce n'est pas infini. Maintenant si le temps d'exécution de cette boucle est si longue que le GUI se retrouve bloqué, alors en effet il faut rendre la main au GUI de temps à autre.

                            • Partager sur Facebook
                            • Partager sur Twitter
                              21 octobre 2015 à 9:48:04

                              Je voulais un tout petit peu expliciter le cas où la boucle prend vraiment longtemps. Avant de voir comment rendre la main au GUI, voyons d'abord le cas où on bloque tout. Peu importe le processus qui prend du temps, moi j'ai juste mis un time.sleep(0.005) pour attendre 5ms. On peut imaginer que c'est le temps pour faire un morceau de notre gros traitement.

                              from tkinter import *
                              import time
                              
                              def long_process():
                                  result['text'] = "Starting to process..."
                                  for i in range(500):
                                      time.sleep(0.005)
                                  result['text'] = "Complete !"
                              
                              
                              fenetre = Tk()
                              Label(fenetre, text="Long processing demo").pack()
                              Button(fenetre, text="Process...", command=long_process).pack()
                              
                              result = Label(fenetre, text="")
                              result.pack()
                               
                              fenetre.mainloop() 
                              

                              Remarque que le message Starting to process... ne s'affiche même pas ! La raison est que le programme ne rend jamais la main à tkinter, donc celui-ci n'a pas l'occasion de redessiner le label et on ne voit pas le nouveau texte. La fenêtre freeze puisque aucun événement n'est traité.

                              Afin que le GUI continue à se comporter correctement, il faut qu'on puisse avoir un peu de temps CPU consacré à la boucle principale de tkinter et un peu de temps CPU à notre fonction. Une manière de procéder est comme je l'ai dit d'appeler after pour appeler notre fonction. Le soucis est qu'on ne peut pas avoir de boucle for. Une manière naïve de procéder serait ceci

                              from tkinter import *
                              
                              
                              def long_process():
                                  global i
                                  time.sleep(0.005)
                                  i += 1
                                  if i < 500:
                                      fenetre.after_idle(fenetre.after, 0, long_process)
                                      return
                                  result['text'] = "Complete !"
                              
                              def process():
                                  global i  # Gloabl ... beûrk
                                  i = 0
                                  result['text'] = "Starting to process..."
                                  long_process()
                              
                              
                              fenetre = Tk()
                              Label(fenetre, text="Long processing demo").pack()
                              
                              Button(fenetre, text="Process...", command=process).pack()
                              
                              result = Label(fenetre, text="")
                              result.pack()
                               
                              fenetre.mainloop() 
                              

                              Le problème est l'apparition de la variable globale i qui nous permet de faire notre boucle. Une petite note sur fenetre.after_idle(fenetre.after, 0, long_process). Cet idiome que j'ai trouvé dans la doc de tcl/tk permet d'enregistrer notre fonction comme un callback dès que possible, mais après avoir traité tous les autres événements.

                              Ce serait tout même plus pratique et lisible si on pouvait suspendre l'exécution de notre boucle et la reprendre la où on l'avait laissée un peu plus tard. Python a un tel objet : un générateur. Le générateur avance jusqu'au prochain yield et retourne la main au code. La prochaine fois qu'on appelle next(notre_generateur), on reprend là où on en était. La seule chose est qu'on va faire next jusqu'à ce que le générateur n'ait plus rien à faire. Il va alors lancer une exception ´StopIteration`. On peut très bien la récupérer et ainsi comprendre que le traitement est terminé. Voici une implémentation

                              from tkinter import *
                              import time
                              
                              
                              def long_process():
                                  result['text'] = "Starting to process..."
                                  for i in range(500):
                                      time.sleep(0.005)
                                      yield # on rend la main, et on reprendra ici au prochain appel
                              
                                  result['text'] = "Complete !"
                                  # Fin du générateur. Python va lancer une exception StopIteration.
                              
                              def process():
                                  generator = long_process()
                              
                                  def process_runner():
                                      try:
                                          next(generator)
                                      except StopIteration:
                                          return
                                      fenetre.after_idle(fenetre.after, 0, process_runner)
                              
                                  process_runner()
                              
                              fenetre = Tk()
                              Label(fenetre, text="Long processing demo").pack()
                              Button(fenetre, text="Process...", command=process).pack()
                              
                              result = Label(fenetre, text="")
                              result.pack()
                               
                              fenetre.mainloop()
                              

                              Cette idée est utilisée dans le module asyncio. On appelle ça une coroutine. Ma manière de faire est aussi très (trop ?) simple car je ne traite pas les cas où notre long_process lance une exception. Mais tu vois le principe de base.

                              Une autre possibilité est de placer le traitement de tes données dans un thread. Avec tkinter, tu ne vas que vérifier de temps à autre si le thread a fini, sinon (avec un after de nouveau) tu vas re-planifier cette vérification. Lorsque la vérification indique que le thread a fini son traitement, tu peux accéder aux données. Attention: l'entrée en jeu des threads amène son lot de problèmes, comme la synchronisation des données. Pour mon exemple, j'ai utilisé une Queue afin de partager des infos entre la nouvelle thread et le programme principal (le main thread). Comme ce n'est qu'un exemple, la fonction ne place finalement qu'une chaîne de caractère dans notre queue. Mais tu pourrais imaginer qu'à chaque tour de boucle, une ligne soit traitée et placée dans la queue. Le main thread peut alors déjà faire autre chose (l'afficher ?) en attendant.

                              from tkinter import *
                              import time
                              import threading
                              from queue import Queue
                              
                              
                              def long_process(queue):
                                  for i in range(500):
                                      time.sleep(0.005)
                              
                                  queue.put("Here is the result!")
                                  
                              
                              def process():
                                  my_queue = Queue()
                                  my_thread = threading.Thread(target=long_process, args=(my_queue,))
                                  result['text'] = "Starting to process..."
                                  my_thread.start()
                              
                                  def check_for_thread_completion():
                                      if my_thread.is_alive():
                                          fenetre.after(200, check_for_thread_completion)
                                          return
                              
                                      # Our thread is dead, so data is available !
                                      data = my_queue.get()
                                      result['text'] = data
                                  
                                  fenetre.after(200, check_for_thread_completion)
                              
                              fenetre = Tk()
                              Label(fenetre, text="Long processing demo").pack()
                              Button(fenetre, text="Process...", command=process).pack()
                              
                              result = Label(fenetre, text="")
                              result.pack()
                               
                              fenetre.mainloop() 
                              

                              Ces méthodes - coroutines ou thread - ne sont pas facile à mettre en place. Elles ne sont pas nécessaire si tout ce que tu fais prend un temps raisonnable. Donc ne considère seulement ces solutions que lorsque l'opération demandée prend un temps considérable et va empêcher le GUI de répondre.

                              -
                              Edité par Dan737 27 janvier 2017 à 14:46:56

                              • Partager sur Facebook
                              • Partager sur Twitter
                                27 janvier 2017 à 14:41:11

                                On peut faire une boucle avec while :

                                while 0==0:
                                    #Ici ton Code


                                ...

                                -
                                Edité par YASSINROUIS 2 mars 2017 à 18:19:23

                                • Partager sur Facebook
                                • Partager sur Twitter
                                Anonyme
                                  27 janvier 2017 à 17:50:39

                                  YASSINROUIS a écrit:

                                  On peut faire une boucle avec while :

                                  while 0==0:
                                      #Ici ton Code



                                  L'intervention ci-dessus est très moche, merci de ne pas l'utiliser dans vos codes...

                                  • Partager sur Facebook
                                  • Partager sur Twitter
                                    2 mars 2017 à 18:18:53

                                    Peut être mettre "A JOUR" avec .update()
                                    • Partager sur Facebook
                                    • Partager sur Twitter
                                      27 avril 2017 à 19:12:15 - Message modéré pour le motif suivant : Message complètement hors sujet


                                        11 avril 2019 à 21:49:27

                                        j ai un probleme avec mon programme quand je clic sur valider l'ecran freeze voila mon script:

                                        from tkinter import*

                                        from random import*

                                        from math import*

                                        import os

                                        nb_casino=0

                                        somme_total=5000

                                        somme_mis=0

                                        nb_choisi=0

                                        def casino ():

                                        button.pack_forget()

                                        label_title5.pack_forget()

                                        button3.pack_forget()

                                        button1.pack_forget()

                                        label_title1=Label(frame,text="nombre a parier entre 0 et 49 compris",font=("Courrier",40),bg='#41B77F',fg="white")

                                        label_title1.pack()

                                        entree.pack()

                                        label_title=Label(frame,text="somme à miser vous avez {}$".format(somme_total),font=("Courrier",40),bg='#41B77F',fg="white")

                                        label_title.pack()

                                        entree1.pack()

                                        button2.pack(pady=10,fill=X)

                                        button1.pack(pady=10,fill=X)

                                        def bro():

                                        button2.pack_forget()

                                        conti=True

                                        print("ca marche")

                                        while conti:

                                        button3.pack_forget()

                                        nb_choisi=entree.get()

                                        somme_mis=entree1.get()

                                        somme_total=5000

                                        continuer=True

                                        while continuer:

                                        print("ca marche1")

                                        situas.pack_forget()

                                        situas1.pack_forget()

                                        situas2.pack_forget()

                                        situas3.pack_forget()

                                        nb_choisi=int(nb_choisi)

                                        somme_mis=int(somme_mis)

                                        somme_total=somme_total-somme_mis

                                        if nb_choisi >= 0 and nb_choisi <= 49:

                                        nb_casino=randrange(50)

                                        if nb_casino==nb_choisi:

                                        somme_mis=somme_mis*3

                                        somme_total=somme_mis+somme_total

                                        situas1.pack()

                                        elif nb_casino%2==nb_choisi%2:

                                        somme_mis=somme_mis*1.5

                                        somme_total=somme_mis+somme_total

                                        situas.pack()

                                        else:

                                        somme_mis=0

                                        situas2.pack()

                                        else:

                                        somme_mis=0

                                        ceil(somme_mis)

                                        print("vous avez perdu")

                                        print(somme_mis)

                                        print(nb_casino)

                                        if somme_total <= 0:

                                        print("Vous êtes ruiné ! C'est la fin de la partie.")

                                        entree.pack_forget()

                                        entree1.pack_forget

                                        button2.pack_forget()

                                        label_title1.pack_forget()

                                        label_title.pack_forget()

                                        label_title3=Label(frame,text="Vous avez perdu dommage!",font=("Courrier",40),bg='#41B77F',fg="white")

                                        label_title3.pack()

                                        continuer = False

                                        else:

                                        print("Vous avez à présent {0}$".format(somme_total))

                                        quitter = input("Souhaitez-vous quitter le casino (o/n) ? ")

                                        liste.pack()

                                        liste.geometry("300x50")

                                        liste.insert(END, "non je veux pas quitter le casino",command=fe.quit)

                                        liste.insert(END, "oui je veux quitter le casino",command=fe.quit)

                                        conti=False

                                        situas3.pack()

                                        if quitter == "o" or quitter == "O":

                                        print("Vous quittez le casino avec vos gains.")

                                        continuer = False

                                        os.system("pause")

                                        else:

                                        button3.pack()

                                        button2.pack()

                                        conti=False

                                        def continuee():

                                        conti=True

                                        button3.pack_forget()

                                        def print_in_console():

                                        fe.after_idle(print_in_console)

                                        fe=Tk()

                                        fe.title("roulette casino")

                                        fe.geometry("1900x1050")

                                        fe.config(background='#41B77F')

                                        fe.minsize(480,360)

                                        frame=Frame(fe,bg='#41B77F')

                                        label_title5=Label(frame,font=("Courrier",30),bg='#41B77F',fg="white",text=("Vous vous installez à la table de roulette avec {0}$.".format(somme_total)))

                                        label_title5.pack()

                                        quitter = Button(fe, text='Quitter',command=fe.quit)

                                        quitter.pack

                                        entree=Entry(frame)

                                        entree1=Entry(frame)

                                        button=Button(frame,text="Démarrer jeu de la roulette",font=("Courrier,200"),bg="white",fg='#41B77F',command=casino)

                                        button.pack(pady=10,fill=X)

                                        button1=Button(frame,text="Quitter",font=("Courrier,200"),bg="white",fg='#41B77F',command=fe.quit)

                                        button1.pack(pady=10,fill=X)

                                        button3=Button(frame,text="valider",font=("Courrier,200"),bg="white",fg='#41B77F',command=continuee)

                                        liste = Listbox(frame,bg='#41B77F')

                                        situas=Label(frame,text="Vous avez 50% en plus que ce que vous avez misé",font=("Courrier",40),bg='#41B77F',fg="white")

                                        situas1=Label(frame,text="Vous avez gagné trois fois ce que vous avez misé",font=("Courrier",40),bg='#41B77F',fg="white")

                                        situas2=Label(frame,text="Vous n'avez rien gagné dommage ",font=("Courrier",40),bg='#41B77F',fg="white")

                                        situas3=Label(frame,text="Vous les vous rester dans le casino (o/n)?",font=("Courrier",40),bg='#41B77F',fg="white")

                                        button2=Button(frame,text="Valider",font=("Courrier,200"),bg="white",fg='#41B77F',command=bro)

                                        frame.pack(expand=YES)

                                        fe.mainloop()

                                        fe.destroy()

                                        -
                                        Edité par personne111 11 avril 2019 à 21:50:47

                                        • Partager sur Facebook
                                        • Partager sur Twitter

                                        Boucle avec Tkinter

                                        × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                                        × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                                        • Editeur
                                        • Markdown