Wie übergebe ich Argumente an einen Button-Befehl in Tkinter?

175

Angenommen, ich habe Folgendes Buttonmit Tkinter in Python erstellt:

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)

Die Methode actionwird aufgerufen, wenn ich die Taste drücke. Was ist, wenn ich der Methode einige Argumente übergeben möchte action?

Ich habe es mit folgendem Code versucht:

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

Dies ruft die Methode sofort auf und das Drücken der Taste bewirkt nichts.

Jack
quelle
4
frame = Tk.Frame (master = win) .grid (row = 1, column = 1) # Q. Was ist der Wert von frame jetzt?
Noob Oddy

Antworten:

265

Ich persönlich bevorzuge die Verwendung lambdasin einem solchen Szenario, da es imo klarer und einfacher ist und Sie auch nicht zwingt, viele Wrapper-Methoden zu schreiben, wenn Sie nicht die Kontrolle über die aufgerufene Methode haben, aber das ist sicherlich Geschmackssache.

So würden Sie es mit einem Lambda machen (beachten Sie, dass es auch eine Implementierung von Currying im Funktionsmodul gibt, damit Sie das auch verwenden können):

button = Tk.Button(master=frame, text='press', command= lambda: action(someNumber))
Voo
quelle
11
Sie schreiben immer noch Wrapper-Methoden, Sie tun es nur inline.
Agf
53
Dies funktioniert nicht, wenn someNumber tatsächlich eine Variable ist, die Werte innerhalb einer Schleife ändert, die viele Schaltflächen erstellt. Dann ruft jede Schaltfläche action () mit dem letzten Wert auf, der someNumber zugewiesen wurde, und nicht mit dem Wert, den sie beim Erstellen der Schaltfläche hatte. Die Lösung mit partialfunktioniert in diesem Fall.
Scrontch
1
Das hat bei mir super funktioniert. Können Sie jedoch auch erklären, warum die OP-Erklärung von "This just invokes the method immediately, and pressing the button does nothing"passiert?
Tommy
17
@Scrontch Ich frage mich, wie viele unerfahrene Tkinter-Benutzer sich nie in der von Ihnen erwähnten Falle gefühlt haben! Auf jeden Fall kann man erfassen den aktuellen Wert des Idiom verwenden , callback=lambda x=x: f(x)wie infs = [lambda x=x: x*2 for x in range(5)] ; print [f() for f in fs]
gboffi
1
@Voo was meinst du oben mit "obwohl Python-Leute der alten Schule wahrscheinlich bei der Standardparameterzuweisung für das Lambda bleiben werden"? Ich habe Lambda nicht zum Arbeiten gebracht und benutze daher jetzt teilweise.
Klamer Schutte
99

Dies kann auch mithilfe partialder folgenden Funktionsfunktionen der Standardbibliothek erfolgen :

from functools import partial
#(...)
action_with_arg = partial(action, arg)
button = Tk.Button(master=frame, text='press', command=action_with_arg)
Dologan
quelle
33
Oder noch kürzer:button = Tk.Button(master=frame, text='press', command=partial(action, arg))
Klamer Schutte
Entschuldigung für die Nekro'ing, aber wie würden Sie dies bei zwei oder mehr Argumenten tun?
Kartoffelpüree
5
action_with_args = partial(action, arg1, arg2... argN)
Dologan
Ich würde diesen Ansatz wählen, weil Lambda bei mir nicht funktioniert hat.
Zaven Zareyan
19

Beispiel GUI:

Angenommen, ich habe die GUI:

import tkinter as tk

root = tk.Tk()

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

root.mainloop()

Was passiert, wenn eine Taste gedrückt wird?

Beachten Sie, dass beim btnDrücken eine eigene Funktion aufgerufen wird, die der button_press_handleim folgenden Beispiel sehr ähnlich ist :

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

mit:

button_press_handle(btn['command'])

Sie können sich einfach vorstellen, dass diese commandOption als Verweis auf die Methode festgelegt werden sollte, die aufgerufen werden soll, ähnlich wie callbackin button_press_handle.


