Почему команда моей кнопки выполняется немедленно, когда я создаю кнопку, а не когда я нажимаю на нее?
Мой код:
from Tkinter import *
admin = Tk()
def button(an):
print(an)
print('het')
b = Button(admin, text='as', command=button('hey'))
b.pack()
mainloop()
Кнопка не работает, она печатает "hey" и "het" один раз без моей команды, а затем, когда я нажимаю кнопку, ничего не происходит.
Переведено автоматически
Ответ 1
Рассмотрим этот код:
b = Button(admin, text='as', command=button('hey'))
Он выполняет точно то же самое, что и этот:
result = button('hey')
b = button(admin, text='as', command=result)
Аналогично, если вы создаете привязку, подобную этой:
listbox.bind("<<ListboxSelect>>", some_function())
... это то же самое, что и это:
result = some_function()
listbox.bind("<<ListboxSelect>>", result)
Опция command
принимает ссылку на функцию, что является причудливым способом сказать, что вам нужно передать ей имя функции. Чтобы передать ссылку, вы должны использовать только имя, без использования круглых скобок или аргументов. Например:
b = Button(... command = button)
Если вы хотите передать параметр, такой как "hey", вы должны использовать немного дополнительного кода:
- Вы можете создать промежуточную функцию, которая может вызываться без вашего аргумента и которая затем вызывает вашу
button
функцию, - Вы можете использовать
lambda
для создания того, что называется анонимной функцией. Во всех отношениях это функция, за исключением того, что у нее нет имени. Когда вы вызываетеlambda
команду, она возвращает ссылку на созданную функцию, что означает, что ее можно использовать для значенияcommand
параметра для кнопки. - Вы можете использовать functools.partial
Для меня lambda
самый простой, поскольку он не требует какого-либо дополнительного импорта, как это делает functools.partial
, хотя некоторые люди думают, что это functools.partial
проще для понимания.
Чтобы создать лямбда-функцию, которая вызывает вашу button
функцию с аргументом, вы должны сделать что-то вроде этого:
lambda: button('hey')
В итоге получается функция, функционально эквивалентная:
def some_name():
return button('hey')
Как я говорил ранее, lambda
возвращает ссылку на эту безымянную функцию. Поскольку ссылка - это то, что ожидает command
опция, вы можете использовать lambda
непосредственно при создании кнопки:
b = Button(... command = lambda: button('hey'))
На этом сайте есть вопрос, в котором много интересных комментариев о лямбде в целом. Смотрите Вопрос Почему лямбды Python полезны?. В том же обсуждении есть ответ, который показывает, как использовать лямбды в цикле, когда вам нужно передать переменную в обратный вызов.
Наконец, смотрите zone.effbot.org Статью под названием Обратные вызовы Tkinter для получения хорошего руководства. Охват lambda
довольно скудный, но информация там все равно может быть полезной.
Ответ 2
Вам нужно создать функцию без параметров, которые вы можете использовать в качестве команды:
b = Button(admin, text='as', command=lambda: button('hey'))
Смотрите раздел "Передача аргумента обратным вызовам" в этом документе.
Ответ 3
Пример графического интерфейса:
Допустим, у меня есть графический интерфейс:
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
Обратите пристальное внимание на отсутствие метода ()
with 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()
Ответ 4
Движок оценивает результат работы функции, когда присваивает значение в строке "... command = ..."
"Команда" ожидает, что будет возвращена функция, вот почему использование лямбда-выражения может выполнить эту работу, потому что оно создает анонимную функцию, которая возвращается "команде" во время оценки. Вы также можете закодировать свою собственную функцию, она также выполнит эту работу.
это пример с лямбдой и без лямбды:
#!/usr/bin/python
# coding=utf-8
from Tkinter import *
# Creation de la fenêtre principale (main window)
Mafenetre = Tk()
res1 = StringVar()
res2 = StringVar()
def isValidInput(obj):
if hasattr(obj, 'get') and callable(getattr(obj, 'get')):
return TRUE
return FALSE
# stupid action 2 (return 12 on purpose to show potential mistake)
def action1(*arguments):
print "action1 running"
for arg in arguments:
if isValidInput(arg):
print "input value: ", arg.get()
res1.set(arg.get())
else:
print "other value:", arg
print "\n"
return 12
# stupid action 2
def action2(*arguments):
print "action2 running"
a = arguments[0]
b = arguments[1]
if isValidInput(a) and isValidInput(b):
c = a.get() + b.get()
res2.set(c)
print c
print "\n"
# a stupid workflow manager ordered by name
def start_tasks(*arguments, **keywords):
keys = sorted(keywords.keys())
for kw in keys:
print kw, "plugged "
keywords[kw](*arguments)
# valid callback wrapper with lambda
def action1_callback(my_input):
return lambda args=[my_input]: action1(*args)
# valid callback wrapper without lambda
def action1_callback_nolambda(*args, **kw):
def anon():
action1(*args)
return anon
# first input string
input1 = StringVar()
input1.set("delete me...")
f1 = Entry(Mafenetre, textvariable=input1, bg='bisque', fg='maroon')
f1.focus_set()
f1.pack(fill="both", expand="yes", padx="5", pady=5)
# failed callback because the action1 function is evaluated, it will return 12.
# in this case the button won't work at all, because the assignement expect a function
# in order to have the button command to execute something
ba1 = Button(Mafenetre)
ba1['text'] = "show input 1 (ko)"
ba1['command'] = action1(input1)
ba1.pack(fill="both", expand="yes", padx="5", pady=5)
# working button using a wrapper
ba3 = Button(Mafenetre)
ba3['text'] = "show input 1 (ok)"
# without a lambda it is also working if the assignment is a function
#ba1['command'] = action1_callback_nolambda(input1)
ba3['command'] = action1_callback(input1)
ba3.pack(fill="both", expand="yes", padx="5", pady=5)
# display result label
Label1 = Label(Mafenetre, text="Action 1 result:")
Label1.pack(fill="both", expand="yes", padx="5", pady=5)
# display result value
resl1 = Label(Mafenetre, textvariable=res1)
resl1.pack(fill="both", expand="yes", padx="5", pady=5)
# second input string
input2 = StringVar()
f2 = Entry(Mafenetre, textvariable=input2, bg='bisque', fg='maroon')
f2.focus_set()
f2.pack(fill="both", expand="yes", padx="5", pady=5)
# third test without wrapper, but making sure that several arguments are well handled by a lambda function
ba2 = Button(Mafenetre)
ba2['text'] = "execute action 2"
ba2['command'] = lambda args=[input1, input2], action=action2: start_tasks(*args, do=action)
ba2.pack(fill="both", expand="yes", padx="5", pady=5)
# display result label
Label2 = Label(Mafenetre, text="Action 2 result:")
Label2.pack(fill="both", expand="yes", padx="5", pady=5)
# display result value
resl2 = Label(Mafenetre, textvariable=res2)
resl2.pack(fill="both", expand="yes", padx="5", pady=5)
Mafenetre.mainloop()