108 votes

Pourquoi la commande de mon bouton est-elle exécutée immédiatement lorsque je crée le bouton, et non lorsque je clique dessus ?

Mon code est:

from Tkinter import *

admin = Tk()
def button(an):
    print(an)
    print('het')

b = Button(admin, text='as', command=button('hey'))
b.pack()
mainloop()

Le bouton ne fonctionne pas, il imprime 'hey' et 'het' une fois sans ma commande, et ensuite, lorsque j'appuie sur le bouton, rien ne se passe.

11 votes

@Mike-SMT C'est exactement pour cela que je veux récompenser les gens qui publient de bonnes réponses à des questions courantes - surtout si les questions sont faciles. Beaucoup de personnes publient des réponses bâclées et peu motivées à des questions faciles. Je veux que les gens réalisent qu'il n'est pas nécessaire d'être un expert en programmation pour rédiger des réponses exceptionnelles.

125voto

Bryan Oakley Points 63365

Considérez ce code :

b = Button(admin, text='as', command=button('hey'))

Cela fait exactement la même chose que ceci :

result = button('hey')
b = button(admin, text='as', command=result)

De même, si vous créez une liaison comme ceci :

listbox.bind("<>", some_function())

... c'est la même chose que ceci :

result = some_function()
listbox.bind("<>", result)

L'option command prend une référence à une fonction, ce qui signifie que vous devez lui passer le nom de la fonction. Pour passer une référence, vous devez utiliser uniquement le nom, sans utiliser de parenthèses ou d'arguments. Par exemple :

b = Button(... command = button)

Si vous souhaitez passer un paramètre tel que "hey", vous devez utiliser un peu de code supplémentaire :

  • Vous pouvez créer une fonction intermédiaire qui peut être appelée sans votre argument et qui appelle ensuite votre fonction button,
  • Vous pouvez utiliser lambda pour créer ce qui est appelé une fonction anonyme. De toutes les manières, c'est une fonction sauf qu'elle n'a pas de nom. Lorsque vous appelez la commande lambda, elle renvoie une référence à la fonction créée, ce qui signifie qu'elle peut être utilisée pour la valeur de l'option command du bouton.
  • Vous pouvez utiliser functools.partial

Pour moi, lambda est le plus simple car il ne nécessite pas d'importations supplémentaires comme le fait functools.partial, même si certaines personnes pensent que functools.partial est plus facile à comprendre.

Pour créer une fonction lambda qui appelle votre fonction button avec un argument, vous feriez quelque chose comme ceci :

lambda: button('hey')

Vous obtenez une fonction équivalente à :

def some_name():
    return button('hey')

Comme je l'ai dit plus tôt, lambda renvoie une référence à cette fonction sans nom. Étant donné qu'une référence est ce que l'option command attend, vous pouvez utiliser lambda directement dans la création du bouton :

b = Button(... command = lambda: button('hey'))

Il y a une question sur ce site qui comporte de nombreux commentaires intéressants sur lambda en général. Voir la question Pourquoi les lambdas Python sont-ils utiles ?. Cette même discussion a une réponse qui montre comment utiliser les lambdas dans une boucle lorsque vous avez besoin de transmettre une variable au rappel.

Enfin, consultez l'article zone.effbot.org intitulé Tkinter Callbacks pour un bon tutoriel. La couverture de lambda est assez maigre, mais les informations qui s'y trouvent pourraient être utiles.

16voto

Lukáš Lalinský Points 22537

Vous devez créer une fonction sans paramètres que vous pouvez utiliser comme commande :

b = Button(admin, text='as', command=lambda: button('hey'))

Voir la section "Passer des arguments aux rappels" de ce document.

15voto

Nae Points 5451

Exemple GUI :

Supposons que j'ai l'interface graphique (GUI) :

import tkinter as tk

root = tk.Tk()

btn = tk.Button(root, text="Appuyez")
btn.pack()

root.mainloop()

Ce Qui Se Passe Quand un Bouton Est Pressé

Remarquez que lorsque le btn est pressé, il appelle sa propre fonction qui est très similaire à button_press_handle dans l'exemple suivant :

def button_press_handle(callback=None):
    if callback:
        callback() # Où exactement la méthode assignée à btn['command'] est appelée

avec :

button_press_handle(btn['command'])

Vous pouvez simplement penser que l'option command devrait être définie comme une référence à la méthode que nous voulons appeler, similaire à callback dans button_press_handle.


Appel d'une Méthode (un Callback) Lorsque le Bouton Est Pressé

Sans arguments

Donc si je voulais print quelque chose lorsque le bouton est pressé, je devrais définir :

btn['command'] = print # par défaut, print est une nouvelle ligne

Prêtez attention au manque de () avec la méthode print qui est omis dans le sens que : "C'est le nom de la méthode que je veux que vous appeliez lorsque vous appuyez mais ne l'appelez pas juste à cet instant précis." Cependant, je n'ai pas passé d'arguments pour print donc elle a imprimé ce qu'elle imprime lorsqu'elle est appelée sans arguments.