Aufrufen einer Methode ( Rückruf ) Wenn die Taste gedrückt wird

Ohne Argumente

Wenn ich also printetwas wollte, wenn die Taste gedrückt wird, müsste ich Folgendes einstellen:

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

Achten Sie auf das Fehlen von ()mit dem printVerfahren , das im Sinne weggelassen wird , dass: „Das ist der Name der Methode, die ich Sie wollen , wenn sie gedrückt nennen , aber . Sie es nicht nur diesem Augenblick nennen“ Ich habe jedoch keine Argumente für das übergeben, printsodass gedruckt wurde, was auch immer gedruckt wird, wenn es ohne Argumente aufgerufen wird.

Mit Argument (en)

Wenn ich nun auch Argumente an die Methode übergeben möchte, die beim Drücken der Schaltfläche aufgerufen werden soll, kann ich die anonymen Funktionen verwenden, die mit der Lambda- Anweisung erstellt werden können, in diesem Fall für die printintegrierte Methode wie die folgende ::

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

Aufrufen mehrerer Methoden, wenn die Taste gedrückt wird

Ohne Argumente

Sie können dies auch mit der lambdaAnweisung erreichen, aber es wird als schlechte Praxis angesehen, und daher werde ich es hier nicht aufnehmen. Es empfiehlt sich, eine separate Methode zu definieren multiple_methods, die die gewünschten Methoden aufruft und diese dann als Rückruf für den Tastendruck festlegt:

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

Mit Argument (en)

Um Argumente an Methoden zu übergeben, die andere Methoden aufrufen, verwenden Sie erneut die lambdaAnweisung, aber zuerst:

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

und dann setzen:

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

Rückgabe von Objekten aus dem Rückruf

Beachten Sie auch, dass callbackdies nicht wirklich möglich ist, returnda es nur button_press_handlemit callback()im Gegensatz zu aufgerufen wird return callback(). Es tut returnaber nicht überall außerhalb dieser Funktion. Daher sollten Sie lieber Objekte ändern , auf die im aktuellen Bereich zugegriffen werden kann.


Vollständiges Beispiel mit globalen Objektmodifikationen.

Das folgende Beispiel ruft eine Methode auf, die btnden Text jedes Mal ändert , wenn die Taste gedrückt wird:

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

Spiegel

Nae
quelle
10

Die Fähigkeit von Python, Standardwerte für Funktionsargumente bereitzustellen, gibt uns einen Ausweg.

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

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

Für weitere Schaltflächen können Sie eine Funktion erstellen, die eine Funktion zurückgibt:

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
quelle
4
Dies löst das Problem nicht. Was ist, wenn Sie drei Schaltflächen erstellen, die alle dieselbe Funktion aufrufen, aber unterschiedliche Argumente übergeben müssen?
Bryan Oakley
Sie können eine Funktion erstellen, die eine Funktion zurückgibt.
MarrekNožka
Ich weiß, dass dies nicht mehr aktiv ist, aber ich habe hier von stackoverflow.com/questions/35616411/… einen Link erstellt . Dies funktioniert genauso wie die Verwendung von Lambda-Ausdrücken. Sie können eine Funktion für jede Schaltfläche auf die gleiche Weise wie beim Erstellen definieren ein Lambda-Ausdruck für jede Taste.
Tadhg McDonald-Jensen
Setzen Sie das erste Codebeispiel in eine Schleife, die sich ständig ändert myX und myYperfekt funktioniert. Vielen Dank.
Tadhg McDonald-Jensen
3

Aufbauend auf Matt Thompsons Antwort: Eine Klasse kann aufrufbar gemacht werden, sodass sie anstelle einer Funktion verwendet werden kann:

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

