Comment passer des arguments à une commande Button dans Tkinter?

176

Supposons que j'ai fait ce qui suit Buttonavec Tkinter en Python:

import Tkinter as Tk
win = Tk.Toplevel()
frame = Tk.Frame(master=win).grid(row=1, column=1)
button = Tk.Button(master=frame, text='press', command=action)

La méthode actionest appelée lorsque j'appuie sur le bouton, mais que faire si je souhaite transmettre des arguments à la méthode action?

J'ai essayé avec le code suivant:

button = Tk.Button(master=frame, text='press', command=action(someNumber))

Cela appelle simplement la méthode immédiatement et le fait d'appuyer sur le bouton ne fait rien.

Jack
la source
4
frame = Tk.Frame (master = win) .grid (row = 1, column = 1) # Q. quelle est la valeur de frame maintenant?
noob oddy

Réponses:

265

Je préfère personnellement utiliser lambdas dans un tel scénario, car imo est plus clair et plus simple et ne vous oblige pas non plus à écrire beaucoup de méthodes wrapper si vous n'avez pas le contrôle sur la méthode appelée, mais c'est certainement une question de goût.

C'est ainsi que vous le feriez avec un lambda (notez qu'il y a aussi une implémentation de currying dans le module fonctionnel, vous pouvez donc l'utiliser aussi):

button = Tk.Button(master=frame, text='press', command= lambda: action(someNumber))
Voo
la source
11
Vous écrivez toujours des méthodes wrapper, vous le faites simplement en ligne.
agf
54
Cela ne fonctionne pas si someNumber est en fait une variable qui change les valeurs à l'intérieur d'une boucle qui crée de nombreux boutons. Ensuite, chaque bouton appellera action () avec la dernière valeur qui a été affectée à someNumber et non la valeur qu'elle avait lors de la création du bouton. La solution utilisant partialfonctionne dans ce cas.
Scrontch
1
Cela a très bien fonctionné pour moi. Cependant, pouvez-vous également expliquer pourquoi la déclaration des PO "This just invokes the method immediately, and pressing the button does nothing"se produit?
Tommy
17
@Scrontch Je me demande combien d'utilisateurs novices de Tkinter ne se sont jamais sentis dans le piège que vous avez mentionné! En tout cas, on peut capturer la valeur actuelle en utilisant l'idiome callback=lambda x=x: f(x)comme dansfs = [lambda x=x: x*2 for x in range(5)] ; print [f() for f in fs]
gboffi
1
@Voo que voulez-vous dire ci-dessus avec "bien que les gens de la vieille école python s'en tiendront probablement à l'affectation des paramètres par défaut pour le lambda"? Je n'ai pas fait fonctionner lambda et donc maintenant utiliser partial.
Klamer Schutte
99

Cela peut également être fait en utilisant partialles functools de la bibliothèque standard , comme ceci:

from functools import partial
#(...)
action_with_arg = partial(action, arg)
button = Tk.Button(master=frame, text='press', command=action_with_arg)
Dologan
la source
33
Ou encore plus court:button = Tk.Button(master=frame, text='press', command=partial(action, arg))
Klamer Schutte
Désolé pour nécro'ing, mais comment feriez-vous cela dans le cas de deux ou plusieurs arguments?
purée de pommes de terre
5
action_with_args = partial(action, arg1, arg2... argN)
Dologan
J'adopterais cette approche parce que lambda ne fonctionnait pas pour moi.
Zaven Zareyan le
19

Exemple d'interface graphique:

Disons que j'ai l'interface graphique:

import tkinter as tk

root = tk.Tk()

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

root.mainloop()

Que se passe-t-il lorsqu'un bouton est enfoncé

Voyez que lorsque vous btnappuyez dessus, il appelle sa propre fonction, ce qui est très similaire à celui button_press_handlede l'exemple suivant:

def button_press_handle(callback=None):
    if callback:
        callback() # Where exactly the method assigned to btn['command'] is being callled

avec:

button_press_handle(btn['command'])

Vous pouvez simplement penser que cette commandoption doit être définie comme la référence à la méthode que nous voulons appeler, similaire à callbackdans button_press_handle.


Appel d'une méthode ( rappel ) lorsque le bouton est enfoncé

Sans arguments

Alors si je voulais print quelque chose lorsque le bouton est enfoncé, j'aurais besoin de définir:

btn['command'] = print # default to print is new line

Faites très attention à l' absence de ()avec la printméthode qui est omise dans le sens que: "C'est le nom de la méthode que je veux que vous appeliez lorsque vous appuyez dessus, mais ne l'appelez pas juste à l'instant même."Cependant, je n'ai pas passé d'arguments pour le, printdonc il a imprimé tout ce qu'il imprime lorsqu'il est appelé sans arguments.

Avec argument (s)

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

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

Appel de plusieurs méthodes lorsque vous appuyez sur le bouton

Sans pour autant arguments

Vous pouvez également y parvenir en utilisant une lambdadéclaration, mais c'est considéré comme une mauvaise pratique et je ne l'inclurai donc pas ici. La bonne pratique consiste à définir une méthode distincte multiple_methods, qui appelle les méthodes souhaitées, puis à la définir comme rappel de la pression du bouton:

