web-dev-qa-db-fra.com

Tkinter comprend mainloop

Jusqu'à présent, je terminais mes programmes Tkiter avec: tk.mainloop(), sinon rien ne se présenterait! Voir exemple:

from Tkinter import *
import random
import time

tk = Tk()
tk.title = "Game"
tk.resizable(0,0)
tk.wm_attributes("-topmost", 1)

canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0)
canvas.pack()

class Ball:
    def __init__(self, canvas, color):
        self.canvas = canvas
        self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
        self.canvas.move(self.id, 245, 100)
    def draw(self):
        pass

ball = Ball(canvas, "red")

tk.mainloop()

Cependant, lorsqu’on essaie de passer à l’étape suivante de ce programme (faire bouger le ballon avec le temps), le livre que je lis me dit de faire ce qui suit. Changez la fonction draw en:

def draw(self):
    self.canvas.move(self.id, 0, -1)

et ajoutez le code suivant à mon programme:

while 1:
    ball.draw()
    tk.update_idletasks()
    tk.update()
    time.sleep(0.01)

Mais j'ai remarqué que l'ajout de ce bloc de code rendait l'utilisation de tk.mainloop() inutile, puisque tout se présenterait même sans cela !!!

En ce moment, je devrais mentionner que mon livre ne parle jamais de tk.mainloop() (peut-être parce qu'il utilise Python 3 _) mais j'ai appris à ce sujet en cherchant sur le Web car mes programmes ne fonctionnaient pas en copiant le code du livre!

Alors j'ai essayé de faire ce qui suit ne fonctionnerait pas !!!

while 1:
    ball.draw()
    tk.mainloop()
    time.sleep(0.01)

Que se passe-t-il? Qu'est-ce que tk.mainloop()? Que font tk.update_idletasks() et tk.update() et en quoi cela diffère de tk.mainloop()? Dois-je utiliser la boucle ci-dessus? tk.mainloop()? ou les deux dans mes programmes?

45
midkin

tk.mainloop() bloque . Cela signifie que l'exécution de votre programme python s'arrête là. Vous pouvez le voir en écrivant:

while 1:
    ball.draw()
    tk.mainloop()
    print "hello"   #NEW CODE
    time.sleep(0.01)

Vous ne verrez jamais la sortie de l'instruction print. Parce qu'il n'y a pas de boucle, la balle ne bouge pas.

Par contre, les méthodes update_idletasks() et update() ici:

while True:
    ball.draw()
    tk.update_idletasks()
    tk.update()

...ne pas bloquer; l'exécution se poursuit une fois ces méthodes terminées, de sorte que la boucle while s'exécute encore et encore, ce qui fait bouger la balle.

Une boucle infinie contenant les appels de méthode update_idletasks() et update() peut remplacer l'appel de tk.mainloop(). Notez que l’ensemble de la boucle while peut être dit block exactement comme tk.mainloop() car rien n’exécutera après la boucle while.

Cependant, tk.mainloop() ne remplace pas uniquement les lignes:

tk.update_idletasks()
tk.update()

Au lieu de cela, tk.mainloop() est un substitut de la boucle entière while:

while True:
    tk.update_idletasks()
    tk.update()

Réponse au commentaire:

Voici ce que tcl docs dit:

mettre à jour les idletasks

Cette sous-commande de mise à jour supprime tous les événements inactifs actuellement planifiés de la file d'événements de Tcl. Les événements inactifs sont utilisés pour reporter le traitement jusqu'à "il n'y a plus rien à faire", le cas d'utilisation typique étant le redessinage et le recalcul de la géométrie de Tk. En reportant ces opérations jusqu'à ce que Tk soit inactif, des opérations de redessinage coûteuses ne sont effectuées que lorsque toutes les tâches d'un groupe d'événements (par exemple, la libération d'un bouton, le changement de fenêtre en cours, etc.) sont traitées au niveau du script. Cela donne l'impression que Tk est beaucoup plus rapide, mais si vous êtes en train d'effectuer un traitement long, cela peut également signifier qu'aucun événement inactif n'est traité pendant une longue période. En appelant idletasks update, les mises à jour dues aux changements d'état internes sont traitées immédiatement. (Les mises à jour dues au fait que des événements système, par exemple, étant désiconifiés par l'utilisateur, nécessitent une mise à jour complète pour être traitées.)

APN Comme décrit dans la section Mise à jour considérée comme nuisible, l'utilisation de la mise à jour pour gérer les mises à jour non gérées par la mise à jour idletasks pose de nombreux problèmes. Joe English dans une publication comp.lang.tcl décrit une alternative:

Donc, update_idletasks() provoque le traitement de certains sous-ensembles d'événements que update() provoque le traitement.

Depuis le pdate docs :

mettre à jour? idletasks?

La commande update permet d'actualiser l'application en entrant plusieurs fois la boucle d'événements Tcl jusqu'à ce que tous les événements en attente (y compris les rappels inactifs) aient été traités.

Si le mot clé idletasks est spécifié en tant qu'argument de la commande, aucun nouvel événement ni aucune erreur ne sont traités. seuls les rappels inactifs sont invoqués. Ainsi, les opérations normalement différées, telles que les mises à jour de l'affichage et les calculs de disposition de la fenêtre, sont effectuées immédiatement.

KBK (12 février 2000) - Mon opinion personnelle est que la commande [update] n'est pas l'une des meilleures pratiques et qu'un programmeur est bien avisé de l'éviter. J'ai rarement, voire jamais, vu une utilisation de [update] qui ne pourrait pas être programmée plus efficacement par un autre moyen, l'utilisation généralement appropriée des rappels d'événement. À propos, cette précaution s’applique à toutes les commandes Tcl (vwait et tkwait sont les autres coupables) qui entrent dans la boucle d’événements de manière récursive, à l’exception de l’utilisation d’un seul [vwait] au niveau mondial pour lancer la boucle d’événements dans un shell. cela ne le lance pas automatiquement.

Les objectifs les plus courants pour lesquels [mise à jour] a été recommandé sont les suivants: 1) Maintenir l'interface graphique active pendant l'exécution de calculs de longue durée. Voir programme Compte à rebours pour une alternative. 2) Attendre la configuration d’une fenêtre avant d’effectuer des tâches telles que la gestion de la géométrie. L'alternative consiste à lier des événements tels que celui qui informe le processus de la géométrie d'une fenêtre. Voir Centrer une fenêtre pour une alternative.

Quel est le problème avec la mise à jour? Il y a plusieurs réponses. Premièrement, cela tend à compliquer le code de l'interface graphique environnante. Si vous travaillez avec les exercices dans le programme Compte à rebours, vous aurez une idée de la facilité avec laquelle chaque événement est traité par son propre rappel. Deuxièmement, c'est une source d'insectes insidieux. Le problème général est que l'exécution de [update] a des effets secondaires presque sans contraintes; au retour de [update], un script peut facilement découvrir que le tapis a été retiré de dessous. On discute plus avant de ce phénomène chez Update, considéré comme nuisible.

.....

Existe-t-il une chance de faire fonctionner mon programme sans la boucle while?

Oui, mais les choses deviennent un peu difficiles. Vous pourriez penser à quelque chose comme ce qui suit fonctionnerait:

class Ball:
    def __init__(self, canvas, color):
        self.canvas = canvas
        self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
        self.canvas.move(self.id, 245, 100)

    def draw(self):
        while True:
           self.canvas.move(self.id, 0, -1)

ball = Ball(canvas, "red")
ball.draw()
tk.mainloop()

Le problème est que ball.draw () provoquera l'exécution de l'exécution d'une boucle infinie dans la méthode draw (). Tk.mainloop () ne s'exécutera donc jamais et vos widgets ne s'afficheront jamais. Dans la programmation graphique, il faut éviter à tout prix des boucles infinies pour que les widgets restent sensibles aux entrées de l'utilisateur, par exemple. clics de souris.

La question est donc de savoir comment exécuter quelque chose à maintes reprises sans créer une boucle infinie. Tkinter a une réponse à ce problème: la méthode after() d'un widget:

from Tkinter import *
import random
import time

tk = Tk()
tk.title = "Game"
tk.resizable(0,0)
tk.wm_attributes("-topmost", 1)

canvas = Canvas(tk, width=500, height=400, bd=0, highlightthickness=0)
canvas.pack()

class Ball:
    def __init__(self, canvas, color):
        self.canvas = canvas
        self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
        self.canvas.move(self.id, 245, 100)

    def draw(self):
        self.canvas.move(self.id, 0, -1)
        self.canvas.after(1, self.draw)  #(time_delay, method_to_execute)




ball = Ball(canvas, "red")
ball.draw()  #Changed per Bryan Oakley's comment
tk.mainloop()

La méthode after () ne bloque pas (elle crée en réalité un autre thread d'exécution), donc l'exécution continue dans votre programme python après after () est appelé, ce qui signifie que tk.mainloop () est exécuté ensuite, ainsi vos widgets seront configurés et affichés. La méthode after () permet également à vos widgets de répondre aux autres entrées de l'utilisateur. Essayez d’exécuter le programme suivant, puis cliquez avec votre souris sur différents points de la toile:

from Tkinter import *
import random
import time

root = Tk()
root.title = "Game"
root.resizable(0,0)
root.wm_attributes("-topmost", 1)

canvas = Canvas(root, width=500, height=400, bd=0, highlightthickness=0)
canvas.pack()

class Ball:
    def __init__(self, canvas, color):
        self.canvas = canvas
        self.id = canvas.create_oval(10, 10, 25, 25, fill=color)
        self.canvas.move(self.id, 245, 100)

        self.canvas.bind("<Button-1>", self.canvas_onclick)
        self.text_id = self.canvas.create_text(300, 200, anchor='se')
        self.canvas.itemconfig(self.text_id, text='hello')

    def canvas_onclick(self, event):
        self.canvas.itemconfig(
            self.text_id, 
            text="You clicked at ({}, {})".format(event.x, event.y)
        )

    def draw(self):
        self.canvas.move(self.id, 0, -1)
        self.canvas.after(50, self.draw)




ball = Ball(canvas, "red")
ball.draw()  #Changed per Bryan Oakley's comment.
root.mainloop()
68
7stud
while 1:
    root.update()

... est (très!) plus ou moins similaire à:

root.mainloop()

La différence est que mainloop est la manière correcte de coder et que la boucle infinie est subtilement incorrecte. Je soupçonne cependant que la grande majorité du temps fonctionnera. C'est simplement que mainloop est une solution beaucoup plus propre. Après tout, appeler mainloop est essentiellement comme suit:

while the_window_has_not_been_destroyed():
    wait_until_the_event_queue_is_not_empty()
    event = event_queue.pop()
    event.handle()

... qui, comme vous pouvez le constater, n’est pas très différent de votre propre boucle while. Alors, pourquoi créer votre propre boucle infinie alors que tkinter en a déjà une que vous pouvez utiliser?

Mettez dans les termes les plus simples possibles: appelez toujours mainloop comme dernière ligne logique du code de votre programme. C'est ainsi que Tkinter a été conçu pour être utilisé.

11
Bryan Oakley

J'utilise un modèle de conception MVC/MVA, avec plusieurs types de "vues". Un type est un "GuiView", qui est une fenêtre Tk. Je passe une référence de vue à mon objet window qui fait des choses comme des boutons de lien pour voir les fonctions (que la classe adaptateur/contrôleur appelle également).

Pour ce faire, le constructeur de l'objet de vue devait être terminé avant de créer l'objet window. Après avoir créé et affiché la fenêtre, je souhaitais effectuer automatiquement certaines tâches initiales avec la vue. Au début, j'ai essayé de les faire après mainloop (), mais cela n’a pas fonctionné car mainloop () bloqué!

En tant que tel, j'ai créé l'objet window et utilisé tk.update () pour le dessiner. Ensuite, j'ai démarré mes tâches initiales et j'ai finalement commencé la boucle principale.

import Tkinter as tk

class Window(tk.Frame):
    def __init__(self, master=None, view=None ):
        tk.Frame.__init__( self, master )
        self.view_ = view       
        """ Setup window linking it to the view... """

class GuiView( MyViewSuperClass ):

    def open( self ):
        self.tkRoot_ = tk.Tk()
        self.window_ = Window( master=None, view=self )
        self.window_.pack()
        self.refresh()
        self.onOpen()
        self.tkRoot_.mainloop()         

    def onOpen( self ):        
        """ Do some initial tasks... """

    def refresh( self ):        
        self.tkRoot_.update()
1
BuvinJ