我正在尝试使用Tkinter为Tic Tac Toe游戏实现一个简单的GUI。作为第一步,我正在尝试制作一系列按钮,这些按钮从未标记变为点击时带有“X”标签。我尝试了以下内容:
import Tkinter as tk
class ChangeButton:
def __init__(self, master, grid=np.diag(np.ones(3))):
frame = tk.Frame(master)
frame.pack()
self.grid = grid
self.buttons = [[tk.Button()]*3]*3
for i in range(3):
for j in range(3):
self.buttons[i][j] = tk.Button(frame, text="", command=self.toggle_text(self.buttons[i][j]))
self.buttons[i][j].grid(row=i, column=j)
def toggle_text(self, button):
if button["text"] == "":
button["text"] = "X"
root = tk.Tk()
root.title("Tic Tac Toe")
app = ChangeButton(root)
root.mainloop()
但是,生成的窗口如下所示:
单击时按钮不会改变。任何想法为什么这不起作用?
答案 0 :(得分:0)
主要问题是,使用command=self.toggle_text(self.buttons[i][j]))
,您调用回调函数并将其结果绑定到command. Instead, you have to bind the function itself to
命令, or a
lambda`将使用正确的参数调用该函数。这样做的天真方式如下:
command=lambda: self.toggle_text(self.buttons[i][j]) # won't work!
但这在循环内部不起作用,因为lambda
内的变量在执行函数时评估,即在循环之后 i
和j
将采用每个函数的 last 值。有关更详细的说明,请参阅例如here。解决此问题的一种方法是将这些变量声明为lambda
的参数,同时使用循环中的当前值作为其默认值,即lambda i=i, j=j: ...
。这样,i
和j
在声明函数时进行评估,而不是在调用函数时进行评估。
您的command
和周围的循环将如下所示:
for i in range(3):
for j in range(3):
self.buttons[i][j] = tk.Button(frame, text="",
command=lambda i=i, j=j: self.toggle_text(self.buttons[i][j]))
self.buttons[i][j].grid(row=i, column=j)
还有一个问题,与第一个问题无关,与初始化self.buttons
列表的方式有关。通过执行[[tk.Button()]*3]*3
,列表将包含对相同列表的三个引用,每个引用包含对相同按钮的三个引用。参见例如here进行更深入的讨论。此外,您不需要在循环中初始化列表中的按钮,就像之后设置它们一样。我建议改为使用嵌套列表理解:
self.buttons = [[None for _ in range(3)] for _ in range(3)]