¿Por qué el comando de mi botón se ejecuta inmediatamente cuando creo el botón y no cuando hago clic en él? [duplicar]

Resuelto salk asked hace 13 años • 0 respuestas

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.

salk avatar Apr 24 '11 04:04 salk
Aceptado

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 commandopció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 buttonfunción,
  • Puede utilizar lambdapara 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 al lambdacomando, devuelve una referencia a la función creada, lo que significa que se puede usar para el valor de la commandopción del botón.
  • Puedes usar functools.partial

Para mí, lambdaes el más simple ya que no requiere importaciones adicionales como functools.partiallo hace, aunque algunas personas piensan que functools.partiales más fácil de entender.

Para crear una función lambda que llame a su buttonfunció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, lambdadevuelve una referencia a esta función sin nombre. Dado que una referencia es lo que commandespera la opción, puedes usarla lambdadirectamente 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 lambdaes bastante escasa, pero la información que contiene aún puede ser útil.

Bryan Oakley avatar Apr 24 '2011 16:04 Bryan Oakley

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 btnse presiona llama a su propia función la cual es muy similar a la button_press_handledel 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 que se llame, similar a callbacken button_press_handle.


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

Sin argumentos

Entonces, si quisiera 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 del ()método printque 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 printque 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 printun 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 lambdauna 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_methodsque 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 lambdala 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 callbacken realidad no se puede returnporque solo se llama dentro button_press_handlecon 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 btnel 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

Nae avatar Dec 26 '2017 15:12 Nae

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()
C.Vergnaud avatar Oct 31 '2018 23:10 C.Vergnaud

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.

Deer Lawson avatar Aug 28 '2020 17:08 Deer Lawson