如何创建一个按钮数组,在使用Tkinter单击时更改文本

时间:2016-08-09 08:47:10

标签: python tkinter

我正在尝试使用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()

但是,生成的窗口如下所示:

enter image description here

单击时按钮不会改变。任何想法为什么这不起作用?

1 个答案:

答案 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内的变量在执行函数时评估,即在循环之后 ij将采用每个函数的 last 值。有关更详细的说明,请参阅例如here。解决此问题的一种方法是将这些变量声明为lambda的参数,同时使用循环中的当前值作为其默认值,即lambda i=i, j=j: ...。这样,ij在声明函数时进行评估,而不是在调用函数时进行评估。

您的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)]