¿Por qué el comando de mi botón se ejecuta inmediatamente cuando creo el botón y no cuando hago clic en él? [duplicar]
Mi código es:
from Tkinter import *
admin = Tk()
def button(an):
print(an)
print('het')
b = Button(admin, text='as', command=button('hey'))
b.pack()
mainloop()
El botón no funciona, imprime 'hey' y 'het' una vez sin mi comando y luego, cuando presiono el botón, no pasa nada.
Considere este código:
b = Button(admin, text='as', command=button('hey'))
Hace exactamente lo mismo que esto:
result = button('hey')
b = button(admin, text='as', command=result)
Del mismo modo, si crea un enlace como este:
listbox.bind("<<ListboxSelect>>", some_function())
... es lo mismo que esto:
result = some_function()
listbox.bind("<<ListboxSelect>>", result)
La command
opción toma una referencia a una función, que es una forma elegante de decir que debes pasarle el nombre de la función. Para pasar una referencia debes usar solo el nombre, sin usar paréntesis ni argumentos. Por ejemplo:
b = Button(... command = button)
Si desea pasar un parámetro como "hey", debe usar un pequeño código adicional:
- Puede crear una función intermedia que se pueda llamar sin su argumento y que luego llame a su
button
función, - Puede utilizar
lambda
para crear lo que se conoce como función anónima . En todos los sentidos es una función excepto que no tiene nombre. Cuando llama allambda
comando, devuelve una referencia a la función creada, lo que significa que se puede usar para el valor de lacommand
opción del botón. - Puedes usar functools.partial
Para mí, lambda
es el más simple ya que no requiere importaciones adicionales como functools.partial
lo hace, aunque algunas personas piensan que functools.partial
es más fácil de entender.
Para crear una función lambda que llame a su button
función con un argumento, haría algo como esto:
lambda: button('hey')
Terminas con una función que es funcionalmente equivalente a:
def some_name():
return button('hey')
Como dije antes, lambda
devuelve una referencia a esta función sin nombre. Dado que una referencia es lo que command
espera la opción, puedes usarla lambda
directamente en la creación del botón:
b = Button(... command = lambda: button('hey'))
Hay una pregunta en este sitio que tiene muchos comentarios interesantes sobre lambda, en general. Vea la pregunta ¿Por qué son útiles las lambdas de Python? . Esa misma discusión tiene una respuesta que muestra cómo usar lambdas en un bucle cuando necesitas pasar una variable a la devolución de llamada.
Finalmente, consulte el artículo de Zone.effbot.org titulado Tkinter Callbacks para obtener un buen tutorial. La cobertura lambda
es bastante escasa, pero la información que contiene aún puede ser útil.
Ejemplo de interfaz gráfica de usuario:
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?
Mira que cuando btn
se presiona llama a su propia función la cual es muy similar a la button_press_handle
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 command
opción debe establecerse como la referencia al método que queremos que se llame, similar a callback
en button_press_handle
.
Llamar a un método (una devolución de llamada ) cuando se presiona el botón
Sin argumentos
Entonces, si quisiera print
algo 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 del ()
método print
que se omite en el sentido de que: "Este es el nombre del método que quiero que llame cuando lo presione, pero no lo llame solo en este mismo instante". Sin embargo, no pasé ningún argumento para print
que imprimiera lo que imprimiera cuando se llama 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 una declaración lambda , en este caso para print
un método integrado, 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 puedes lograrlo usando lambda
una declaració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_methods
que llame a los métodos deseados y luego lo configure como 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, utilice nuevamente lambda
la declaración, pero primero:
def multiple_methods(*args, **kwargs):
print(args[0]) # the first inner callback
print(kwargs['opt1']) # another inner callback
y luego establece:
btn['command'] = lambda arg="live", kw="as the" : a_new_method(arg, opt1=kw)
Devolver objetos de la devolución de llamada
También tenga en cuenta que callback
en realidad no se puede return
porque solo se llama dentro button_press_handle
con callback()
en lugar de return callback()
. Lo hace return
, pero no en ningún otro 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 globales de objetos
El siguiente ejemplo llamará a un método que cambia btn
el texto 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
El motor evalúa el resultado de la función cuando está asignando el valor en la línea "...command=..."
El "comando" espera que se devuelva una función, es por eso que usar una lambda puede hacer el trabajo porque está creando una función anónima que se devuelve al "comando" durante la evaluación. También puedes codificar tu propia función, también hará el trabajo.
este es un ejemplo con lambda y sin lambda:
#!/usr/bin/python
# coding=utf-8
from Tkinter import *
# Creation de la fenêtre principale (main window)
Mafenetre = Tk()
res1 = StringVar()
res2 = StringVar()
def isValidInput(obj):
if hasattr(obj, 'get') and callable(getattr(obj, 'get')):
return TRUE
return FALSE
# stupid action 2 (return 12 on purpose to show potential mistake)
def action1(*arguments):
print "action1 running"
for arg in arguments:
if isValidInput(arg):
print "input value: ", arg.get()
res1.set(arg.get())
else:
print "other value:", arg
print "\n"
return 12
# stupid action 2
def action2(*arguments):
print "action2 running"
a = arguments[0]
b = arguments[1]
if isValidInput(a) and isValidInput(b):
c = a.get() + b.get()
res2.set(c)
print c
print "\n"
# a stupid workflow manager ordered by name
def start_tasks(*arguments, **keywords):
keys = sorted(keywords.keys())
for kw in keys:
print kw, "plugged "
keywords[kw](*arguments)
# valid callback wrapper with lambda
def action1_callback(my_input):
return lambda args=[my_input]: action1(*args)
# valid callback wrapper without lambda
def action1_callback_nolambda(*args, **kw):
def anon():
action1(*args)
return anon
# first input string
input1 = StringVar()
input1.set("delete me...")
f1 = Entry(Mafenetre, textvariable=input1, bg='bisque', fg='maroon')
f1.focus_set()
f1.pack(fill="both", expand="yes", padx="5", pady=5)
# failed callback because the action1 function is evaluated, it will return 12.
# in this case the button won't work at all, because the assignement expect a function
# in order to have the button command to execute something
ba1 = Button(Mafenetre)
ba1['text'] = "show input 1 (ko)"
ba1['command'] = action1(input1)
ba1.pack(fill="both", expand="yes", padx="5", pady=5)
# working button using a wrapper
ba3 = Button(Mafenetre)
ba3['text'] = "show input 1 (ok)"
# without a lambda it is also working if the assignment is a function
#ba1['command'] = action1_callback_nolambda(input1)
ba3['command'] = action1_callback(input1)
ba3.pack(fill="both", expand="yes", padx="5", pady=5)
# display result label
Label1 = Label(Mafenetre, text="Action 1 result:")
Label1.pack(fill="both", expand="yes", padx="5", pady=5)
# display result value
resl1 = Label(Mafenetre, textvariable=res1)
resl1.pack(fill="both", expand="yes", padx="5", pady=5)
# second input string
input2 = StringVar()
f2 = Entry(Mafenetre, textvariable=input2, bg='bisque', fg='maroon')
f2.focus_set()
f2.pack(fill="both", expand="yes", padx="5", pady=5)
# third test without wrapper, but making sure that several arguments are well handled by a lambda function
ba2 = Button(Mafenetre)
ba2['text'] = "execute 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)
# display result label
Label2 = Label(Mafenetre, text="Action 2 result:")
Label2.pack(fill="both", expand="yes", padx="5", pady=5)
# display result value
resl2 = Label(Mafenetre, textvariable=res2)
resl2.pack(fill="both", expand="yes", padx="5", pady=5)
Mafenetre.mainloop()
Creo que la mejor manera de resolver este problema es utilizar una función 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 no desea utilizar la palabra clave comando, puede utilizar el método .bind() en su lugar:
from tkinter import *
admin= Tk()
def button(an):
print(an)
print("het")
b = Button(admin, text="as")
b.pack()
b.bind("<Button-1>", lambda bb: button("hey"))
mainloop()
Usar una función madre (sin parámetro) que posee la función secundaria (al menos 1 parámetro) que desea llamar es una estupidez.
Sólo para compartir con ustedes, este es uno de mis programas:
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("Exception occurs")
else:
print("Continue")
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("Exception occurs")
else:
print("Continue")
result = num1 - num2
field_3.insert(tkinter.END, str(result))
return result
#Input Panel:
label_1 = tkinter.Label(window, text="First Number:")
label_1.grid(row=0, column=0)
label_2 = tkinter.Label(window, text="Second Number:")
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)
#Button Panel:
button_1 = tkinter.Button(window, text="Plus")
button_1.grid(row=2, column=0)
button_2 = tkinter.Button(window, text="Minus")
button_2.grid(row=2, column=1)
#Answer Panel:
label_3 = tkinter.Label(window, text="The Answer:")
label_3.grid(row=3, column=0)
entry_3 = tkinter.Entry(window)
entry_3.grid(row=3, column=1)
#Event Handling:
button_1.bind("<Button-1>", lambda p: plus_them(entry_1, entry_2, entry_3))
button_2.bind("<Button-1>", lambda m: minus_them(entry_1, entry_2, entry_3))
#Window Stuff:
window.title("Plus and Minus Calculator")
window.mainloop()
Eso es todo.