¿Cómo pasar argumentos a un comando Button en Tkinter?

176

Supongamos que tengo lo siguiente Buttonhecho con 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)

Se actionllama al método cuando presiono el botón, pero ¿y si quisiera pasar algunos argumentos al método action?

He intentado con el siguiente código:

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

Esto solo invoca el método inmediatamente, y presionar el botón no hace nada.

Jack
fuente
44
frame = Tk.Frame (master = win) .grid (fila = 1, columna = 1) # P. ¿cuál es el valor de frame ahora?
noob oddy

Respuestas:

265

Personalmente prefiero usarlo lambdasen tal escenario, porque en mi opinión es más claro y simple y tampoco te obliga a escribir muchos métodos de envoltura si no tienes control sobre el método llamado, pero eso es ciertamente una cuestión de gustos.

Así es como lo haría con un lambda (tenga en cuenta que también hay alguna implementación de curry en el módulo funcional, por lo que también puede usar eso):

button = Tk.Button(master=frame, text='press', command= lambda: action(someNumber))
Voo
fuente
11
Todavía estás escribiendo métodos de envoltura, solo lo estás haciendo en línea.
agf
54
Esto no funciona si someNumber es, de hecho, una variable que cambia los valores dentro de un bucle que crea muchos botones. Luego, cada botón llamará a action () con el último valor que se ha asignado a someNumber y no el valor que tenía cuando se creó el botón. La solución usando partialfunciona en este caso.
Scrontch
1
Esto funciono muy bien para mi. Sin embargo, ¿puede explicar también por qué "This just invokes the method immediately, and pressing the button does nothing"sucede la declaración de OP ?
Tommy
17
@Scrontch ¡Me pregunto cuántos usuarios principiantes de Tkinter nunca se sintieron en la trampa que mencionaste! En cualquier caso, uno puede capturar el valor actual usando el idioma callback=lambda x=x: f(x)como enfs = [lambda x=x: x*2 for x in range(5)] ; print [f() for f in fs]
gboffi
1
@Voo, ¿a qué te refieres con "aunque la gente de la vieja escuela de Python probablemente se apegará a la asignación de parámetros predeterminada para la lambda"? No conseguí que lambda funcionara y ahora uso parcial.
Klamer Schutte
99

Esto también se puede hacer mediante el uso partialde functools de biblioteca estándar , como este:

from functools import partial
#(...)
action_with_arg = partial(action, arg)
button = Tk.Button(master=frame, text='press', command=action_with_arg)
Dologan
fuente
33
O incluso más corto:button = Tk.Button(master=frame, text='press', command=partial(action, arg))
Klamer Schutte
Perdón por necro'ing, pero ¿cómo haría esto en el caso de dos o más argumentos?
puré de patatas
55
action_with_args = partial(action, arg1, arg2... argN)
Dologan el
Adoptaría este enfoque porque lambda no funcionó para mí.
Zaven Zareyan
19

GUI de ejemplo:

Digamos que tengo la GUI:

import tkinter as tk

root = tk.Tk()

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

root.mainloop()

¿Qué sucede cuando se presiona un botón?

Observe que cuando btnse presiona llama a su propia función, que es muy similar a button_press_handlela del siguiente ejemplo:

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

con:

button_press_handle(btn['command'])

Simplemente puede pensar que esa commandopción debe establecerse como, la referencia al método que queremos llamar, similar a callbackin button_press_handle.


Llamar a un método ( devolución de llamada ) cuando se presiona el botón

Sin argumentos

Entonces, si quería printalgo cuando se presiona el botón, necesitaría configurar:

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

Preste mucha atención a la falta de ()un printmétodo que se omite en el sentido de que: "Este es el nombre del método al que quiero que llame cuando lo presione, pero no lo llame en este mismo instante". Sin embargo, no pasé ningún argumento para el, printpor lo que imprimió lo que imprime cuando se lo llamó sin argumentos.

Con argumento (s)

Ahora, si también quisiera pasar argumentos al método que quiero que me llamen cuando se presiona el botón, podría hacer uso de las funciones anónimas, que se pueden crear con la declaración lambda , en este caso para el printmétodo incorporado, como el siguiente :

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

Llamar a varios métodos cuando se presiona el botón

Sin argumentos

También puede lograrlo usando la lambdadeclaración, pero se considera una mala práctica y, por lo tanto, no la incluiré aquí. La buena práctica es definir un método separado multiple_methodsque llame a los métodos deseados y luego configurarlo como la devolución de llamada al presionar el botón:

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

Con argumento (s)

Para pasar argumentos al método que llama a otros métodos, nuevamente use la lambdadeclaración, pero primero:

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

y luego establecer:

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

Devolver objeto (s) de la devolución de llamada

