Partage
  • Partager sur Facebook
  • Partager sur Twitter

Tkinter - probleme de boucle avec command

Sujet résolu
    1 juillet 2013 à 11:28:21

    Bonjour a tous,

    J'ai actuellement (encore) un peu de mal a me mettre a tkinter pour faire des interfaces graphiques.

    Actuellement, j'ai un petit code (voir ci-dessous), et j'ai du mal a comprendre la gestion des boucles avec tkinter...

    Donc voici le code dans son etat actuel:

    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
    
    
    # imports
    from premier import *
    from tkinter import *
    from tkinter import ttk
    from tkinter import messagebox
    from time import *
    
    
    # init var
    varstop=False
    
    
    # functions
    def launch():
    	"""search first numbers between 2 and var entry"""
    	listbox.delete(0, "end") # clear listbox
    	itime=time() # init time
    	n=entry.get() # get limit
    	
    	if n.isnumeric()==False or int(n)<2:
    		# check inputs
    		messagebox.showwarning("", "insert an integer (>=2) on the entry")	
    	else:
    		stop_button["state"]="normal"
    		launch_button["state"]="disabled"
    		
    		n=int(n)
    		i=2
    		nb=0 # count number of first numbers
    		varprogress=0 # var of the progress bar
    		global varstop
    		
    		while i<=n:
    			if varstop==True:
    				# if click on stop button, stop
    				varstop=False
    				return
    			else:
    				if premier(i)==True:
    					nb+=1
    					listbox.insert("end", " "+str(i))
    				varprogress=int((i*100/n)-varprogress+1)
    				progress.step(varprogress)
    				i+=1
    		
    		listbox.insert("end", " number of first number between 1 and "+str(n)+": "+str(nb))
    		temp=time()-itime
    		listbox.insert("end", " process time: "+str(int(temp))+"s")
    		stop_button["state"]="disabled"
    		launch_button["state"]="normal"
    		
    def stop():
    	global varstop
    	varstop=True
    
    	
    # define gui
    gui=Tk()
    gui.title("Premier.py")
    
    frame1=Frame(gui)
    frame2=Frame(gui)
    frame3=Frame(gui)
    
    label=Label(frame1, text="Limit (>=2) :")
    listbox=Listbox(frame2, height=25, width=50, selectmode="extended")
    progress=ttk.Progressbar(frame3, length=350)
    entry=Entry(frame1, width=25)
    launch_button=Button(frame3, width=10, text="launch", command=launch)
    stop_button=Button(frame3, width=10, text="stop", state="disabled")
    
    # create gui
    frame1.pack(expand=True)
    frame2.pack(expand=True)
    frame3.pack(expand=True)
    
    label.pack(side="left")
    entry.pack(side="right")
    listbox.pack(side="left")
    progress.pack(pady=5)
    launch_button.pack(side="left", padx=50, pady=5)
    stop_button.pack(side="right", padx=50, pady=5)
    
    gui.mainloop()
    
    

    Et voici une petite vidéo de ce qui se passe quand je lance le calcul (des nombres premieres entre 1 et la limite définie dans l'entry du haut de la gui)

    Vous remarquerez que quand la boucle de la fonction launch() en lancée, la gui ne repond plus et est totalement inactive...

    J'ai beau avoir mis des instructions pour activer les bouton stop au debut de la fonction, il reste inactivé, et le bouton launch reste activé...

    De meme, les valeurs de nombres premiers trouvé s'affichent toute d'un coup et pas une par une comme je l'aurais voulu.

    En meme temps, il devient impossible de gerer la barre de progression malgré les instructions du scripts...

    Je me demande donc d'ou viens le probleme, comment le resoudre ?

    Y a t'il des spécificités a prendre en compte pour les fonctions lancées avec un bouton via le parametre command ?

    Merci d'avance,

    -
    Edité par N3mesis94 1 juillet 2013 à 11:30:32

    • Partager sur Facebook
    • Partager sur Twitter
    "Ce qui ne tue pas rend plus fort", Nietzsche
      4 juillet 2013 à 12:12:10

      Alors, ça avance un peu, apparement, ce probleme viens du fait que Tkinter ne fait pas du double threading...

      voila les solutions proposées sur le post que j'ai trouvé:

      1. move your time-intensive code to another thread, or
      2. move your time-intensive code to another process, or
      3. refactor your time-intensive code into small pieces that can be done in small iterations via the event loop, or
      4. as an inefficient and possibly dangerous work-around, call update_idletasks after every iteration of your loop, which will at least allow "idle" events to be processed (such as redrawing the GUI

      J'ai essayé la 4eme, mais ça met juste a jour l'apparence de l'interface graphique donc impossible de faire un bouton stop par exemple...

      Et les autres alternatives je ne comprend pas vraiment... Quelqu'un a une idée de ce que ça veut dire ?

      • Partager sur Facebook
      • Partager sur Twitter
      "Ce qui ne tue pas rend plus fort", Nietzsche
      Anonyme
        4 juillet 2013 à 22:10:03

        Je pense que le code est mal parti, car ce que je peux voir c'est que tu affiches au fur et à mesure de la découverte d'un nombre premier ce nombre dans le widget, ce qui demande une quantité de mémoire énorme, niveau rafraîchissement + calculs.

        Ce que je conseillerais c'est de créer un générateur des nombres premiers trouvés

        Puis afficher tous les nombres premiers en une fois

        values = '\n'.join(map(str, gen))
        listbox.insert("end", values)

        En fait l'essentiel n'est pas dans Tkinter mais dans ta façon de déterminer tes nombres premiers, qui doit être fait totalement à part...

        • Partager sur Facebook
        • Partager sur Twitter
          5 juillet 2013 à 8:39:22

          Certe, je pourrais stocker les nombres premiers dans un tuple pour les afficher a la fin... Mais la on en est quand meme a un probleme d'optimisation alors meme que le script ne marche pas vraiment...

          Mon vrai probleme viens de la gestion des deux boutons et du fait que l'interface bloque quand je lance la boucle...

          D'apres ce que j'en ai compris, le probleme pourrait etre resolu avec du threading. Ce n'est encore une fois pas une chose que j'ai l'habitude d'utiliser, donc je vais tout de meme vous demander conseil avant de me lancer...

          En fait, j'avais penser a faire, pour le bouton de lancement, eux fonctions...

          Le boutton appelerais la premiere fonction :

          def thread_launch():
              var_thread_launch=threading.Thread(target=launch)
              var_thread_launch.start()

          qui au final lance la fonction précédement codée (qui sera par la suite retouchée) dans un nouveau thread...

          Est ce que cela suffit pour que l'interaction avec les deux boutons soit gérée ou bien est ce qu'il faut faire quelque chose d'autre ?

          • Partager sur Facebook
          • Partager sur Twitter
          "Ce qui ne tue pas rend plus fort", Nietzsche
          Anonyme
            5 juillet 2013 à 9:09:59

            Bon parce-qu'il faut que je démontre, voici un code vite fait, faisant la même chose...

            Seule différence, j'utilise pas une listbox, mais un widget text, plus adapté pour écrire du texte.

            Comme tu verras il y a une très bonne réaction, pas besoin de thread, et c'est pas parce-que c'est écris en anglais que je changerais d'avis. :)

            from tkinter import *
            
            def primes_sieve(limit):
                a = [True] * limit
                a[0] = a[1] = False
            
                for (i, isprime) in enumerate(a):
                    if isprime:
                        yield i
                        for n in range(i*i, limit, i):
                            a[n] = False
            
            def display():
                try:
                    val = int(entre.get())
                    bouton['state'] = DISABLED
                    values = '\n'.join(map(str, (i for i in primes_sieve(val))))
                    liste.insert("end", values)
                    vider['state'] = NORMAL
                except ValueError:
                    entre.delete(0, END)
                
            
            def vider():
                bouton['state'] = NORMAL
                entre.delete(0, END)
                liste.delete(1.0, END)
                vider['state'] = DISABLED
            
            root = Tk()
            
            liste = Text(root)
            liste.pack()
            
            entre = Entry(root)
            entre.pack()
            
            bouton = Button(root, text="launch", command=display)
            bouton.pack()
            
            vider = Button(root, text="clear", command=vider, state=DISABLED)
            vider.pack()
            
            root.mainloop()


            -
            Edité par Anonyme 5 juillet 2013 à 9:15:14

            • Partager sur Facebook
            • Partager sur Twitter
              5 juillet 2013 à 11:23:15

              C'est vrai que ça va bien plus vite !!!

              Mais sinon, encore une fois, si tu veux mettre un bouton pour stopper le processus au milieu comment ferait tu ?

              Car il faudrait dans ce cas que la mainloop ne soit pas bloquée pendant que la boucle de recherche est en cours non ?

              • Partager sur Facebook
              • Partager sur Twitter
              "Ce qui ne tue pas rend plus fort", Nietzsche
                5 juillet 2013 à 16:23:59

                J'ai pas suivi du tout le sujet, mais rapidement :

                N3mesis94 a écrit:

                Mais sinon, encore une fois, si tu veux mettre un bouton pour stopper le processus au milieu comment ferait tu ?

                Car il faudrait dans ce cas que la mainloop ne soit pas bloquée pendant que la boucle de recherche est en cours non ?

                En effet : il faut alors que ton processus s'exécute dans un thread séparé. Régulièrement tu vérifies si une variable stop (ou autre) n'est pas à True. Et dans le thread principal, tu colles un bouton dont l'action est de setter cette variable stop à True.

                -
                Edité par nohar 5 juillet 2013 à 16:24:40

                • Partager sur Facebook
                • Partager sur Twitter
                Zeste de Savoir, le site qui en a dans le citron !
                  5 juillet 2013 à 17:24:31

                  nohar a écrit:

                  J'ai pas suivi du tout le sujet, mais rapidement :

                  N3mesis94 a écrit:

                  Mais sinon, encore une fois, si tu veux mettre un bouton pour stopper le processus au milieu comment ferait tu ?

                  Car il faudrait dans ce cas que la mainloop ne soit pas bloquée pendant que la boucle de recherche est en cours non ?

                  En effet : il faut alors que ton processus s'exécute dans un thread séparé. Régulièrement tu vérifies si une variable stop (ou autre) n'est pas à True. Et dans le thread principal, tu colles un bouton dont l'action est de setter cette variable stop à True.

                  -
                  Edité par nohar il y a environ 1 heure


                  Dans ce cas, ma question serait comment ferait tu pour lancer une fonction dans un nouveau thread via un bouton Tkinter ?
                  • Partager sur Facebook
                  • Partager sur Twitter
                  "Ce qui ne tue pas rend plus fort", Nietzsche
                    8 juillet 2013 à 13:19:56

                    Bon alors j'ai resolu le probleme et tout marche visiblement...

                    J'ai utilisé le threading, mais je ne sais pas si je l'ai fait d'une maniere tres "elegante"...

                    Sinon, je vais probablement m'attaquer maintenant a l'optimisation du programme, mais en tout cas la GUI marche nickel, il faudrait juste que je pense a rajouter une scrollbar pour la liste...


                    Edit:

                    J'ai ajouté une scrollbar et optimisé le processus de recherche et d'affichage grace aux conseils de fred (4x plus rapide tout de meme...):

                    #!/usr/bin/python3
                    # -*- coding: utf-8 -*-
                    
                    
                    # imports
                    from tkinter import *
                    from tkinter import ttk
                    from tkinter import messagebox
                    from time import *
                    from threading import *
                    
                    
                    # init var
                    varstop=False
                    
                    
                    # functions
                    def thread_launch():
                    	thread=Thread(name="thread", target=launch)
                    	thread.start()
                    
                    def launch():
                    	"""search prime numbers between 2 and var entry"""
                    	textlist.delete("1.0", "end") # clear textlist
                    	n=entry.get() # get limit
                    	
                    	if n.isnumeric()==False or int(n)<2:
                    		# check inputs
                    		messagebox.showwarning("", "insert an integer (>=2) on the entry")	
                    	else:
                    		# change button state
                    		stop_button["state"]="normal"
                    		launch_button["state"]="disabled"
                    
                    		n=int(n)
                    		itime=time() # init time
                    		i=3
                    		nb=1 # count number of first numbers
                    		prime_list=[2] # list of prime numbers found between 2 and the limit
                    		global varstop
                    		
                    		# search prime numbers loop
                    		while i<=n and varstop==False:
                    			j=0
                    			prime=True
                    			while prime==True and j<len(prime_list):
                    				# if i is not divisable by previous prime numbers, i is a prime number 
                    				if i%prime_list[j]==0:
                    					prime=False
                    				j+=1
                    			if prime==True:
                    				nb+=1
                    				prime_list.append(i)
                    			varprogress=(i*100)/n
                    			progress["value"]=int(varprogress)
                    			i+=2 # par numbers are not prime except 2
                    		
                    		# form and insert the text in textlist
                    		values="\n"
                    		if varstop==True:
                    			values=values+" process interrupted at "+str(varprogress)+"%"+"\n"
                    			varstop=False
                    		
                    		values=values+" number of prime numbers between 1 and "+str(i-1)+": "+str(nb)+"\n"
                    		temp=time()-itime
                    		values=values+" process time: "+str(int(temp))+"s"+"\n"
                    		
                    		for elt in prime_list:
                    			values=values+" "+str(elt)+"\n"
                    		textlist.insert("end",values)
                    		
                    		stop_button["state"]="disabled"
                    		launch_button["state"]="normal"
                    		progress["value"]=0
                    		# textlist.see("1.0")
                    		
                    def stop():
                    	global varstop
                    	varstop=True
                    
                    	
                    # create gui
                    gui=Tk()
                    gui.title("Prime")
                    
                    frame1=Frame(gui)
                    frame2=Frame(gui)
                    frame3=Frame(gui)
                    
                    scrollbar=Scrollbar(frame2)
                    label=Label(frame1, text="Limit (>=2) :")
                    textlist=Text(frame2, height=25, width=55)
                    progress=ttk.Progressbar(frame3, length=350)
                    entry=Entry(frame1, width=25)
                    launch_button=Button(frame3, width=10, text="launch", command=thread_launch)
                    stop_button=Button(frame3, width=10, text="stop", state="disabled", command=stop)
                    
                    # link scrollbar with textlist yview
                    scrollbar.config(command=textlist.yview)
                    textlist.config(yscrollcommand=scrollbar.set)
                    
                    # print gui with pack
                    frame1.pack(expand=True)
                    frame2.pack(expand=True)
                    frame3.pack(expand=True)
                    
                    label.pack(side="left")
                    entry.pack(side="right")
                    textlist.pack(side="left", fill="y")
                    scrollbar.pack(side="left", fill="y")
                    progress.pack(pady=5)
                    launch_button.pack(side="left", padx=50, pady=5)
                    stop_button.pack(side="right", padx=50, pady=5)
                    
                    gui.mainloop()
                    
                    



                    -
                    Edité par N3mesis94 9 juillet 2013 à 10:54:43

                    • Partager sur Facebook
                    • Partager sur Twitter
                    "Ce qui ne tue pas rend plus fort", Nietzsche

                    Tkinter - probleme de boucle avec command

                    × 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