Bagaimana cara menyampaikan argumen ke perintah Button di Tkinter?

176

Misalkan saya Buttonmembuat yang berikut dengan Tkinter dalam 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)

Metode actiondipanggil ketika saya menekan tombol, tetapi bagaimana jika saya ingin memberikan beberapa argumen ke metode action?

Saya sudah mencoba dengan kode berikut:

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

Ini hanya memanggil metode segera, dan menekan tombol tidak melakukan apa-apa.

Mendongkrak
sumber
4
frame = Tk.Frame (master = win) .grid (baris = 1, kolom = 1) # Q. berapa nilai frame sekarang?
noob oddy

Jawaban:

265

Saya pribadi lebih suka menggunakan lambdasdalam skenario seperti itu, karena itu lebih jelas dan lebih sederhana dan juga tidak memaksa Anda untuk menulis banyak metode pembungkus jika Anda tidak memiliki kendali atas metode yang disebut, tetapi itu tentu saja masalah selera.

Begitulah cara Anda melakukannya dengan lambda (perhatikan ada juga beberapa implementasi currying pada modul fungsional, sehingga Anda dapat menggunakannya juga):

button = Tk.Button(master=frame, text='press', command= lambda: action(someNumber))
Voo
sumber
11
Anda masih menulis metode pembungkus, Anda hanya melakukannya sebaris.
agf
54
Ini tidak berfungsi jika someNumber sebenarnya adalah variabel yang mengubah nilai di dalam loop yang menciptakan banyak tombol. Kemudian setiap tombol akan memanggil action () dengan nilai terakhir yang telah ditetapkan untuk someNumber dan bukan nilai yang dimilikinya ketika tombol itu dibuat. Solusi menggunakan partialkarya dalam kasus ini.
Scrontch
1
Ini bekerja baik untuk saya. Namun, dapatkah Anda juga menjelaskan mengapa pernyataan OPs "This just invokes the method immediately, and pressing the button does nothing"terjadi?
Tommy
17
@Scrontch Saya ingin tahu berapa banyak pengguna Tkinter pemula yang tidak pernah merasakan dalam perangkap yang Anda sebutkan! Bagaimanapun orang dapat menangkap nilai saat ini menggunakan idiom callback=lambda x=x: f(x)sebagai infs = [lambda x=x: x*2 for x in range(5)] ; print [f() for f in fs]
gboffi
1
@ Oh, apa yang Anda maksud di atas dengan "meskipun orang python jadul mungkin akan tetap berpegang pada penetapan parameter default untuk lambda"? Saya tidak mendapatkan lambda untuk bekerja dan karenanya sekarang menggunakan sebagian.
Klamer Schutte
99

Ini juga dapat dilakukan dengan menggunakan partialdari functools perpustakaan standar , seperti ini:

from functools import partial
#(...)
action_with_arg = partial(action, arg)
button = Tk.Button(master=frame, text='press', command=action_with_arg)
Dologan
sumber
33
Atau bahkan lebih pendek:button = Tk.Button(master=frame, text='press', command=partial(action, arg))
Klamer Schutte
Maaf untuk necro'ing, tetapi bagaimana Anda melakukan ini dalam kasus dua atau lebih argumen?
mashedpotatoes
5
action_with_args = partial(action, arg1, arg2... argN)
Dologan
Saya akan mengambil pendekatan ini karena lambda tidak bekerja untuk saya.
Zaven Zareyan
19

Contoh GUI:

Katakanlah saya punya GUI:

import tkinter as tk

root = tk.Tk()

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

root.mainloop()

Apa Yang Terjadi Saat Tombol Ditekan

Lihat bahwa ketika btnditekan itu memanggil fungsinya sendiri yang sangat mirip dengan button_press_handledalam contoh berikut:

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

dengan:

button_press_handle(btn['command'])

Anda cukup berpikir bahwa commandopsi harus ditetapkan sebagai, referensi ke metode yang ingin kita panggil, mirip dengan callbackdi button_press_handle.


Memanggil Metode ( Callback ) Saat Tombol Ditekan

Tanpa argumen

Jadi jika saya ingin printsesuatu ketika tombol ditekan saya perlu mengatur:

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

Perhatikan dekat dengan kurangnya dari ()dengan printmetode yang dihilangkan dalam arti bahwa: "Ini adalah nama metode yang saya ingin Anda untuk panggilan ketika ditekan tapi . Tidak menyebutnya hanya sangat instan ini" Namun, saya tidak memberikan argumen untuk printitu sehingga mencetak apa pun yang dicetak ketika dipanggil tanpa argumen.

Dengan Argumen

Sekarang Jika saya ingin juga memberikan argumen pada metode yang ingin saya panggil ketika tombol ditekan, saya dapat menggunakan fungsi anonim, yang dapat dibuat dengan pernyataan lambda , dalam hal ini untuk printmetode bawaan, seperti berikut ini :

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

Memanggil Banyak Metode saat Tombol Ditekan

Tanpa Argumen

Anda juga dapat mencapai itu menggunakan lambdapernyataan tetapi dianggap praktik buruk dan karenanya saya tidak akan memasukkannya di sini. Praktik yang baik adalah mendefinisikan metode terpisah multiple_methods,, yang memanggil metode yang diinginkan dan kemudian menetapkannya sebagai panggilan balik ke tombol tekan:

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

Dengan Argumen

Untuk meneruskan argumen ke metode yang memanggil metode lain, sekali lagi gunakan lambdapernyataan, tetapi pertama-tama:

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

dan kemudian atur:

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

Mengembalikan Objek Dari Callback

