วิธีการส่งผ่านข้อโต้แย้งไปยังคำสั่งปุ่มใน Tkinter?


175

สมมติว่าฉันButtonทำต่อไปนี้กับ Tkinter ใน 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)

วิธีการที่actionเรียกว่าเมื่อฉันกดปุ่ม แต่สิ่งที่ถ้าผมต้องการที่จะผ่านการขัดแย้งบางอย่างเพื่อให้วิธีการaction?

ฉันได้ลองด้วยรหัสต่อไปนี้:

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

นี่เป็นการเรียกใช้เมธอดทันทีและการกดปุ่มก็ไม่ทำอะไรเลย


4
frame = Tk.Frame (master = win) .grid (แถว = 1, คอลัมน์ = 1) # Q. ตอนนี้มูลค่าของเฟรมคืออะไร?
noob oddy

คำตอบ:


265

ฉันเองชอบที่จะใช้lambdasในสถานการณ์เช่นนี้เพราะ imo มันชัดเจนและง่ายขึ้นและยังไม่บังคับให้คุณเขียนวิธีการห่อหุ้มถ้าคุณไม่สามารถควบคุมวิธีการที่เรียกว่า แต่นั่นเป็นเรื่องของรสนิยม

นั่นเป็นวิธีที่คุณจะทำกับแลมบ์ดา (โปรดทราบว่ายังมีการติดตั้งโมดูลการทำงานบางส่วนเพื่อให้คุณสามารถใช้งานได้เช่นกัน):

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

11
คุณยังคงเขียนวิธีการห่อหุ้มคุณเพียงทำแบบอินไลน์
agf

54
สิ่งนี้ใช้ไม่ได้หากsomeNumberเป็นตัวแปรที่เปลี่ยนแปลงค่าภายในลูปที่สร้างปุ่มจำนวนมาก จากนั้นแต่ละปุ่มจะเรียกการดำเนินการ () พร้อมค่าสุดท้ายที่กำหนดให้กับบางหมายเลขและไม่ใช่ค่าที่มีเมื่อสร้างปุ่มขึ้นมา การแก้ปัญหาโดยใช้partialงานในกรณีนี้
Scrontch

1
มันใช้งานได้ดีสำหรับฉัน อย่างไรก็ตามคุณสามารถอธิบายได้ว่าทำไมคำสั่ง OPs จึง"This just invokes the method immediately, and pressing the button does nothing"เกิดขึ้น
Tommy

17
@Scrontch ฉันสงสัยว่าผู้ใช้ Tkinter สามเณรไม่เคยรู้สึกกับดักที่คุณพูดถึง! ในทุก ๆ อัตราหนึ่งสามารถจับมูลค่าปัจจุบันโดยใช้สำนวนcallback=lambda x=x: f(x)เหมือนในfs = [lambda x=x: x*2 for x in range(5)] ; print [f() for f in fs]
gboffi

1
@Voo คุณหมายถึงอะไรข้างต้นกับ "แม้ว่าคนงูหลามในโรงเรียนเก่าอาจจะยึดติดกับการกำหนดค่าเริ่มต้นสำหรับแลมบ์ดา"? ฉันไม่ได้รับแลมบ์ดาดังนั้นตอนนี้จึงใช้งานบางส่วน
Klamer Schutte

99

สิ่งนี้สามารถทำได้โดยใช้partialจากฟังก์ชั่นไลบรารีมาตรฐานเช่นนี้

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

33
หรือสั้นกว่านี้:button = Tk.Button(master=frame, text='press', command=partial(action, arg))
Klamer Schutte

ขออภัยที่ไม่ทราบ แต่คุณจะทำอย่างไรในกรณีที่มีข้อโต้แย้งสองข้อขึ้นไป?
mashedpotatoes

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

ฉันจะใช้วิธีนี้เพราะแลมบ์ดาไม่ได้ผลสำหรับฉัน
Zaven Zareyan

19

ตัวอย่าง GUI:

สมมติว่าฉันมี GUI:

import tkinter as tk

root = tk.Tk()

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

root.mainloop()

เกิดอะไรขึ้นเมื่อกดปุ่ม

ดูว่าเมื่อbtnกดแล้วจะเรียกใช้ฟังก์ชันของตัวเองซึ่งคล้ายกับbutton_press_handleในตัวอย่างต่อไปนี้:

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

ด้วย:

button_press_handle(btn['command'])

คุณสามารถคิดว่าcommandตัวเลือกที่ควรจะตั้งเป็นอ้างอิงถึงวิธีการที่เราต้องการที่จะเรียกว่าคล้ายกับในcallbackbutton_press_handle


การเรียกใช้เมธอด ( โทรกลับ ) เมื่อกดปุ่ม

ไม่มีข้อโต้แย้ง