Avec Argument(s)

Maintenant, si je voulais également passer des arguments à la méthode que je veux appeler lorsque le bouton est pressé, je pourrais utiliser les fonctions anonymes, qui peuvent être créées avec l'instruction lambda, dans ce cas pour la méthode intégrée print, comme suit :

btn['command'] = lambda arg1="Bonjour", arg2=" ", arg3="Monde !" : print(arg1 + arg2 + arg3)

Appel de Multiples Méthodes Lorsque le Bouton Est Pressé

Sans Arguments

Vous pouvez également y parvenir en utilisant l'instruction lambda mais cela est considéré comme une mauvaise pratique et donc je ne l'inclurai pas ici. La bonne pratique est de définir une méthode séparée, multiple_methods, qui appelle les méthodes souhaitées et ensuite la définir comme rappel pour l'appui sur le bouton :

def multiple_methods():
    print("Vicariement") # le premier rappel interne
    print("Je") # un autre rappel interne

Avec Argument(s)

Pour passer des argument(s) à la méthode qui appelle d'autres méthodes, utilisez à nouveau l'instruction lambda, mais d'abord :

def multiple_methods(*args, **kwargs):
    print(args[0]) # le premier rappel interne
    print(kwargs['opt1']) # un autre rappel interne

et ensuite définissez :

btn['command'] = lambda arg="vivre", kw="comme le" : une_nouvelle_methode(arg, opt1=kw)

Retour d'Objet(s) Depuis le Callback

Notez également que le callback ne peut pas vraiment return car il n'est appelé que à l'intérieur de button_press_handle avec callback() par opposition à return callback(). Il return mais pas à l'extérieur de cette fonction. Ainsi, vous devriez plutôt modifier les objets accessibles dans la portée actuelle.


Exemple Complet avec Modification(s) d'Objets globaux

L'exemple ci-dessous appellera une méthode qui changera le texte de btn à chaque fois que le bouton est pressé :

import tkinter as tk

i = 0
def text_mod():
    global i, btn           # btn peut être omis mais pas sûr s'il doit l'être
    txt = ("Vicariement", "Je", "vis", "comme", "le", "monde", "entier", "meurt")
    btn['text'] = txt[i]    # l'objet global qui est modifié
    i = (i + 1) % len(txt)  # un autre objet global qui est modifié

root = tk.Tk()

btn = tk.Button(root, text="Mon Bouton")
btn['command'] = text_mod

btn.pack(fill='both', expand=True)

root.mainloop()

Mirroir

4voto

C.Vergnaud Points 633

Le moteur évalue le résultat de la fonction lorsqu'il assigne la valeur à la ligne "...commande = ..."

La "commande" attend qu'une fonction soit retournée, c'est pourquoi l'utilisation d'une lambda peut faire l'affaire car elle crée une fonction anonyme qui est renvoyée à la "commande" lors de l'évaluation. Vous pouvez également coder votre propre fonction, cela fera également l'affaire.

voici un exemple avec lambda et sans lambda:

#!/usr/bin/python
# codage=utf-8

from Tkinter import *
# Création de la fenêtre principale (fenêtre principale)
Mafenetre = Tk()
res1 = StringVar()
res2 = StringVar()

def isValidInput(obj):
    if hasattr(obj, 'get') and callable(getattr(obj, 'get')):
        return TRUE
    return FALSE

# action stupide 2 (retourne 12 volontairement pour montrer une erreur potentielle)
def action1(*arguments):
    print "action1 en cours"
    for arg in arguments:
        if isValidInput(arg):
            print "valeur d'entrée: ", arg.get()
            res1.set(arg.get())
        else:
            print "autre valeur:", arg
    print "\n"
    return 12

# action stupide 2
def action2(*arguments):
    print "action2 en cours"
    a = arguments[0]
    b = arguments[1]
    if isValidInput(a) and isValidInput(b):
        c = a.get() + b.get()
        res2.set(c)
        print c
    print "\n"

# un gestionnaire de tâches stupide commandé par nom
def start_tasks(*arguments, **keywords):
    keys = sorted(keywords.keys())
    for kw in keys:
        print kw, "branché "
        keywords[kw](*arguments)

# wrapper de rappel valide avec lambda
def action1_callback(my_input):
    return lambda args=[my_input]: action1(*args)

# wrapper de rappel valide sans lambda
def action1_callback_nolambda(*args, **kw):
    def anon():
        action1(*args)
    return anon

# première chaîne d'entrée
input1 = StringVar()
input1.set("effacez-moi...")
f1 = Entry(Mafenetre, textvariable=input1, bg='bisque', fg='maroon')
f1.focus_set()
f1.pack(fill="both", expand="yes", padx="5", pady=5)