Juga catatan lebih lanjut yang callbacktidak bisa benar-benar returnkarena itu hanya disebut button_press_handledengan yang callback()bertentangan return callback(). Itu returntetapi tidak di mana pun di luar fungsi itu. Dengan demikian, Anda sebaiknya memodifikasi objek yang dapat diakses dalam lingkup saat ini.


Contoh Lengkap dengan Modifikasi Objek global

Contoh di bawah ini akan memanggil metode yang mengubah btnteks setiap kali tombol ditekan:

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

Cermin

Tidak
sumber
10

Kemampuan Python untuk memberikan nilai default untuk argumen fungsi memberi kita jalan keluar.

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

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

Untuk lebih banyak tombol, Anda dapat membuat fungsi yang mengembalikan fungsi:

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
sumber
4
Ini tidak menyelesaikan masalah. Bagaimana jika Anda membuat tiga tombol yang semuanya memanggil fungsi yang sama tetapi perlu memberikan argumen yang berbeda?
Bryan Oakley
Anda dapat membuat fungsi yang mengembalikan fungsi.
MarrekNožka
Saya tahu ini tidak aktif lagi tetapi saya ditautkan ke sini dari stackoverflow.com/questions/35616411/... , ini berfungsi dengan cara yang sama persis seperti menggunakan ekspresi lambda, Anda dapat menentukan fungsi untuk setiap tombol dengan cara yang sama seperti membuat ekspresi lambda untuk setiap tombol.
Tadhg McDonald-Jensen
menempatkan contoh kode pertama dalam satu lingkaran yang terus berubah myXdan myYbekerja dengan sempurna terima kasih banyak.
Tadhg McDonald-Jensen
3

Membangun di atas Matt Thompsons menjawab: kelas dapat dibuat callable sehingga dapat digunakan sebagai pengganti fungsi:

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

Alasan itu memanggil metode segera dan menekan tombol tidak melakukan apa-apa adalah yang action(somenumber)dievaluasi dan nilai kembalinya dikaitkan sebagai perintah untuk tombol. Jadi jika actionmencetak sesuatu untuk memberi tahu Anda telah berjalan dan kembali None, Anda hanya menjalankan actionuntuk mengevaluasi nilai kembali dan diberikan Nonesebagai perintah untuk tombol.

Untuk memiliki tombol untuk memanggil fungsi dengan berbagai argumen, Anda dapat menggunakan variabel global, meskipun saya tidak bisa merekomendasikannya:

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

Apa yang akan saya lakukan adalah membuat classobjek yang berisi setiap variabel yang diperlukan dan metode untuk mengubahnya sesuai kebutuhan:

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

Saya percaya harus memperbaikinya

Harrison Sills
sumber
2

Hal terbaik untuk dilakukan adalah menggunakan lambda sebagai berikut:

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

Saya sangat terlambat, tetapi di sini ada cara yang sangat sederhana untuk mencapainya.

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

Anda cukup membungkus fungsi yang ingin Anda gunakan di fungsi lain dan memanggil fungsi kedua pada tombol tekan.

Rhett
sumber
Karena kode Anda telah dimasukkan var1dan var2di modul utama, Anda dapat melewati function1semuanya dan memasukkan printpernyataan function2. Apakah Anda merujuk pada situasi lain yang saya tidak mengerti?
Brom
2

Lambdas semuanya baik dan bagus, tetapi Anda juga dapat mencoba ini (yang berfungsi sebagai for loop 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)

Ini bekerja karena ketika pengikatan diatur, tekan tombol melewati acara sebagai argumen. Anda kemudian dapat memanggil atribut dari acara seperti event.charuntuk mendapatkan "1" atau "UP" dll. Jika Anda memerlukan argumen atau beberapa argumen selain atribut acara. cukup buat kamus untuk menyimpannya.

Bugbeeb
sumber
2

Saya pernah mengalami masalah ini sebelumnya. Anda bisa menggunakan lambda:

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

Gunakan lambda untuk meneruskan data entri ke fungsi perintah jika Anda memiliki lebih banyak tindakan untuk dilakukan, seperti ini (saya sudah mencoba membuatnya generik, jadi adaptasikan saja):

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

Ini akan meneruskan informasi dalam acara tersebut ke fungsi tombol. Mungkin ada lebih banyak cara Pythonesque untuk menulis ini, tetapi itu berhasil untuk saya.

Jayce
sumber
1

JasonPy - beberapa hal ...

jika Anda menempel tombol dalam satu lingkaran itu akan dibuat berulang-ulang ... yang mungkin bukan yang Anda inginkan. (mungkin ini)...

Alasan selalu mendapatkan indeks terakhir adalah acara lambda berjalan saat Anda mengkliknya - bukan saat program dimulai. Saya tidak yakin 100% apa yang Anda lakukan, tetapi mungkin mencoba menyimpan nilai ketika itu dibuat kemudian menyebutnya nanti dengan tombol lambda.

misalnya: (jangan gunakan kode ini, hanya sebuah contoh)

for entry in stuff_that_is_happening:
    value_store[entry] = stuff_that_is_happening

maka Anda bisa mengatakan ....

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

semoga ini membantu!

David W. Beck
sumber
1

Salah satu cara sederhana akan mengkonfigurasi buttondengan lambdaseperti sintaks berikut:

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

Untuk keturunan: Anda juga dapat menggunakan kelas untuk mencapai sesuatu yang serupa. Misalnya:

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

Tombol kemudian dapat dengan mudah dibuat oleh:

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

Pendekatan ini juga memungkinkan Anda untuk mengubah argumen fungsi dengan pengaturan yaitu instance1.x = 3.

Matt Thompson
sumber
1

Anda harus menggunakan lambda:

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

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

keluaran:

hello
Giovanni G. PY
sumber