ดังนั้นหากฉันต้องการprintบางสิ่งเมื่อกดปุ่มฉันจะต้องตั้งค่า:

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

ให้ความสนใจใกล้เคียงกับการขาดของ()ด้วยprintวิธีการที่ถูกละไว้ในความหมายที่ว่า: "นี่คือชื่อของวิธีการที่ฉันต้องการให้คุณโทรเมื่อกดแต่ . ไม่ได้เรียกว่าเพียงแค่นี้มากทันที" อย่างไรก็ตามฉันไม่ได้ผ่านการขัดแย้งใด ๆ สำหรับprintมันจึงพิมพ์สิ่งที่มันพิมพ์เมื่อเรียกว่าไม่มีข้อโต้แย้ง

ด้วยอาร์กิวเมนต์

ตอนนี้ถ้าฉันต้องการส่งอาร์กิวเมนต์ไปยังวิธีที่ฉันต้องการให้เรียกเมื่อกดปุ่มฉันสามารถใช้ฟังก์ชันที่ไม่ระบุชื่อซึ่งสามารถสร้างด้วยคำสั่งlambdaในกรณีนี้สำหรับprintวิธีการในตัวเช่นต่อไปนี้ :

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

การเรียกใช้หลายวิธีเมื่อกดปุ่ม

ไม่มีข้อโต้แย้ง

คุณยังสามารถใช้lambdaคำสั่งนั้นได้ แต่ก็ถือว่าเป็นการฝึกฝนที่ไม่ดีดังนั้นฉันจะไม่รวมที่นี่ แนวปฏิบัติที่ดีคือการกำหนดวิธีการแยกต่างหากmultiple_methodsซึ่งเรียกวิธีการที่ต้องการแล้วตั้งค่าเป็นการเรียกกลับไปที่การกดปุ่ม:

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

ด้วยอาร์กิวเมนต์

ในการส่งผ่านอาร์กิวเมนต์ไปยังเมธอดที่เรียกใช้เมธอดอื่นให้ใช้lambdaคำสั่งอีกครั้ง แต่ก่อนอื่น:

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

จากนั้นตั้งค่า:

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

การส่งคืนวัตถุจากการติดต่อกลับ

นอกจากนี้ยังทราบต่อไปว่าcallbackไม่สามารถจริงๆreturnเพราะมันเป็นเพียงการเรียกภายในbutton_press_handleด้วยเมื่อเทียบกับcallback() return callback()มันทำreturnแต่ไม่ได้อยู่นอกฟังก์ชั่นนั้น ดังนั้นคุณควรแก้ไขวัตถุที่เข้าถึงได้ในขอบเขตปัจจุบัน


ทำตัวอย่างให้สมบูรณ์ด้วยการแก้ไขวัตถุระดับโลก

ตัวอย่างด้านล่างจะเรียกเมธอดที่เปลี่ยนbtnข้อความทุกครั้งที่กดปุ่ม:

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

กระจกเงา


10

ความสามารถของ Python ในการกำหนดค่าเริ่มต้นสำหรับอาร์กิวเมนต์ของฟังก์ชันทำให้เรามีทางออก

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

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

สำหรับปุ่มเพิ่มเติมคุณสามารถสร้างฟังก์ชั่นที่ส่งคืนฟังก์ชัน:

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
นี่ไม่ได้แก้ปัญหา ถ้าคุณกำลังสร้างปุ่มสามปุ่มที่ทุกคนเรียกใช้ฟังก์ชันเดียวกัน แต่ต้องผ่านการขัดแย้งต่างกัน
ไบรอัน Oakley

คุณสามารถสร้างฟังก์ชั่นซึ่งส่งกลับฟังก์ชั่น
MarrekNožka

ฉันรู้ว่านี่ไม่ได้ใช้งานอีกต่อไป แต่ฉันเชื่อมโยงกับที่นี่จากstackoverflow.com/questions/35616411/…วิธีนี้ทำงานเหมือนกับการใช้แลมบ์ดานิพจน์คุณสามารถกำหนดฟังก์ชั่นสำหรับปุ่มแต่ละปุ่มในลักษณะเดียวกับการสร้าง การแสดงออกแลมบ์ดาสำหรับแต่ละปุ่ม
Tadhg McDonald-Jensen

วางตัวอย่างโค้ดแรกในลูปที่เปลี่ยนแปลงตลอดเวลาmyXและmyYทำงานได้อย่างสมบูรณ์แบบขอบคุณมาก
Tadhg McDonald-Jensen

3

คำตอบสำหรับอาคารใน Thom Thompsons: สามารถสร้างคลาส callable เพื่อให้สามารถใช้แทนฟังก์ชัน:

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