def multiple_methods():
    print("Vicariously") # the first inner callback
    print("I") # another inner callback

Avec argument (s)

Afin de passer des arguments à la méthode qui appelle d'autres méthodes, utilisez à nouveau lambdainstruction, mais d'abord:

def multiple_methods(*args, **kwargs):
    print(args[0]) # the first inner callback
    print(kwargs['opt1']) # another inner callback

puis définissez:

btn['command'] = lambda arg="live", kw="as the" : a_new_method(arg, opt1=kw)

Retour des objets à partir du rappel

Notez également que callbackcela ne peut pas vraiment returnparce qu'il est uniquement appelé à l'intérieur button_press_handleavec callback()par opposition à return callback(). Cela fonctionne, returnmais pas n'importe où en dehors 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 change btnle texte de chaque fois que vous appuyez sur le bouton:

import tkinter as tk

i = 0
def text_mod():
    global i, btn           # btn can be omitted but not sure if should be
    txt = ("Vicariously", "I", "live", "as", "the", "whole", "world", "dies")
    btn['text'] = txt[i]    # the global object that is modified
    i = (i + 1) % len(txt)  # another global object that gets modified

root = tk.Tk()

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

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

root.mainloop()

Miroir

Nae
la source
10

La capacité de Python à fournir des valeurs par défaut pour les arguments de fonction nous donne une issue.

def fce(x=myX, y=myY):
    myFunction(x,y)
button = Tk.Button(mainWin, text='press', command=fce)

Voir: http://infohost.nmt.edu/tcc/help/pubs/tkinter/web/extra-args.html

Pour plus de boutons, vous pouvez créer une fonction qui renvoie une fonction:

def fce(myX, myY):
    def wrapper(x=myX, y=myY):
        pass
        pass
        pass
        return x+y
    return wrapper

button1 = Tk.Button(mainWin, text='press 1', command=fce(1,2))
button2 = Tk.Button(mainWin, text='press 2', command=fce(3,4))
button3 = Tk.Button(mainWin, text='press 3', command=fce(9,8))
MarrekNožka
la source
4
Cela ne résout pas le problème. Que faire si vous créez trois boutons qui appellent tous la même fonction mais doivent passer des arguments différents?
Bryan Oakley
Vous pouvez créer une fonction qui renvoie une fonction.
MarrekNožka
Je sais que ce n'est plus actif mais je me suis lié ici à partir de stackoverflow.com/questions/35616411/… , cela fonctionne exactement de la même manière que l'utilisation d'expressions lambda, vous pouvez définir une fonction pour chaque bouton de la même manière que faire une expression lambda pour chaque bouton.
Tadhg McDonald-Jensen
mettre le premier exemple de code dans une boucle qui ne cesse de changer myXet myYfonctionne parfaitement merci beaucoup.
Tadhg McDonald-Jensen
3

S'appuyant sur la réponse de Matt Thompsons: une classe peut être rendue appelable afin qu'elle puisse être utilisée à la place d'une fonction:

import tkinter as tk

class Callback:
    def __init__(self, func, *args, **kwargs):
        self.func = func
        self.args = args
        self.kwargs = kwargs
    def __call__(self):
        self.func(*self.args, **self.kwargs)

def default_callback(t):
    print("Button '{}' pressed.".format(t))

root = tk.Tk()

buttons = ["A", "B", "C"]

for i, b in enumerate(buttons):
    tk.Button(root, text=b, command=Callback(default_callback, b)).grid(row=i, column=0)

tk.mainloop()
Christoph Frings
la source
2

La raison pour laquelle il appelle la méthode immédiatement et que le fait d'appuyer sur le bouton ne fait rien est qu'il action(somenumber)est évalué et que sa valeur de retour est attribuée en tant que commande pour le bouton. Donc, si actionquelque chose imprime pour vous dire qu'il a été exécuté et qu'il retourne None, il vous suffit de l'exécuter actionpour évaluer sa valeur de retour et de le donner Nonecomme commande pour le bouton.

Pour avoir des boutons pour appeler des fonctions avec différents arguments, vous pouvez utiliser des variables globales, bien que je ne puisse pas le recommander:

import Tkinter as Tk

frame = Tk.Frame(width=5, height=2, bd=1, relief=Tk.SUNKEN)
frame.grid(row=2,column=2)
frame.pack(fill=Tk.X, padx=5, pady=5)
def action():
    global output
    global variable
    output.insert(Tk.END,variable.get())
button = Tk.Button(master=frame, text='press', command=action)
button.pack()
variable = Tk.Entry(master=frame)
variable.pack()
output = Tk.Text(master=frame)
output.pack()

if __name__ == '__main__':
    Tk.mainloop()

Ce que je ferais, c'est créer un classobjet dont les objets contiendraient toutes les variables requises et les méthodes pour les modifier au besoin:

