Jak przekazać argumenty do polecenia przycisku w Tkinter?

175

Załóżmy, że wykonałem następujące czynności Buttonz Tkinter w Pythonie:

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)

Metoda actionjest wywoływana po naciśnięciu przycisku, ale co jeśli chciałbym przekazać do niej jakieś argumenty action?

Próbowałem z następującym kodem:

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

Po prostu natychmiast wywołuje metodę, a naciśnięcie przycisku nic nie robi.

Jacek
źródło
4
frame = Tk.Frame (master = win) .grid (row = 1, column = 1) # Q. Jaka jest teraz wartość ramki?
noob oddy

Odpowiedzi:

265

Osobiście wolę używać lambdasw takim scenariuszu, ponieważ imo jest bardziej przejrzyste i prostsze, a także nie zmusza do pisania wielu metod opakowujących, jeśli nie masz kontroli nad wywołaną metodą, ale to z pewnością kwestia gustu.

Tak byś to zrobił z lambdą (zwróć uwagę, że w module funkcjonalnym jest też implementacja curry, więc możesz też tego użyć):

button = Tk.Button(master=frame, text='press', command= lambda: action(someNumber))
Voo
źródło
11
Nadal piszesz metody opakowujące, robisz to tylko w tekście.
agf
53
To nie działa, jeśli someNumber jest w rzeczywistości zmienną, która zmienia wartości wewnątrz pętli, która tworzy wiele przycisków. Następnie każdy przycisk wywoła akcję () z ostatnią wartością, która została przypisana do someNumber, a nie z wartością, jaką miał w momencie tworzenia przycisku. Rozwiązanie wykorzystujące partialdziała w tym przypadku.
Scrontch
1
To działało świetnie dla mnie. Czy możesz jednak również wyjaśnić, dlaczego ma miejsce oświadczenie dotyczące PO "This just invokes the method immediately, and pressing the button does nothing"?
Tommy,
17
@Scrontch Zastanawiam się, ilu początkujących użytkowników Tkintera nigdy nie wpadło w pułapkę, o której wspomniałeś! W każdym razie można uchwycić aktualną wartość używając idiomu callback=lambda x=x: f(x)jak wfs = [lambda x=x: x*2 for x in range(5)] ; print [f() for f in fs]
gboffi
1
@Voo co masz na myśli powyżej, mówiąc „chociaż ludzie starej szkoły Pythona prawdopodobnie będą trzymać się domyślnego przypisania parametrów dla lambdy”? Nie udało mi się uruchomić lambdy i dlatego teraz używam częściowej.
Klamer Schutte
99

Można to również zrobić za pomocą functoolspartial ze standardowej biblioteki , na przykład:

from functools import partial
#(...)
action_with_arg = partial(action, arg)
button = Tk.Button(master=frame, text='press', command=action_with_arg)
Dologan
źródło
33
Albo jeszcze krócej:button = Tk.Button(master=frame, text='press', command=partial(action, arg))
Klamer Schutte
Przepraszam za nekro, ale jak byś to zrobił w przypadku dwóch lub więcej kłótni?
puree ziemniaczane
5
action_with_args = partial(action, arg1, arg2... argN)
Dologan
Przyjąłbym takie podejście, ponieważ lambda nie działa dla mnie.
Zaven Zareyan
19

Przykład GUI:

Powiedzmy, że mam GUI:

import tkinter as tk

root = tk.Tk()

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

root.mainloop()

Co się dzieje po naciśnięciu przycisku

Zobacz, że po btnnaciśnięciu wywołuje własną funkcję, która jest bardzo podobna do button_press_handlew poniższym przykładzie:

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

z:

button_press_handle(btn['command'])

Możesz po prostu pomyśleć, że commandopcja powinna być ustawiona jako odwołanie do metody, którą chcemy wywołać, podobnie jak callbackw button_press_handle.


Wywołanie metody ( oddzwonienie ) po naciśnięciu przycisku

Bez argumentów

Więc gdybym chciał printcoś po wciśnięciu przycisku musiałbym ustawić:

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

Zwrócić szczególną uwagę na brak w ()z printmetody, która została pominięta w ten sposób, że: „Jest to metoda, której nazwa Chcę żebyś zadzwonił po naciśnięciu ale . Nie nazywają to po prostu to bardzo natychmiastowy” Jednak nie przekazałem żadnych argumentów, printwięc wydrukowało wszystko, co drukuje, gdy zostanie wywołane bez argumentów.

Z argumentami

Teraz Gdybym chciał również przekazać argumenty do metody, którą chcę wywołać po naciśnięciu przycisku, mógłbym skorzystać z funkcji anonimowych, które można utworzyć za pomocą wyrażenia lambda , w tym przypadku dla printmetody wbudowanej, jak poniżej :

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

Wywołanie wielu metod po naciśnięciu przycisku

Bez argumentów

Możesz to również osiągnąć za pomocą lambdastwierdzenia, ale jest to uważane za złą praktykę i dlatego nie będę go tutaj umieszczać. Dobrą praktyką jest zdefiniowanie osobnej metody, multiple_methodsktóra wywołuje żądane metody, a następnie ustawia ją jako wywołanie zwrotne do przycisku press:

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

Z argumentami

Aby przekazać argument (y) do metody, która wywołuje inne metody, ponownie użyj lambdainstrukcji, ale najpierw:

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

a następnie ustaw:

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

Zwracanie obiektów z wywołania zwrotnego

Ponadto zauważ, że callbacktak naprawdę nie może, returnponieważ jest wywoływany tylko wewnątrz button_press_handlez, callback()a nie z return callback(). Czyni returnale nie nigdzie poza tą funkcją. Dlatego powinieneś raczej modyfikować obiekty, które są dostępne w bieżącym zakresie.


