Как передать аргументы команде Button в Tkinter?


176

Предположим, я 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 (row = 1, column = 1) # В. Каково значение сейчас frame?
Нуб Одди

Ответы:


265

Лично я предпочитаю использовать lambdasв таком сценарии, потому что, по моему мнению , он понятнее и проще, а также не заставляет вас писать множество методов-обёрток, если у вас нет контроля над вызываемым методом, но это, безусловно, дело вкуса.

Вот как вы бы сделали это с лямбдой (обратите внимание, что в функциональном модуле также есть некоторая реализация каррирования), так что вы также можете использовать это):

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

11
Вы все еще пишете методы-обертки, вы просто делаете это встроенным.
AGF

54
Это не работает, если someNumber на самом деле является переменной, которая изменяет значения внутри цикла, который создает много кнопок. Затем каждая кнопка будет вызывать action () с последним значением, которое было присвоено someNumber, а не со значением, которое было при создании кнопки. Решение с использованием partialработает в этом случае.
Scrontch

1
Это отлично сработало для меня. Однако не могли бы вы также объяснить, почему происходит утверждение ОП "This just invokes the method immediately, and pressing the button does nothing"?
Томми

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
@Ву, что вы имеете в виду выше, когда «хотя питон старой школы, вероятно, будет придерживаться назначения параметров по умолчанию для лямбды»? Я не заставил лямбду работать, и поэтому теперь использую частичную.
Кламер Шутте

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))
Кламер Шутте

Извините за necro'ing, но как бы вы сделали это в случае двух или более аргументов?
картофельные пюре

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

Я бы взял этот подход, потому что лямбда не работает для меня.
Завен Зареян

19

Пример 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опция должна быть установлена ​​как ссылка на метод, который мы хотим вызвать, как callbackв button_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
Это не решает проблему. Что если вы создаете три кнопки, которые все вызывают одну и ту же функцию, но должны передавать разные аргументы?
Брайан Оукли

Вы можете создать функцию, которая возвращает функцию.
MarrekNožka

Я знаю, что это больше не активно, но я связался здесь со stackoverflow.com/questions/35616411/… , это работает точно так же, как при использовании лямбда-выражений, вы можете определить функцию для каждой кнопки так же, как лямбда-выражение для каждой кнопки.
Тэдхг Макдональд-Дженсен

Поместив первый пример кода в цикл, который постоянно меняется myXи myYработает отлично, большое спасибо.
Тэдхг Макдональд-Дженсен

3

Опираясь на ответ Мэтта Томпсона: класс можно сделать вызываемым, чтобы его можно было использовать вместо функции:

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. Вы ссылаетесь на другие ситуации, которые я не понимаю?
Бром

2

Лямбды все хорошо, но вы также можете попробовать это (что работает в цикле 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)

Это работает, потому что, когда привязка установлена, нажатие клавиши передает событие в качестве аргумента. Затем вы можете вызвать атрибуты события, например, event.charполучить «1» или «ВВЕРХ». Если вам нужен аргумент или несколько аргументов, отличных от атрибутов события. просто создайте словарь для их хранения.


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

Вам нужно использовать lambda:

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

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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.