Làm cách nào để truyền đối số cho lệnh Nút trong Tkinter?


175

Giả sử tôi có những điều sau đây Buttonvới Tkinter trong 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)

Phương thức actionđược gọi khi tôi nhấn nút, nhưng nếu tôi muốn truyền một số đối số cho phương thứcaction thì sao?

Tôi đã thử với đoạn mã sau:

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

Điều này chỉ cần gọi phương thức ngay lập tức và nhấn nút không làm gì cả.


4
frame = Tk.Frame (master = win) .grid (row = 1, cột = 1) # Q. Giá trị của khung bây giờ là bao nhiêu?
noob oddy

Câu trả lời:


265

Cá nhân tôi thích sử dụng lambdastrong một kịch bản như vậy, bởi vì nó rõ ràng và đơn giản hơn và cũng không buộc bạn phải viết nhiều phương thức trình bao nếu bạn không kiểm soát được phương thức được gọi, nhưng đó chắc chắn là vấn đề của hương vị.

Đó là cách bạn làm điều đó với lambda (lưu ý rằng cũng có một số cách thực hiện currying trong mô-đun chức năng, vì vậy bạn cũng có thể sử dụng điều đó):

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

11
Bạn vẫn đang viết các phương thức trình bao bọc, bạn chỉ đang thực hiện nội tuyến.
agf

53
Điều này không có tác dụng nếu someNumber là trong thực tế một biến mà thay đổi các giá trị bên trong một vòng lặp tạo ra nhiều nút. Sau đó, mỗi nút sẽ gọi hành động () với giá trị cuối cùng đã được gán cho someNumber và không phải giá trị mà nó có khi nút được tạo. Các giải pháp sử dụng partialcông trình trong trường hợp này.
Scrontch

1
Nó hiệu quả tuyệt vời đối với tôi. Tuy nhiên, bạn cũng có thể giải thích tại sao tuyên bố của OP "This just invokes the method immediately, and pressing the button does nothing"xảy ra không?
Tommy

17
@Scrontch Tôi tự hỏi có bao nhiêu người dùng Tkinter mới làm quen chưa bao giờ cảm thấy trong cái bẫy mà bạn đề cập! Ở bất kỳ giá nào, người ta có thể nắm bắt giá trị hiện tại bằng cách sử dụng thành ngữ callback=lambda x=x: f(x)như trongfs = [lambda x=x: x*2 for x in range(5)] ; print [f() for f in fs]
gboffi

1
@Voo bạn có ý gì ở trên với "mặc dù những con trăn trường học cũ có thể sẽ dính vào sự gán tham số mặc định cho lambda"? Tôi đã không làm cho lambda để làm việc và do đó bây giờ sử dụng một phần.
Klamer Schutte

99

Đây cũng có thể được thực hiện bằng cách sử dụng partialtừ các thư viện chuẩn functools , như thế này:

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

33
Hoặc thậm chí ngắn hơn:button = Tk.Button(master=frame, text='press', command=partial(action, arg))
Klamer Schutte

Xin lỗi vì đã hoại tử, nhưng bạn sẽ làm thế nào trong trường hợp có hai hoặc nhiều đối số?
nghiền khoai tây

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

Tôi sẽ thực hiện phương pháp này vì lambda không hiệu quả với tôi.
Zaven Zareyan

19

GUI ví dụ:

Hãy nói rằng tôi có GUI:

import tkinter as tk

root = tk.Tk()

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

root.mainloop()

Điều gì xảy ra khi nhấn nút

Xem rằng khi btnđược nhấn, nó gọi hàm riêng của nó rất giống với button_press_handleví dụ sau:

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

với:

button_press_handle(btn['command'])

Bạn có thể nghĩ đơn giản rằng commandtùy chọn đó nên được đặt là, tham chiếu đến phương thức chúng ta muốn được gọi, tương tự như callbacktrong button_press_handle.


Gọi một phương thức ( Gọi lại ) Khi nhấn nút

Không cần tranh luận

Vì vậy, nếu tôi muốn printmột cái gì đó khi nhấn nút, tôi sẽ cần phải thiết lập:

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

Hãy chú ý đến các thiếu của ()với printphương pháp được bỏ qua trong ý nghĩa rằng: "Đây là tên của phương pháp mà tôi muốn bạn để gọi khi ép nhưng . Không gọi nó chỉ rất tức này" Tuy nhiên, tôi đã không vượt qua bất kỳ đối số nào printđể nó in bất cứ thứ gì nó in khi được gọi mà không có đối số.

Với (các) đối số

Bây giờ Nếu tôi cũng muốn truyền đối số cho phương thức tôi muốn được gọi khi nhấn nút, tôi có thể sử dụng các hàm ẩn danh, có thể được tạo bằng câu lệnh lambda , trong trường hợp này là printphương thức dựng sẵn, như sau :

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

Gọi nhiều phương thức khi nhấn nút

Không có đối số

Bạn cũng có thể đạt được điều đó bằng cách sử dụng lambdacâu lệnh nhưng nó được coi là thông lệ xấu và do đó tôi sẽ không đưa nó vào đây. Cách thực hành tốt là xác định một phương thức riêng biệt, multiple_methodsgọi các phương thức muốn và sau đó đặt nó làm phương thức gọi lại cho nút nhấn:

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

Với (các) đối số

Để truyền (các) đối số cho phương thức gọi các phương thức khác, một lần nữa sử dụng lambdacâu lệnh, nhưng trước tiên:

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

và sau đó đặt:

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

Đối tượng quay lại từ cuộc gọi lại