import Tkinter as Tk
class Window:
    def __init__(self):
        self.frame = Tk.Frame(width=5, height=2, bd=1, relief=Tk.SUNKEN)
        self.frame.grid(row=2,column=2)
        self.frame.pack(fill=Tk.X, padx=5, pady=5)

        self.button = Tk.Button(master=self.frame, text='press', command=self.action)
        self.button.pack()

        self.variable = Tk.Entry(master=self.frame)
        self.variable.pack()

        self.output = Tk.Text(master=self.frame)
        self.output.pack()

    def action(self):
        self.output.insert(Tk.END,self.variable.get())

if __name__ == '__main__':
    window = Window()
    Tk.mainloop()
berna1111
la source
2
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))

Je pense que devrait résoudre ce problème

Harrison Sills
la source
2

La meilleure chose à faire est d'utiliser lambda comme suit:

button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))
dominic thorpe
la source
2

Je suis extrêmement en retard, mais voici une manière très simple de l'accomplir.

import tkinter as tk
def function1(param1, param2):
    print(str(param1) + str(param2))

var1 = "Hello "
var2 = "World!"
def function2():
    function1(var1, var2)

root = tk.Tk()

myButton = tk.Button(root, text="Button", command=function2)
root.mainloop()

Enveloppez simplement la fonction que vous souhaitez utiliser dans une autre fonction et appelez la deuxième fonction en appuyant sur le bouton.

Rhett
la source
Comme votre code a mis var1et var2dans le module principal, vous pouvez sauter function1du tout et insérer une printinstruction function2. Faites-vous référence à d'autres situations que je ne comprends pas?
Brom le
2

Les lambdas sont tous très bien, mais vous pouvez également essayer ceci (qui fonctionne dans une boucle for btw):

root = Tk()

dct = {"1": [*args], "2": [*args]}
def keypress(event):
    *args = dct[event.char]
    for arg in args:
        pass
for i in range(10):
    root.bind(str(i), keypress)

Cela fonctionne car lorsque la liaison est définie, une pression sur une touche transmet l'événement en tant qu'argument. Vous pouvez alors appeler des attributs hors de l'événement comme event.charpour obtenir "1" ou "UP" ect. Si vous avez besoin d'un argument ou de plusieurs arguments autres que les attributs d'événement. créez simplement un dictionnaire pour les stocker.

Bugbeeb
la source
2

J'ai également rencontré ce problème auparavant. Vous pouvez simplement utiliser lambda:

button = Tk.Button(master=frame, text='press',command=lambda: action(someNumber))
Andrew Shi
la source
1

Utilisez un lambda pour transmettre les données d'entrée à la fonction de commande si vous avez plus d'actions à effectuer, comme celle-ci (j'ai essayé de le rendre générique, alors adaptez-vous):

event1 = Entry(master)
button1 = Button(master, text="OK", command=lambda: test_event(event1.get()))

def test_event(event_text):
    if not event_text:
        print("Nothing entered")
    else:
        print(str(event_text))
        #  do stuff

Cela transmettra les informations de l'événement à la fonction de bouton. Il y a peut-être des façons plus pythonesques d'écrire cela, mais cela fonctionne pour moi.

Jayce
la source
1

JasonPy - quelques petites choses ...

si vous collez un bouton dans une boucle, il sera créé encore et encore et encore ... ce qui n'est probablement pas ce que vous voulez. (peut etre c'est)...

La raison pour laquelle il obtient toujours le dernier index est que les événements lambda sont exécutés lorsque vous cliquez dessus, et non au démarrage du programme. Je ne suis pas sûr à 100% de ce que vous faites, mais essayez peut-être de stocker la valeur lorsqu'elle est créée, puis appelez-la plus tard avec le bouton lambda.

par exemple: (n'utilisez pas ce code, juste un exemple)

for entry in stuff_that_is_happening:
    value_store[entry] = stuff_that_is_happening

alors vous pouvez dire ...

button... command: lambda: value_store[1]

J'espère que cela t'aides!

David W. Beck
la source
1

Un moyen simple serait de configurer buttonavec lambdala syntaxe suivante:

button['command'] = lambda arg1 = local_var1, arg2 = local_var2 : function(arg1, arg2)
Nae
la source
1

Pour la postérité: vous pouvez également utiliser des classes pour réaliser quelque chose de similaire. Par exemple:

class Function_Wrapper():
    def __init__(self, x, y, z):
        self.x, self.y, self.z = x, y, z
    def func(self):
        return self.x + self.y + self.z # execute function

Le bouton peut alors être simplement créé par:

instance1 = Function_Wrapper(x, y, z)
button1  = Button(master, text = "press", command = instance1.func)

Cette approche vous permet également de changer les arguments de la fonction en définissant ie instance1.x = 3.

Matt Thompson
la source
1

Vous devez utiliser lambda:

button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))
scruffyboy13
la source
1

Utiliser lambda

import tkinter as tk

root = tk.Tk()
def go(text):
    print(text)

b = tk.Button(root, text="Click", command=lambda: go("hello"))
b.pack()
root.mainloop()

production:

hello
Giovanni G. PY
la source