# rappel échoué car la fonction action1 est évaluée, elle renverra 12. 
# dans ce cas, le bouton ne fonctionnera pas du tout, car l'assignation attend une fonction 
# afin que la commande du bouton puisse exécuter quelque chose
ba1 = Button(Mafenetre)
ba1['text'] = "montrer l'entrée 1 (ko)"
ba1['command'] = action1(input1)
ba1.pack(fill="both", expand="yes", padx="5", pady=5)

# bouton fonctionnant avec un wrapper
ba3 = Button(Mafenetre)
ba3['text'] = "montrer l'entrée 1 (ok)"
# sans lambda, cela fonctionne également si l'assignation est une fonction
#ba1['command'] = action1_callback_nolambda(input1)
ba3['command'] = action1_callback(input1)
ba3.pack(fill="both", expand="yes", padx="5", pady=5)

# libellé de résultat d'affichage
Label1 = Label(Mafenetre, text="Résultat de l'action 1:")
Label1.pack(fill="both", expand="yes", padx="5", pady=5)
# afficher la valeur du résultat
resl1 = Label(Mafenetre, textvariable=res1)
resl1.pack(fill="both", expand="yes", padx="5", pady=5)

# deuxième chaîne d'entrée
input2 = StringVar()
f2 = Entry(Mafenetre, textvariable=input2, bg='bisque', fg='maroon')
f2.focus_set()
f2.pack(fill="both", expand="yes", padx="5", pady=5)

# troisième test sans wrapper, mais en s'assurant que plusieurs arguments sont bien gérés par une fonction lambda
ba2 = Button(Mafenetre)
ba2['text'] = "exécuter action 2"
ba2['command'] = lambda args=[input1, input2], action=action2: start_tasks(*args, do=action)
ba2.pack(fill="both", expand="yes", padx="5", pady=5)

# libellé de résultat d'affichage
Label2 = Label(Mafenetre, text="Résultat de l'action 2:")
Label2.pack(fill="both", expand="yes", padx="5", pady=5)
# afficher la valeur du résultat
resl2 = Label(Mafenetre, textvariable=res2)
resl2.pack(fill="both", expand="yes", padx="5", pady=5)

Mafenetre.mainloop()

3voto

Deer Lawson Points 21

Je pense que la meilleure façon de résoudre ce problème est d'utiliser une fonction lambda.

from tkinter import *
admin= Tk()
def button(an):
    print(an)
    print("het")
b = Button(admin, text="as", command=lambda: button("hey"))
b.pack()
mainloop()

Si vous ne voulez pas utiliser le mot-clé command, vous pouvez utiliser la méthode .bind() à la place :

from tkinter import *
admin= Tk()
def button(an):
    print(an)
    print("het")
b = Button(admin, text="as")
b.pack()
b.bind("", lambda bb: button("hey"))
mainloop()

Utiliser une fonction mère (sans paramètre) qui possède la fonction enfant (au moins 1 paramètre) que vous voulez appeler est stupide.

Juste pour partager avec vous, voici l'un de mes programmes :

import tkinter
window = tkinter.Tk()

def plus_them(field_1, field_2, field_3):
    field_3.delete(0, 'end')
    num1 = 0
    num2 = 0
    try:
        num1 = int(field_1.get())
        num2 = int(field_2.get())
    except:
        print("Une exception s'est produite")
    else:
        print("Continuer")
    result = num1 + num2
    field_3.insert(tkinter.END, str(result))
    return result
def minus_them(field_1, field_2, field_3):
    field_3.delete(0, 'end')
    num1 = 0
    num2 = 0
    try:
        num1 = int(field_1.get())
        num2 = int(field_2.get())
    except:
        print("Une exception s'est produite")
    else:
        print("Continuer")
    result = num1 - num2
    field_3.insert(tkinter.END, str(result))
    return result

#Panel d'entrée :
label_1 = tkinter.Label(window, text="Premier Nombre :")
label_1.grid(row=0, column=0)
label_2 = tkinter.Label(window, text="Deuxième Nombre :")
label_2.grid(row=1, column=0)
entry_1 = tkinter.Entry(window)
entry_1.grid(row=0, column=1)
entry_2 = tkinter.Entry(window)
entry_2.grid(row=1, column=1)

#Panel de boutons :
button_1 = tkinter.Button(window, text="Plus")
button_1.grid(row=2, column=0)
button_2 = tkinter.Button(window, text="Moins")
button_2.grid(row=2, column=1)

#Panel de réponse :
label_3 = tkinter.Label(window, text="La réponse :")
label_3.grid(row=3, column=0)
entry_3 = tkinter.Entry(window)
entry_3.grid(row=3, column=1)

#Gestion des événements :
button_1.bind("", lambda p: plus_them(entry_1, entry_2, entry_3))
button_2.bind("", lambda m: minus_them(entry_1, entry_2, entry_3))

#Configuration de la fenêtre :
window.title("Calculatrice d'Addition et de Soustraction")
window.mainloop()

C'est tout.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X