Come passare argomenti a un comando Button in Tkinter?


176

Supponiamo che io abbia fatto quanto segue Buttoncon Tkinter in 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)

Il metodo actionviene chiamato quando premo il pulsante, ma se volessi passare alcuni argomenti al metodo action?

Ho provato con il seguente codice:

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

Questo invoca immediatamente il metodo e premere il pulsante non fa nulla.


4
frame = Tk.Frame (master = win) .grid (row = 1, column = 1) # Q. qual è il valore del frame ora?
Noob Oddy,

Risposte:


265

Personalmente preferisco usarlo lambdasin uno scenario del genere, perché imo è più chiaro e semplice e inoltre non ti costringe a scrivere molti metodi wrapper se non hai il controllo sul metodo chiamato, ma è sicuramente una questione di gusti.

Ecco come lo faresti con un lambda (nota che c'è anche una certa implementazione del curry nel modulo funzionale, quindi puoi usarlo anche tu):

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

11
Stai ancora scrivendo metodi wrapper, lo stai solo facendo in linea.
AGF

54
Questo non funziona se someNumber è in realtà una variabile che modifica i valori all'interno di un ciclo che crea molti pulsanti. Quindi ogni pulsante chiamerà action () con l'ultimo valore che è stato assegnato a someNumber e non il valore che aveva al momento della creazione del pulsante. La soluzione che utilizza partialfunziona in questo caso.
Scrontch,

1
Questo ha funzionato alla grande per me. Tuttavia, puoi anche spiegare perché la dichiarazione di OP è avvenuta "This just invokes the method immediately, and pressing the button does nothing"?
Tommy,

17
@Scrontch Mi chiedo quanti utenti principianti di Tkinter non abbiano mai sentito nella trappola che hai citato! Ad ogni modo si può catturare il valore corrente usando il linguaggio callback=lambda x=x: f(x)come infs = [lambda x=x: x*2 for x in range(5)] ; print [f() for f in fs]
gboffi

1
@Voo cosa vuoi dire sopra con "anche se i python della vecchia scuola probabilmente si atterranno all'assegnazione dei parametri di default per la lambda"? Non riuscivo a far funzionare lambda e quindi ora uso parzialmente.
Klamer Schutte,

99

Questo può essere fatto anche usando partialdalle funzioni della libreria standard , in questo modo:

from functools import partial
#(...)
action_with_arg = partial(action, arg)
button = Tk.Button(master=frame, text='press', command=action_with_arg)

33
O ancora più breve:button = Tk.Button(master=frame, text='press', command=partial(action, arg))
Klamer Schutte,

Ci scusiamo per il necro'ing, ma come faresti in caso di due o più argomenti?
mashedpotatoes,

5
action_with_args = partial(action, arg1, arg2... argN)
Dologan,

Seguirei questo approccio perché lambda non ha funzionato per me.
Zaven Zareyan,

19

Esempio di GUI:

Diciamo che ho la GUI:

import tkinter as tk

root = tk.Tk()

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

root.mainloop()

Cosa succede quando si preme un pulsante

Vedi che quando btnviene premuto chiama la sua stessa funzione che è molto simile al button_press_handleseguente esempio:

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'])

Puoi semplicemente pensare che l' commandopzione dovrebbe essere impostata come, il riferimento al metodo che vogliamo essere chiamato, simile a callbackin button_press_handle.


Richiamo di un metodo ( richiamata ) quando si preme il pulsante

Senza argomenti

Quindi, se volessi printqualcosa quando si preme il pulsante, avrei bisogno di impostare:

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

Prestare particolare attenzione alla mancanza di ()con il printmetodo che viene omesso nel senso che: "Questo è il nome del metodo che io voglio che chiami quando viene premuto , ma . Non chiamatelo solo questo istante" Tuttavia, non ho passato alcun argomento per il motivo per printcui ha stampato tutto ciò che stampa quando chiamato senza argomenti.

Con Argomento (i)

Ora, se volessi passare anche argomenti al metodo che voglio essere chiamato quando si preme il pulsante, potrei usare le funzioni anonime, che possono essere create con l' istruzione lambda , in questo caso per il printmetodo incorporato, come il seguente :

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

Chiamata a più metodi quando si preme il pulsante

Senza argomenti

Puoi anche ottenere questa lambdaaffermazione usando ma è considerata una cattiva pratica e quindi non la includerò qui. La buona pratica è definire un metodo separato multiple_methods, che chiama i metodi desiderati e quindi impostarlo come callback al pulsante premere:

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

Con Argomento (i)

Per passare l'argomento / i al metodo che chiama altri metodi, usa nuovamente l' lambdaaffermazione, ma prima:

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

e quindi impostare:

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

Restituzione di oggetti dal callback

Inoltre un'ulteriore nota che callbacknon si può davvero returnperché è chiamato solo all'interno button_press_handlecon callback()in contrasto return callback(). Lo fa returnma non al di fuori di quella funzione. Pertanto, è preferibile modificare gli oggetti che sono accessibili nell'ambito corrente.


Esempio completo con modifiche globali agli oggetti

L'esempio seguente chiamerà un metodo che cambia btnil testo ogni volta che si preme il pulsante:

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

Specchio


10

La capacità di Python di fornire valori predefiniti per gli argomenti delle funzioni ci dà una via d'uscita.

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

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

Per più pulsanti è possibile creare una funzione che restituisce una funzione:

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

4
Questo non risolve il problema. Cosa succede se si stanno creando tre pulsanti che chiamano tutti la stessa funzione ma devono passare argomenti diversi?
Bryan Oakley

È possibile creare una funzione che restituisce una funzione.
MarrekNožka,

So che questo non è più attivo, ma ho collegato qui da stackoverflow.com/questions/35616411/… , funziona esattamente allo stesso modo dell'uso delle espressioni lambda, puoi definire una funzione per ciascun pulsante nello stesso modo in cui un'espressione lambda per ogni pulsante.
Tadhg McDonald-Jensen,

mettendo il primo esempio di codice in un ciclo che continua a cambiare myXe myYfunziona perfettamente, grazie mille.
Tadhg McDonald-Jensen,

3

Basandosi sulla risposta di Matt Thompsons: una classe può essere richiamata in modo da poterla utilizzare al posto di una funzione:

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

2

Il motivo per cui richiama immediatamente il metodo e premendo il pulsante non fa nulla è che action(somenumber)viene valutato e il suo valore restituito viene attribuito come comando per il pulsante. Quindi, se actionstampa qualcosa per dirti che è stato eseguito e restituito None, esegui semplicemente actionper valutare il suo valore di ritorno e dato Nonecome comando per il pulsante.

Per avere pulsanti per chiamare funzioni con argomenti diversi puoi usare variabili globali, anche se non posso raccomandarlo:

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

Quello che vorrei fare è creare un classoggetto i cui oggetti contengano tutte le variabili richieste e i metodi per modificarli secondo necessità:

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

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

Credo che dovrebbe risolvere questo problema


2

La cosa migliore da fare è usare lambda come segue:

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

2

Sono estremamente in ritardo, ma ecco un modo molto semplice per realizzarlo.

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

È sufficiente avvolgere la funzione che si desidera utilizzare in un'altra funzione e chiamare la seconda funzione alla pressione del pulsante.


Come il tuo codice ha inserito var1e var2nel modulo principale, puoi saltare function1tutto e inserire la printdichiarazione function2. Ti riferisci ad altre situazioni che non capisco?
Brom,

2

Le lambda sono tutte buone e buone, ma puoi anche provare questo (che funziona in un ciclo 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)

Questo funziona perché quando viene impostata l'associazione, la pressione di un tasto passa l'evento come argomento. È quindi possibile richiamare gli attributi dall'evento come event.charottenere "1" o "UP" ect. Se è necessario un argomento o più argomenti diversi dagli attributi dell'evento. basta creare un dizionario per memorizzarli.


2

Ho già riscontrato questo problema prima. Puoi semplicemente usare lambda:

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

1

Usa un lambda per passare i dati di immissione alla funzione di comando se hai più azioni da eseguire, in questo modo (ho provato a renderlo generico, quindi basta adattare):

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

Questo passerà le informazioni nell'evento alla funzione pulsante. Potrebbero esserci più modi Pythonesque di scrivere questo, ma funziona per me.


1

JasonPy - alcune cose ...

se si inserisce un pulsante in un loop, questo verrà creato più e più e più volte ... che probabilmente non è quello che vuoi. (forse lo è)...

Il motivo per cui ottiene sempre l'ultimo indice sono gli eventi lambda eseguiti quando si fa clic su di essi, non all'avvio del programma. Non sono sicuro al 100% di quello che stai facendo, ma forse prova a memorizzare il valore quando viene creato, quindi chiamalo in seguito con il pulsante lambda.

es: (non usare questo codice, solo un esempio)

for entry in stuff_that_is_happening:
    value_store[entry] = stuff_that_is_happening

allora puoi dire ....

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

spero che questo ti aiuti!


1

Un modo semplice sarebbe configurare buttoncon lambdala seguente sintassi simile:

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

1

Per i posteri: puoi anche usare le lezioni per ottenere qualcosa di simile. Per esempio:

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

Il pulsante può quindi essere semplicemente creato da:

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

Questo approccio consente anche di modificare gli argomenti della funzione impostando ad es instance1.x = 3.


1

Devi usare lambda:

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

1

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

produzione:

hello
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.