เหตุผลที่เรียกใช้เมธอดทันทีและการกดปุ่มไม่ได้ทำสิ่งใดที่action(somenumber)ได้รับการประเมินและค่าส่งคืนจะถูกนำมาประกอบเป็นคำสั่งสำหรับปุ่ม ดังนั้นหากactionพิมพ์สิ่งที่จะบอกคุณว่ามันมีการรันและส่งคืนNoneคุณแค่รันactionเพื่อประเมินค่าที่ส่งคืนและกำหนดNoneเป็นคำสั่งสำหรับปุ่ม

หากต้องการมีปุ่มเพื่อเรียกใช้ฟังก์ชันที่มีอาร์กิวเมนต์ต่างกันคุณสามารถใช้ตัวแปรโกลบอลได้ แต่ฉันไม่สามารถแนะนำได้:

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

สิ่งที่ฉันจะทำคือการทำให้classวัตถุที่มีทุกตัวแปรที่จำเป็นต้องใช้และวิธีการในการเปลี่ยนสิ่งที่จำเป็น:

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

2

ฉันมาช้ามาก แต่นี่เป็นวิธีที่ง่ายที่สุดในการทำให้สำเร็จ

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

คุณเพียงแค่ห่อฟังก์ชั่นที่คุณต้องการใช้ในฟังก์ชั่นอื่นแล้วเรียกฟังก์ชั่นที่สองในการกดปุ่ม


ในฐานะที่เป็นรหัสของคุณได้วางvar1และvar2ในโมดูลหลักคุณสามารถข้ามfunction1เลยและใส่คำสั่งในprint function2คุณอ้างถึงสถานการณ์อื่นที่ฉันไม่เข้าใจใช่ไหม
Brom

2

แลมบ์ดาทำได้ดีและดี แต่คุณสามารถลองสิ่งนี้ได้ (ซึ่งใช้งานได้สำหรับลูป 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)

สิ่งนี้ทำงานได้เพราะเมื่อตั้งค่าการเชื่อมโยงการกดปุ่มจะส่งผ่านเหตุการณ์เป็นอาร์กิวเมนต์ จากนั้นคุณสามารถเรียกแอททริบิวออกจากกิจกรรมเช่นevent.charรับ "1" หรือ "UP" ect หากคุณต้องการอาร์กิวเมนต์หรือหลายอาร์กิวเมนต์นอกเหนือจากคุณสมบัติเหตุการณ์ เพียงสร้างพจนานุกรมเพื่อจัดเก็บ


2

ฉันเคยพบปัญหานี้มาก่อนเช่นกัน คุณสามารถใช้แลมบ์ดา:

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

1

ใช้แลมบ์ดาเพื่อส่งผ่านข้อมูลรายการไปยังฟังก์ชั่นคำสั่งหากคุณมีการดำเนินการเพิ่มเติมเช่นนี้ (ฉันได้พยายามทำให้เป็นแบบทั่วไปดังนั้นเพียงแค่ปรับ):

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

สิ่งนี้จะส่งผ่านข้อมูลในเหตุการณ์ไปยังฟังก์ชั่นปุ่ม อาจมีวิธีเขียนแบบ Pythonesque มากกว่านี้ แต่ใช้ได้สำหรับฉัน


1

JasonPy - บางสิ่ง ...

หากคุณติดปุ่มในวงมันจะถูกสร้างซ้ำแล้วซ้ำอีก ... ซึ่งอาจไม่ใช่สิ่งที่คุณต้องการ (อาจเป็น) ...

เหตุผลที่ทำให้ดัชนีล่าสุดนั้นเป็นเหตุการณ์แลมบ์ดาเมื่อคุณคลิกมันไม่ใช่เมื่อโปรแกรมเริ่มทำงาน ฉันไม่แน่ใจ 100% สิ่งที่คุณกำลังทำ แต่อาจลองเก็บค่าเมื่อทำแล้วโทรในภายหลังด้วยปุ่มแลมบ์ดา

เช่น: (อย่าใช้รหัสนี้เป็นเพียงตัวอย่าง)

for entry in stuff_that_is_happening:
    value_store[entry] = stuff_that_is_happening

จากนั้นคุณสามารถพูดได้ ....

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

หวังว่านี่จะช่วยได้!


1

วิธีง่ายๆวิธีหนึ่งคือการกำหนดค่าbuttonด้วยlambdaเช่นไวยากรณ์ต่อไปนี้:

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

1

สำหรับลูกหลาน: คุณยังสามารถใช้คลาสเพื่อให้ได้สิ่งที่คล้ายกัน ตัวอย่างเช่น

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

ปุ่มนั้นสามารถสร้างโดย:

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

instance1.x = 3วิธีนี้ยังช่วยให้คุณสามารถเปลี่ยนการขัดแย้งการทำงานโดยการตั้งค่าเช่น



1

ใช้แลมบ์ดา

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

เอาท์พุท:

hello
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.