También tenga en cuenta que callbackno puede realmente returnporque solo se llama dentro button_press_handlecon callback()en lugar de return callback(). Lo hace, returnpero no en cualquier lugar fuera de esa función. Por lo tanto, debería modificar los objetos a los que se puede acceder en el ámbito actual.


Ejemplo completo con modificaciones de objeto globales

El siguiente ejemplo llamará a un método que cambia btnel texto de cada vez que se presiona el botón:

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()

Espejo

Nae
fuente
10

La capacidad de Python para proporcionar valores predeterminados para argumentos de función nos da una salida.

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

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

Para más botones, puede crear una función que devuelva una función:

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
fuente
44
Esto no resuelve el problema. ¿Qué sucede si está creando tres botones que todos llaman a la misma función pero necesitan pasar argumentos diferentes?
Bryan Oakley
Puede crear una función que devuelva una función.
MarrekNožka
Sé que esto ya no está activo, pero me vinculé aquí desde stackoverflow.com/questions/35616411/… , esto funciona exactamente de la misma manera que el uso de expresiones lambda, puede definir una función para cada botón de la misma manera que haciendo una expresión lambda para cada botón.
Tadhg McDonald-Jensen
poniendo el primer ejemplo de código en un bucle que sigue cambiando myXy myYfunciona perfectamente muchas gracias.
Tadhg McDonald-Jensen
3

Sobre la base de la respuesta de Matt Thompson: una clase se puede hacer invocable para que se pueda usar en lugar de una función:

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
fuente
2

La razón por la que invoca el método inmediatamente y al presionar el botón no hace nada es que action(somenumber)se evalúa y su valor de retorno se atribuye como el comando para el botón. Entonces, si actionimprime algo para decirle que se ha ejecutado y regresa None, solo ejecuta actionpara evaluar su valor de retorno y se le da Nonecomo el comando para el botón.

Para tener botones para llamar a funciones con diferentes argumentos, puede usar variables globales, aunque no puedo recomendarlo:

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()

Lo que haría es hacer un classobjeto cuyos objetos contendrían todas las variables requeridas y métodos para cambiarlos según sea necesario:

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
fuente
2
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))

Creo que debería arreglar esto

Harrison Sills
fuente
2

Lo mejor que puede hacer es usar lambda de la siguiente manera:

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

Llego extremadamente tarde, pero aquí hay una manera muy simple de lograrlo.

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()

Simplemente envuelva la función que desea usar en otra función y llame a la segunda función al presionar el botón.

Rhett
fuente
Como su código ha puesto var1y var2en el módulo principal, puede omitir function1y poner una printdeclaración function2. ¿Se refiere a otras situaciones que no entiendo?
Brom
2

Las lambdas están muy bien, pero también puedes probar esto (que funciona en un bucle for por cierto):

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)

Esto funciona porque cuando se establece el enlace, una pulsación de tecla pasa el evento como argumento. A continuación, puede llamar los atributos fuera del evento como event.charobtener "1" o "UP" ect. Si necesita un argumento o varios argumentos distintos de los atributos del evento. solo crea un diccionario para almacenarlos.

Bugbeeb
fuente
2

También he encontrado este problema antes. Puedes usar lambda:

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

Use una lambda para pasar los datos de entrada a la función de comando si tiene más acciones para llevar a cabo, de esta manera (he intentado hacerlo genérico, así que solo adapte):

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

Esto pasará la información en el evento a la función del botón. Puede haber más formas pitonescas de escribir esto, pero funciona para mí.

Jayce
fuente
1

JasonPy - algunas cosas ...

Si coloca un botón en un bucle, se creará una y otra vez ... lo que probablemente no sea lo que desea. (tal vez lo es) ...

La razón por la que siempre obtiene el último índice son los eventos lambda que se ejecutan cuando hace clic en ellos, no cuando se inicia el programa. No estoy seguro al 100% de lo que está haciendo, pero tal vez intente almacenar el valor cuando esté hecho y luego llámelo más tarde con el botón lambda.

por ejemplo: (no use este código, solo un ejemplo)

for entry in stuff_that_is_happening:
    value_store[entry] = stuff_that_is_happening

entonces puedes decir ...

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

¡espero que esto ayude!

David W. Beck
fuente
1

Una forma sencilla sería configurar buttoncon lambdala siguiente sintaxis:

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

Para la posteridad: también puedes usar clases para lograr algo similar. Por ejemplo:

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

El botón puede ser creado simplemente por:

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

Este enfoque también le permite cambiar los argumentos de la función, es decir, estableciendo instance1.x = 3.

Matt Thompson
fuente
1

Necesitas usar lambda:

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

Utilizar 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()

salida:

hello
Giovanni G. PY
fuente