Como passar argumentos para um comando Button no Tkinter?

176

Suponha que eu tenha Buttonfeito o seguinte com o Tkinter em 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)

O método actioné chamado quando pressiono o botão, mas e se eu quisesse passar alguns argumentos para o método action?

Eu tentei com o seguinte código:

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

Isso apenas invoca o método imediatamente e pressionar o botão não faz nada.

Jack
fonte
4
frame = Tk.Frame (master = win) .grid (linha = 1, coluna = 1) # Q. qual é o valor do quadro agora?
Nood oddy

Respostas:

265

Pessoalmente, prefiro usá lambdas-lo nesse cenário, porque é mais claro e simples e também não força você a escrever muitos métodos de invólucro, se você não tiver controle sobre o método chamado, mas isso certamente é uma questão de gosto.

É assim que você faria com um lambda (observe também que há alguma implementação de currying no módulo funcional, para que você possa usá-lo também):

button = Tk.Button(master=frame, text='press', command= lambda: action(someNumber))
Voo
fonte
11
Você ainda está escrevendo métodos de wrapper, apenas fazendo isso em linha.
agf
54
Isso não funciona se someNumber for de fato uma variável que altera valores dentro de um loop que cria muitos botões. Cada botão chamará action () com o último valor atribuído a someNumber e não o valor que tinha quando o botão foi criado. A solução usando partialfunciona neste caso.
Scrontch
1
Isso funcionou muito bem para mim. No entanto, você também pode explicar por que a declaração de OPs "This just invokes the method immediately, and pressing the button does nothing"acontece?
21414 Tommy
17
@ Scrontch Gostaria de saber quantos usuários iniciantes de Tkinter nunca sentiram na armadilha que você mencionou! De qualquer forma pode-se capturar o valor atual usando o idioma callback=lambda x=x: f(x)como emfs = [lambda x=x: x*2 for x in range(5)] ; print [f() for f in fs]
gboffi
1
@Voo, o que você quer dizer com "embora as pessoas python da velha escola provavelmente se atenham à atribuição de parâmetros padrão para o lambda"? Eu não consegui o lambda para trabalhar e, portanto, agora uso parcial.
Klamer Schutte
99

Isso também pode ser feito usando partialas funções da biblioteca padrão , desta forma:

from functools import partial
#(...)
action_with_arg = partial(action, arg)
button = Tk.Button(master=frame, text='press', command=action_with_arg)
Dologan
fonte
33
Ou ainda mais curto:button = Tk.Button(master=frame, text='press', command=partial(action, arg))
Klamer Schutte
Desculpe por necro'ing, mas como você faria isso no caso de dois ou mais argumentos?
Mashedpotatoes
5
action_with_args = partial(action, arg1, arg2... argN)
Dologan
Eu adotaria essa abordagem porque o lambda não funcionou para mim.
Zaven Zareyan
19

GUI de exemplo:

Digamos que eu tenho a GUI:

import tkinter as tk

root = tk.Tk()

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

root.mainloop()

O que acontece quando um botão é pressionado

Veja que quando btnpressionado, ele chama sua própria função, que é muito semelhante ao button_press_handleexemplo a seguir:

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

com:

button_press_handle(btn['command'])

Você pode simplesmente pensar que command opção deve ser definida como, a referência ao método que queremos ser chamado, semelhante a callbackin button_press_handle.


Chamando um método ( retorno de ) quando o botão é pressionado

Sem argumentos

Então, se eu quisesse print algo quando o botão for pressionado, seria necessário definir:

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

Preste muita atenção para a falta de ()com o printmétodo que é omitido no sentido de que: "Este é o nome do método que eu quero que você chame quando pressionado , mas . Não chamá-lo apenas neste instante"No entanto, eu não passei nenhum argumento para printque ele imprimisse o que imprimir quando chamado sem argumentos.

Com argumento (s)

Agora, se eu também quisesse passar argumentos para o método que gostaria de ser chamado quando o botão for pressionado, poderia usar as funções anônimas, que podem ser criadas com a instrução lambda , neste caso para o printmétodo interno, como o seguinte :

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

Chamando vários métodos quando o botão é pressionado

Sem argumentos

Você também pode conseguir isso usando a lambdadeclaração, mas é considerada uma prática ruim e, portanto, não a incluirei aqui. A boa prática é definir um método separado multiple_methods, que chame os métodos desejados e, em seguida, defina-o como retorno de chamada ao pressionar o botão:

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

Com argumento (s)

Para passar argumentos para métodos que chamam outros métodos, use novamente a lambdainstrução, mas primeiro:

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

e defina:

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

