imagen - eventos en python
¿Cómo pasar argumentos a un comando de botón en Tkinter? (11)
Supongamos que tengo el siguiente Button
creado 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)
La action
método se invoca cuando presiono el botón, pero ¿qué ocurre si deseo pasar algunos argumentos a la action
método?
Lo he intentado con el siguiente código:
button = Tk.Button(master=frame, text=''press'', command=action(someNumber))
Esto solo invoca el método de inmediato, y presionar el botón no hace nada.
Ejemplo de GUI:
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 se presiona btn
llama a su propia función que es muy similar a button_press_handle
en el 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 la opción de command
debe establecerse como, la referencia al método que queremos que se llame, similar a la callback
de callback
en button_press_handle
.
Llamar a un método ( Callback ) cuando se presiona el botón
Sin argumentos
Entonces, si quisiera print
algo cuando se presione el botón, necesitaría configurar:
btn[''command''] = print # default to print is new line
Preste mucha atención a la falta de ()
con el método de print
que se omite en el sentido de que: "Este es el nombre del método al que quiero llamar cuando lo presiono, pero no lo llamo solo en este mismo instante". Sin embargo, no pasé ningún argumento para la print
así que imprimió lo que imprime cuando se lo llama sin argumentos.
Con argumento (s)
Ahora bien, si también quisiera pasar argumentos al método al que quiero llamar cuando se presiona el botón, podría utilizar las funciones anónimas, que se pueden crear con la declaración lambda , en este caso para el método integrado de print
, como el siguiendo:
btn[''command''] = lambda arg1="Hello", arg2=" ", arg3="World!" : print(arg1 + arg2 + arg3)
Llamar a métodos múltiples cuando se presiona el botón
Sin argumentos
También puede lograr eso usando la declaración lambda
pero se considera mala práctica y por lo tanto no la incluiré aquí. La buena práctica es definir un método separado, multiple_methods
, que llame a los métodos deseados y luego establecerlo como la devolución de llamada al botón presionar:
def multiple_methods():
print("Vicariously") # the first inner callback
print("I") # another inner callback
Con argumento (s)
Para pasar un argumento (s) al método que llama a otros métodos, nuevamente haga uso de la declaración lambda
, 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)
Devolución de objeto (s) desde la devolución de llamada
Además, tenga en cuenta que la callback
realidad no puede return
ya que solo se button_press_handle
dentro de button_press_handle
con callback()
en lugar de return callback()
. Sí return
pero no está 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 global objetos
El ejemplo siguiente llamará a un método que cambia el texto de btn
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()
Mirror
Esto también se puede hacer mediante el uso partial
de los functools 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)
JasonPy - algunas cosas ...
si pones un botón en un bucle, se creará una y otra vez ... lo cual probablemente no sea lo que quieres. (tal vez lo es) ...
La razón por la que siempre obtiene el último índice es que los eventos lambda se ejecutan al hacer 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, 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!
La capacidad de Python para proporcionar valores predeterminados para los argumentos de la función nos proporciona 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 devuelve 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))
La razón por la que invoca el método de inmediato y al presionar el botón no hace nada es que la action(somenumber)
se evalúa y su valor de retorno se atribuye como el comando para el botón. Por lo tanto, si la action
imprime algo que indica que se ha ejecutado y devuelve None
, solo ejecuta la action
para evaluar su valor de retorno y le da None
como 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 crear una class
cuyos objetos contengan todas las variables requeridas y métodos para cambiarlas 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()
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, establecer instance1.x = 3
.
Sobre la base de la respuesta de Matt Thompson: una clase puede hacerse invocable para que pueda usarse 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()
Una manera simple sería configurar el button
con lambda
como en la siguiente sintaxis:
button[''command''] = lambda arg1 = local_var1, arg2 = local_var2 : function(arg1, arg2)
Utilice una lambda para pasar los datos de entrada a la función de comando si tiene más acciones para llevar a cabo, como esta (he intentado que sea genérica, así que simplemente 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 formas más Pythonesque de escribir esto, pero funciona para mí.
Yo personalmente prefiero usar lambdas
en ese escenario, porque 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 gusto.
Así es como lo harías con un lambda (ten en cuenta que también hay una cierta implementación de currying en el módulo funcional, para que puedas usar eso también):
button = Tk.Button(master=frame, text=''press'', command= lambda: action(someNumber))
button = Tk.Button(master=frame, text=''press'', command=lambda: action(someNumber))
Creo que debería arreglar esto