Kompletny przykład z globalnymi modyfikacjami obiektów

Poniższy przykład wywoła metodę, która zmienia btntekst po każdym naciśnięciu przycisku:

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

Lustro

Nie
źródło
10

Zdolność Pythona do zapewnienia domyślnych wartości argumentów funkcji daje nam wyjście.

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

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

Aby uzyskać więcej przycisków, możesz utworzyć funkcję, która zwraca funkcję:

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
źródło
4
To nie rozwiązuje problemu. Co się stanie, jeśli tworzysz trzy przyciski, które wywołują tę samą funkcję, ale muszą przekazywać różne argumenty?
Bryan Oakley,
Możesz stworzyć funkcję, która zwraca funkcję.
MarrekNožka
Wiem, że to już nie jest aktywne, ale połączyłem się tutaj ze stackoverflow.com/questions/35616411/ ... , to działa dokładnie tak samo, jak przy użyciu wyrażeń lambda, możesz zdefiniować funkcję dla każdego przycisku w taki sam sposób jak tworzenie wyrażenie lambda dla każdego przycisku.
Tadhg McDonald-Jensen
umieszczenie pierwszego przykładu kodu w pętli, która ciągle się zmienia myXi myYdziała idealnie, bardzo dziękuję.
Tadhg McDonald-Jensen
3

Opierając się na odpowiedzi Matta Thompsona: klasa może być wywoływana, więc może być używana zamiast funkcji:

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
źródło
2

Powodem, dla którego wywołuje metodę natychmiast, a naciśnięcie przycisku nic nie robi, jest to, że action(somenumber)jest obliczana, a jej wartość zwracana jest przypisywana jako polecenie dla przycisku. Więc jeśli actiondrukuje coś, co mówi, że zostało uruchomione i wróci None, po prostu biegniesz, actionaby oszacować wartość zwracaną i podajesz Nonejako polecenie dla przycisku.

Aby mieć przyciski do wywoływania funkcji z różnymi argumentami, możesz użyć zmiennych globalnych, chociaż nie mogę tego polecić:

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

To, co zrobiłbym, to utworzyć obiekt, classktórego obiekty zawierałyby wszystkie wymagane zmienne i metody ich zmiany w razie potrzeby:

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
źródło
2
button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))

Myślę, że powinienem to naprawić

Harrison Sills
źródło
2

Najlepszą rzeczą do zrobienia jest użycie lambda w następujący sposób:

button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))
Dominika Thorpe
źródło
2

Jestem bardzo spóźniony, ale tutaj jest bardzo prosty sposób na osiągnięcie tego.

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

Po prostu zawijasz funkcję, której chcesz użyć, do innej funkcji i wywołujesz drugą funkcję po naciśnięciu przycisku.

Rhett
źródło
Jako kod postawił var1i var2modułu głównego, można pominąć function1w ogóle i umieścić printoświadczenie function2. Czy odnosisz się do innych sytuacji, których nie rozumiem?
Brom
2

Lambdy są dobre i dobre, ale możesz też spróbować tego (co przy okazji działa w pętli for):

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)

Działa to, ponieważ gdy powiązanie jest ustawione, naciśnięcie klawisza przekazuje zdarzenie jako argument. Możesz wtedy wywołać atrybuty zdarzenia, takie jak event.charuzyskanie „1” lub „W GÓRĘ” ect. Jeśli potrzebujesz argumentu lub wielu argumentów innych niż atrybuty zdarzenia. po prostu utwórz słownik, aby je przechowywać.

Bugbeeb
źródło
2

Z tym problemem też się spotkałem. Możesz po prostu użyć lambda:

button = Tk.Button(master=frame, text='press',command=lambda: action(someNumber))
Andrew Shi
źródło
1

Użyj lambdy, aby przekazać dane wejściowe do funkcji polecenia, jeśli masz więcej działań do wykonania, takich jak to (próbowałem uczynić to ogólnym, więc po prostu dostosuj):

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

Spowoduje to przekazanie informacji w zdarzeniu do funkcji przycisku. Może być więcej sposobów pisania tego w Pythonie, ale dla mnie to działa.

Jayce
źródło
1

JasonPy - kilka rzeczy ...

jeśli włożysz przycisk w pętlę, będzie on tworzony w kółko ... co prawdopodobnie nie jest tym, czego chcesz. (może to jest)...

Powodem, dla którego zawsze pobiera ostatni indeks, są zdarzenia lambda uruchamiane po ich kliknięciu, a nie po uruchomieniu programu. Nie jestem pewien w 100% tego, co robisz, ale może spróbuj zapisać wartość, gdy zostanie utworzona, a następnie wywołaj ją później przyciskiem lambda.

np .: (nie używaj tego kodu, tylko przykład)

for entry in stuff_that_is_happening:
    value_store[entry] = stuff_that_is_happening

wtedy możesz powiedzieć ....

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

mam nadzieję że to pomoże!

David W. Beck
źródło
1

Jednym prostym sposobem byłoby skonfigurowanie buttonza lambdapomocą następującej składni:

button['command'] = lambda arg1 = local_var1, arg2 = local_var2 : function(arg1, arg2)
Nie
źródło
1

Dla potomnych: możesz również użyć klas, aby osiągnąć coś podobnego. Na przykład:

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

Przycisk można następnie po prostu utworzyć za pomocą:

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

Takie podejście pozwala również na zmianę argumentów funkcji przez np instance1.x = 3. Ustawienie .

Matt Thompson
źródło
1

Musisz użyć lambda:

button = Tk.Button(master=frame, text='press', command=lambda: action(someNumber))
scruffyboy13
źródło
1

Użyj 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()

wynik:

hello
Giovanni G. PY
źródło