Retornando Objeto (s) do retorno de chamada

Além disso, observe que isso callbacknão pode realmente returnporque é chamado apenas por dentro button_press_handlee callback()não por return callback(). Faz, returnmas não em qualquer lugar fora dessa função. Portanto, você deve modificar os objetos acessíveis no escopo atual.


Exemplo completo com modificações globais de objetos

O exemplo abaixo chamará um método que altera btno texto de cada vez que o botão é pressionado:

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

Espelho

Nae
fonte
10

A capacidade do Python de fornecer valores padrão para argumentos de função nos dá uma saída.

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

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

Para mais botões, você pode criar uma função que retorna uma função:

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
fonte
4
Isso não resolve o problema. E se você estiver criando três botões que todos chamam a mesma função, mas precisam passar argumentos diferentes?
Bryan Oakley
Você pode criar uma função que retorna uma função.
MarrekNožka
Eu sei que isso não está mais ativo, mas vinculei aqui a partir de stackoverflow.com/questions/35616411/… , funciona exatamente da mesma maneira que o uso de expressões lambda, você pode definir uma função para cada botão da mesma maneira que cria uma expressão lambda para cada botão.
Tadhg McDonald-Jensen
colocando o primeiro exemplo de código em um loop que continua mudando myXe myYfunciona perfeitamente, muito obrigado.
Tadhg McDonald-Jensen
3

Com base na resposta de Matt Thompsons: uma classe pode ser tornada de chamar, para que possa ser usada em vez de uma função:

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

O motivo pelo qual invoca o método imediatamente e pressionar o botão não faz nada é o que action(somenumber)é avaliado e seu valor de retorno é atribuído como o comando do botão. Portanto, se actionimprimir algo para dizer que foi executado e retornado None, basta executar actionpara avaliar seu valor de retorno eNone o comando do botão.

Para ter botões para chamar funções com argumentos diferentes, você pode usar variáveis ​​globais, embora eu não possa recomendar:

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

O que eu faria é criar classobjetos cujos objetos conteriam todas as variáveis ​​necessárias e métodos para alterá-las conforme necessário:

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

Eu acredito que deveria consertar isso

Harrison Sills
fonte
2

A melhor coisa a fazer é usar o lambda da seguinte maneira:

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

Estou extremamente atrasado, mas aqui está uma maneira muito simples de conseguir isso.

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

Você simplesmente quebra a função que deseja usar em outra função e chama a segunda função ao pressionar o botão.

Rhett
fonte
Como seu código colocou var1e var2no módulo principal, você pode pular function1em tudo e colocar printdeclaração em function2. Você se refere a outras situações que eu não entendo?
Brom
2

Os lambdas estão todos muito bem, mas você também pode tentar isso (que funciona em um loop 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)

Isso funciona porque quando a ligação é definida, uma tecla pressionada passa o evento como argumento. Você pode chamar os atributos do evento como event.charse fossem "1" ou "UP". Se você precisar de um argumento ou vários argumentos além dos atributos do evento. basta criar um dicionário para armazená-los.

Bugbeeb
fonte
2

Também já encontrei esse problema antes. Você pode apenas usar lambda:

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

Use um lambda para passar os dados de entrada para a função de comando, se você tiver mais ações a serem executadas, como esta (tentei torná-lo genérico, basta adaptar):

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

Isso passará as informações do evento para a função do botão. Pode haver mais maneiras pitonesas de escrever isso, mas funciona para mim.

Jayce
fonte
1

JasonPy - algumas coisas ...

se você apertar um botão em um loop, ele será criado repetidas vezes ... o que provavelmente não é o que você deseja. (talvez seja) ...

O motivo pelo qual sempre obtém o último índice é que os eventos lambda são executados quando você clica neles - não quando o programa é iniciado. Não tenho certeza de 100% do que você está fazendo, mas talvez tente armazenar o valor quando for criado e depois chame-o mais tarde com o botão lambda.

por exemplo: (não use esse código, apenas um exemplo)

for entry in stuff_that_is_happening:
    value_store[entry] = stuff_that_is_happening

então você pode dizer ....

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

espero que isto ajude!

David W. Beck
fonte
1

Uma maneira simples seria configurar buttoncom lambdaa seguinte sintaxe:

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

Para a posteridade: você também pode usar classes para obter algo semelhante. Por exemplo:

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

O botão pode ser simplesmente criado por:

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

Essa abordagem também permite que você altere os argumentos da função definindo, por exemplo instance1.x = 3.

Matt Thompson
fonte
1

Você precisa usar lambda:

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

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

resultado:

hello
Giovanni G. PY
fonte