Cũng lưu ý thêm rằng callbackcó thể không thực sự returnvì nó chỉ được gọi là bên trong button_press_handlevới callback()như trái ngược với return callback(). Nó không returnnhưng không phải bất cứ nơi nào bên ngoài chức năng đó. Do đó, bạn nên sửa đổi (các) đối tượng có thể truy cập trong phạm vi hiện tại.


Ví dụ hoàn chỉnh với toàn cầu (các) Sửa đổi đối tượng

Ví dụ dưới đây sẽ gọi một phương thức thay đổi btnvăn bản mỗi lần nhấn nút:

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

Gương


10

Khả năng của Python để cung cấp các giá trị mặc định cho các đối số hàm cho chúng ta một lối thoát.

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

Xem: http://infohost.nmt.edu/tcc/help/pub/tkinter/web/extra-args.html

Đối với nhiều nút hơn, bạn có thể tạo một hàm trả về hàm:

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
Điều này không giải quyết được vấn đề. Điều gì sẽ xảy ra nếu bạn đang tạo ba nút mà tất cả đều gọi cùng một hàm nhưng cần truyền các đối số khác nhau?
Bryan Oakley

Bạn có thể tạo một hàm trả về một hàm.
MarrekNožka

Tôi biết điều này không còn hoạt động nữa nhưng tôi đã liên kết đến đây từ stackoverflow.com/questions/35616411/ , điều này hoạt động chính xác giống như sử dụng biểu thức lambda, bạn có thể xác định chức năng cho từng nút theo cách tương tự như khi thực hiện một biểu thức lambda cho mỗi nút.
Tadhg McDonald-Jensen

đưa ví dụ mã đầu tiên vào một vòng lặp liên tục thay đổi myXmyYhoạt động hoàn hảo cảm ơn bạn rất nhiều.
Tadhg McDonald-Jensen

3

Dựa trên câu trả lời của Matt Thompsons: một lớp có thể được gọi để có thể sử dụng nó thay vì hàm:

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

Lý do nó gọi phương thức ngay lập tức và nhấn nút không làm gì action(somenumber)được đánh giá và giá trị trả về của nó được quy là lệnh cho nút. Vì vậy, nếu actionin một cái gì đó để cho bạn biết nó đã chạy và trả về None, bạn chỉ cần chạy actionđể đánh giá giá trị trả về của nó và được đưa ra Nonelàm lệnh cho nút.

Để có các nút để gọi các hàm với các đối số khác nhau, bạn có thể sử dụng các biến toàn cục, mặc dù tôi không thể khuyên bạn nên:

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

Những gì tôi sẽ làm là làm cho một classđối tượng có các đối tượng sẽ chứa mọi biến cần thiết và các phương thức để thay đổi những đối tượng cần thiết:

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

Tôi tin rằng nên sửa nó


2

Điều tốt nhất để làm là sử dụng lambda như sau:

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

2

Tôi cực kỳ muộn, nhưng đây là một cách rất đơn giản để thực hiện nó.

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

Bạn chỉ cần bọc chức năng bạn muốn sử dụng trong một chức năng khác và gọi chức năng thứ hai trên nút nhấn.


Vì mã của bạn đã đặt var1var2trong mô-đun chính, bạn có thể bỏ qua function1tất cả và đưa printcâu lệnh vào function2. Bạn có đề cập đến các tình huống khác mà tôi không nhận được nó?
Brom

2

Lambdas đều tốt và tốt, nhưng bạn cũng có thể thử điều này (hoạt động trong vòng lặp 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)

Điều này hoạt động vì khi liên kết được đặt, một phím bấm sẽ chuyển sự kiện dưới dạng đối số. Sau đó, bạn có thể gọi các thuộc tính ra khỏi sự kiện như event.charnhận được "1" hoặc "LÊN" ect. Nếu bạn cần một đối số hoặc nhiều đối số khác với các thuộc tính sự kiện. chỉ cần tạo một từ điển để lưu trữ chúng.


2

Tôi đã gặp vấn đề này trước đây, quá. Bạn chỉ có thể sử dụng lambda:

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

1

Sử dụng lambda để truyền dữ liệu nhập vào chức năng lệnh nếu bạn có nhiều hành động để thực hiện, như thế này (Tôi đã cố gắng làm cho nó chung chung, vì vậy chỉ cần điều chỉnh):

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

Điều này sẽ chuyển thông tin trong sự kiện đến chức năng nút. Có thể có nhiều cách viết Pythonesque này, nhưng nó hiệu quả với tôi.


1

JasonPy - một vài điều ...

nếu bạn dán một nút trong một vòng lặp, nó sẽ được tạo ra nhiều lần ... đó có thể không phải là điều bạn muốn. (có thể nó là)...

Lý do nó luôn nhận được chỉ mục cuối cùng là các sự kiện lambda chạy khi bạn nhấp vào chúng - không phải khi chương trình bắt đầu. Tôi không chắc chắn 100% những gì bạn đang làm nhưng có thể thử lưu trữ giá trị khi nó được thực hiện sau đó gọi nó sau bằng nút lambda.

ví dụ: (không sử dụng mã này, chỉ là một ví dụ)

for entry in stuff_that_is_happening:
    value_store[entry] = stuff_that_is_happening

sau đó bạn có thể nói ....

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

hi vọng điêu nay co ich!


1

Một cách đơn giản sẽ được cấu hình buttonvới lambdanhư cú pháp như sau:

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

1

Đối với hậu thế: bạn cũng có thể sử dụng các lớp để đạt được một cái gì đó tương tự. Ví dụ:

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

Nút sau đó có thể được tạo đơn giản bằng cách:

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

Cách tiếp cận này cũng cho phép bạn thay đổi các đối số chức năng bằng cách cài đặt instance1.x = 3.


1

Bạn cần sử dụng lambda:

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

1

Sử dụng 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()

đầu ra:

hello
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.