Вопрос-Ответ

Why is my Button's command executed immediately when I create the Button, and not when I click it? [duplicate]

Почему команда моей кнопки выполняется немедленно, когда я создаю кнопку, а не когда я нажимаю на нее?

Мой код:

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