Der Grund, warum die Methode sofort aufgerufen wird und das Drücken der Taste nichts bewirkt, ist der folgende action(somenumber) ausgewertet wird und ihr Rückgabewert als Befehl für die Schaltfläche zugewiesen wird. Wenn Sie also actionetwas drucken, das Ihnen mitteilt, dass es ausgeführt wurde und zurückkehrt None, führen Sie es einfach aus action, um den Rückgabewert auszuwerten, und geben Sie es Noneals Befehl für die Schaltfläche an.

Um Schaltflächen zum Aufrufen von Funktionen mit unterschiedlichen Argumenten zu haben, können Sie globale Variablen verwenden, obwohl ich es nicht empfehlen kann:

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

Was ich tun würde, ist ein classObjekt zu erstellen, dessen Objekte alle erforderlichen Variablen und Methoden enthalten, um diese nach Bedarf zu ändern:

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

Ich glaube, das sollte behoben werden

Harrison Sills
quelle
2

Am besten verwenden Sie Lambda wie folgt:

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

Ich bin sehr spät dran, aber hier ist eine sehr einfache Möglichkeit, dies zu erreichen.

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

Sie verpacken einfach die Funktion, die Sie verwenden möchten, in eine andere Funktion und rufen die zweite Funktion beim Drücken der Taste auf.

Rhett
quelle
Als Ihr Code setzen var1und var2Hauptmodul in, können Sie überspringen function1überhaupt und setzen printin der Aussage function2. Beziehen Sie sich auf andere Situationen, die ich nicht verstehe?
Brom
2

Lambdas sind alle gut und schön, aber Sie können dies auch versuchen (was übrigens in einer for-Schleife funktioniert):

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)

Dies funktioniert, da beim Festlegen der Bindung ein Tastendruck das Ereignis als Argument übergibt. Sie können dann Attribute außerhalb des Ereignisses aufrufen event.char, um "1" oder "UP" zu erhalten. Wenn Sie ein Argument oder mehrere andere Argumente als die Ereignisattribute benötigen. Erstellen Sie einfach ein Wörterbuch, um sie zu speichern.

Bugbeeb
quelle
2

Ich bin auch schon einmal auf dieses Problem gestoßen. Sie können nur Lambda verwenden:

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

Verwenden Sie ein Lambda, um die Eingabedaten an die Befehlsfunktion zu übergeben, wenn Sie weitere Aktionen ausführen müssen (ich habe versucht, sie generisch zu machen, also passen Sie sie einfach an):

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

Dadurch werden die Informationen im Ereignis an die Tastenfunktion übergeben. Es gibt vielleicht mehr pythoneske Schreibweisen, aber es funktioniert für mich.

Jayce
quelle
1

JasonPy - ein paar Dinge ...

Wenn Sie einen Knopf in eine Schleife stecken, wird er immer und immer wieder erstellt ... was wahrscheinlich nicht das ist, was Sie wollen. (vielleicht ist es)...

Der Grund, warum immer der letzte Index abgerufen wird, sind Lambda-Ereignisse, die ausgeführt werden, wenn Sie darauf klicken - nicht, wenn das Programm gestartet wird. Ich bin mir nicht 100% sicher, was Sie tun, aber versuchen Sie vielleicht, den Wert zu speichern, wenn er erstellt wurde, und rufen Sie ihn später mit der Lambda-Taste auf.

zB: (benutze diesen Code nicht, nur ein Beispiel)

for entry in stuff_that_is_happening:
    value_store[entry] = stuff_that_is_happening

dann kann man sagen ....

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

hoffe das hilft!

David W. Beck
quelle
1

Eine einfache Möglichkeit wäre die Konfiguration buttonmit lambdader folgenden Syntax:

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

Für die Nachwelt: Sie können auch Klassen verwenden, um etwas Ähnliches zu erreichen. Zum Beispiel:

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

Die Schaltfläche kann dann einfach erstellt werden durch:

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

Mit diesem Ansatz können Sie auch die Funktionsargumente ändern, indem Sie sie einstellen instance1.x = 3.

Matt Thompson
quelle
1

Sie müssen verwenden lambda:

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

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

Ausgabe:

hello
Giovanni G. PY
quelle