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

tkinter creating buttons in for loop passing command arguments

tkinter создает кнопки в цикле передачи аргументов команды

Я пытаюсь создавать кнопки в tkinter в for цикле. И с каждым циклом передаю i значение count в качестве аргумента в значении команды. Поэтому, когда функция вызывается из command значения, я могу сказать, какая кнопка была нажата, и действовать соответствующим образом.

Проблема в том, что, скажем, длина равна 3, это создаст 3 кнопки с заголовками Game 1 через Game 3, но при нажатии любой из кнопок печатное значение всегда 2, последняя итерация. Похоже, что кнопки создаются как отдельные объекты, но i значение в аргументах команды, похоже, одинаковое. Вот код:

def createGameURLs(self):
self.button = []
for i in range(3):
self.button.append(Button(self, text='Game '+str(i+1),
command=lambda: self.open_this(i)))
self.button[i].grid(column=4, row=i+1, sticky=W)

def open_this(self, myNum):
print(myNum)

Есть ли способ заставить текущее i значение на каждой итерации привязываться к этой конкретной кнопке?


Эту проблему можно рассматривать как частный случай создания функций в цикле. Также есть что фиксируют замыкания лямбда-функций?, для более технического обзора.

Смотрите также Как передавать аргументы команде Button в Tkinter? об общей проблеме передачи аргументов обратным вызовам Button .

Переведено автоматически
Ответ 1

Измените свой лямбда на lambda i=i: self.open_this(i).

Это может показаться волшебным, но вот что происходит. Когда вы используете эту лямбду для определения своей функции, вызов open_this не получает значение переменной i во время определения функции. Вместо этого он выполняет замыкание, которое похоже на заметку самому себе, в которой говорится: "Я должен посмотреть, каково значение переменной i на момент вызова". Конечно, функция вызывается после завершения цикла, поэтому в это время i всегда будет равно последнему значению из цикла.

Использование i=i трюка заставляет вашу функцию сохранять текущее значение i на момент определения вашего лямбда-выражения, вместо того, чтобы ждать поиска значения i позже.

Ответ 2

Вот как работают замыкания в python. Я сам однажды столкнулся с этой проблемой. Вы могли бы использовать functools.partial для этого.

for i in range(3):
self.button.append(Button(self, text='Game '+str(i+1), command=partial(self.open_this, i)))
Ответ 3

Просто прикрепите область действия кнопок к лямбда-функции следующим образом:

btn["command"] = lambda btn=btn: click(btn) где click(btn) - функция, которая передает саму кнопку. Это создаст область привязки от кнопки к самой функции.

Характеристики:


  • Настройка размера сетки

  • Адаптивное изменение размера

  • Переключить активное состояние


#Python2
#from Tkinter import *
#import Tkinter as tkinter
#Python3
from tkinter import *
import tkinter

root = Tk()
frame=Frame(root)
Grid.rowconfigure(root, 0, weight=1)
Grid.columnconfigure(root, 0, weight=1)
frame.grid(row=0, column=0, sticky=N+S+E+W)
grid=Frame(frame)
grid.grid(sticky=N+S+E+W, column=0, row=7, columnspan=2)
Grid.rowconfigure(frame, 7, weight=1)
Grid.columnconfigure(frame, 0, weight=1)

active="red"
default_color="white"

def main(height=5,width=5):
for x in range(width):
for y in range(height):
btn = tkinter.Button(frame, bg=default_color)
btn.grid(column=x, row=y, sticky=N+S+E+W)
btn["command"] = lambda btn=btn: click(btn)

for x in range(width):
Grid.columnconfigure(frame, x, weight=1)

for y in range(height):
Grid.rowconfigure(frame, y, weight=1)

return frame

def click(button):
if(button["bg"] == active):
button["bg"] = default_color
else:
button["bg"] = active

w= main(10,10)
tkinter.mainloop()

введите описание изображения здесь
введите описание изображения здесь

введите описание изображения здесь

Ответ 4

It's because the value for the name i changes and isn't captured by lambda:. (You can try that theory out by adding i = 1234 after the loop and seeing what happens.)

You'll need to write a function to wrap that i as a local name, then return a lambda in that function that captures i .

def make_button_click_command(i):
return lambda: button_click(i)

# ...

btn = Button(..., command=make_button_click_command(i))

Another option is functools.partial, which does effectively the same thing:

command=functools.partial(button_click, i)

All in all, you can also simplify things a bit by using just range to get numbers from 0 to 10 and divmod to get the row and column in one function call:

from tkinter import Tk, Button


def button_click(i):
print(i)


def make_button_click_command(i):
return lambda: button_click(i)


root = Tk()

for i in range(10):
value = (i + 1) % 10
row, col = divmod(i, 3)
btn = Button(root, text=value, padx=40, pady=20, command=make_button_click_command(value))
btn.grid(row=row + 1, column=col)

root.mainloop()
python tkinter