【问题标题】:(Instantiating an array of Buttons, but only one works(实例化一组按钮,但只有一个有效
【发布时间】:2025-12-29 15:50:12
【问题描述】:

我正在尝试为围棋游戏的虚拟棋盘创建 GUI。应该有一个nxn 格子网格,玩家可以在其中放置黑色或白色的石头。单击图块将使其从棕褐色(默认)变为黑色,再次单击变为白色,然后单击第三次返回棕褐色。玩家一可以在一个地方点击一次以放置一块石头,玩家二可以点击两次(您需要稍后移除石头,所以点击三下会重置它)。我创建了一个 tile 对象,然后使用嵌套的 for 循环来实例化其中的 9 x 9。不幸的是,运行代码似乎只产生 1 个功能块,而不是 81 个。这段代码应该可以在任何 python 机器上运行(我使用的是 Python 3.4),所以你可以尝试运行它并亲自查看。谁能指出循环只运行一次的原因?

from tkinter import *
window = Tk()
n = 9

"""
A tile is a point on a game board where black or white pieces can be placed. If there are no pieces, it remains tan.
The basic feature is the "core" field which is a tkinter button. when the color is changed, the button is configured to represent this.
"""
class tile(object):
    core = Button(window, height = 2, width = 3, bg = "#F4C364")

    def __init__(self):
        pass

    """the cycle function makes the tile object actually change color, going between three options: black, white, or tan."""
    def cycle(self):

        color = self.core.cget("bg")

        if(color == "#F4C364"): #tan, the inital value.
            self.core.config(bg = "#111111")#white.
        elif (color == "#111111"):
            self.core.config(bg = "#DDDDDD")#black.
        else:
            self.core.config(bg = "#F4C364")#back to tan.

board = [] #create overall array
for x in range(n):
    board.append([])#add subarrays inside it
    for y in range(n):
        board[x].append(tile())#add a tile n times in each of the n subarrays
        T = board[x][y] #for clarity, T means tile
        T.core.config(command = lambda: T.cycle()) #I do this now because cycle hadn't been defined yet when I created the "core" field
        T.core.grid(row = x, column = y) #put them into tkinter.

window.mainloop()

【问题讨论】:

  • 您的命令功能将无法正常工作:它只会循环最后一个按钮。您可以轻松解决此问题:command = lambda t=T: t.cycle()。但是还有更好的方法;我会尽快发布答案。

标签: python arrays object button tkinter


【解决方案1】:

正如 mhawke 在他的回答中指出的那样,您需要将 core 设为实例变量,以便每个 Tile 都有自己的核心。

正如我在上面的评论中提到的,您还需要修复 Button 的命令回调函数。您在问题中使用的代码将调用T 的当前值的.cycle() 方法,这恰好是创建的最后一个图块。因此,无论您在哪里单击,只有最后一个图块会改变颜色。解决此问题的一种方法是在创建时将当前图块作为lambda 函数的默认参数传递。但是因为您使用 OOP 创建 Tile,所以有一种更好的方法,您可以在下面看到。

我对您的代码做了一些修改。

尽管许多 Tkinter 示例使用 from tkinter import *,但这不是一个好习惯。当您执行from some_module import * 时,它会将some_module 中的所有名称带入当前模块(您的脚本),这意味着您可能会不小心用自己的名称覆盖这些名称。更糟糕的是,如果您对多个模块执行import *,则每个新模块的名称都可能与前一个模块的名称发生冲突,并且您无法知道发生了什么,直到您开始遇到神秘的错误。使用import tkinter as tk 意味着您需要进行更多的输入,但这会使生成的程序更不容易出错并且更易于阅读。

我已经修改了__init__ 方法,以便使用窗口和图块的 (x, y) 位置调用它(习惯上使用 x 作为水平坐标,使用 y 作为垂直坐标)。现在,每个 Tile 对象都跟踪其当前状态,其中 0=空,1=黑色,2=白色。这使得更新颜色更容易。因为我们已经传入了窗口和 (x, y),所以我们可以使用该信息将图块添加到网格中。磁贴还会记住位置(在 self.location 中),这可能会派上用场。

我修改了cycle 方法,使其更新background 颜色和图块的activebackground。因此,当鼠标悬停在图块上时,它会更改为(大致)介于当前颜色和单击它会变成的颜色之间的颜色。 IMO,这比鼠标悬停时总是变成浅灰色的瓷砖要好。

我还优化了创建所有图块并将它们存储在列表的板列表中的代码。

import tkinter as tk

colors = (
    #background, #activebackground
    ("#F4C364", "#826232"), #tan
    ("#111111", "#777777"), #black
    ("#DDDDDD", "#E8C8A8"),  #white
)

class Tile(object):
    """ A tile is a point on a game board where black or white pieces can be placed. 
        If there are no pieces, it remains tan.
        The basic feature is the "core" field which is a tkinter button. 
        when the color is changed, the button is configured to represent this.
    """

    def __init__(self, win, x, y):
        #States: 0=empty, 1=black, 2=white
        self.state = 0
        bg, abg = colors[self.state]

        self.core = tk.Button(win, height=2, width=3,
            bg=bg, activebackground=abg,
            command=self.cycle)
        self.core.grid(row=y, column=x)

        #self.location = x, y

    def cycle(self):
        """ the cycle function makes the tile object actually change color,
            going between three options: black, white, or tan.
        """
        #cycle to the next state. 0 -> 1 -> 2 -> 0
        self.state = (self.state + 1) % 3
        bg, abg = colors[self.state]
        self.core.config(bg=bg, activebackground=abg)

        #print(self.location)


window = tk.Tk()
n = 9

board = [] 
for y in range(n):
    row = [Tile(window, x, y) for x in range(n)]
    board.append(row)

window.mainloop()

【讨论】:

    【解决方案2】:

    问题在于core 是一个类变量,它创建一次 并由类tile 的所有实例共享。它应该是每个 tile 实例的实例变量。

    像这样将core = Button(window, height = 2, width = 3, bg = "#F4C364") 移动到tile.__init__()

    class Tile(object):
        def __init__(self):
            self.core = Button(window, height = 2, width = 3, bg = "#F4C364")
    

    【讨论】:

      【解决方案3】:

      问题的根源在于core 由类的所有实例共享,这取决于您如何定义它。您需要将按钮的创建移到初始化程序中。

      我还建议将命令的配置移至按钮本身。调用者不应该需要(也不关心)按钮如何在内部工作。就我个人而言,我会从Button 继承瓷砖,但如果你喜欢组合而不是继承,我会坚持下去。

      例子:

      class tile(object):
      
          def __init__(self):
              self.core = Button(window, height = 2, width = 3, bg = "#F4C364"
                                 command=self.cycle)
      

      【讨论】:

        最近更新 更多