【问题标题】:How can I identify buttons, created in a loop?如何识别在循环中创建的按钮?
【发布时间】:2016-09-12 09:24:57
【问题描述】:

我正在尝试使用 tkinter 在 python 上编写一个扫雷游戏。我首先使用 10x10 的二维列表创建了一个按钮网格。然后我使用循环创建每个按钮,这样我就不必手动创建每个按钮并将它们夹住。

self.b=[[0 for x in range(1,12)] for y in range(1,12)] #The 2 dimensional list
for self.i in range(1,11):
     for self.j in range(1,11):
            self.b[self.i][self.j]=tkinter.Button(root,text = ("     "),command = lambda: self.delete()) # creating the button
            self.b[self.i][self.j].place(x=xp,y=yp) # placing the button
            xp+=26 #because the width and height of the button is 26
        yp+=26
        xp=0

基本上我希望按钮在被按下时消失。问题是我不知道如何让程序专门删除我按下的按钮,因为所有按钮都完全相同。创建删除函数时:

def delete(self):
    self.b[???][???].destroy()

我不知道如何让程序知道用户按下的是哪个按钮,所以它可以删除那个特定的按钮。

问题: 有没有办法让每个按钮都有一些独特的东西,让它与其他按钮区分开来?说给每个按钮一个特定的坐标,那么当按钮(2,3)被按下时,数字2和3被传递给删除函数,所以删除函数可以删除按钮(2,3)?

【问题讨论】:

  • 嗨川,你能评论一下答案吗,你管理好了吗?
  • 如果以下答案之一解决了您的问题,您应该接受它(单击相应答案旁边的复选标记)。这有两件事。它让每个人都知道您的问题已得到您满意的解决,并为帮助您的人提供帮助。有关完整说明,请参阅here

标签: python tkinter minesweeper


【解决方案1】:

在循环中创建按钮时,我们可以创建(实际获得)唯一标识。

例如:如果我们创建一个按钮:

button = Button(master, text="text")

我们可以立即识别它:

print(button)
> <tkinter.Button object .140278326922376>

如果我们将此标识存储到一个列表中,并将命令分配给按钮,在创建期间链接到它们的索引,我们可以在按下时获取它们的特定标识。

我们唯一要做的就是在按下按钮后通过索引获取按钮的标识。

为了能够以索引为参数为按钮设置命令,我们使用functools'partial

简化示例 (python3)

在下面的简化示例中,我们循环创建按钮,将它们的标识添加到列表中 (button_identities)。身份是通过以下方式查找的:bname = (button_identities[n])

现在我们有了身份,我们可以随后让按钮做任何事情,包括编辑或杀死自己,因为我们有了它的身份。

在下面的示例中,按下按钮会将其标签更改为“已单击”

from tkinter import *
from functools import partial

win = Tk()
button_identities = []

def change(n):
    # function to get the index and the identity (bname)
    print(n)
    bname = (button_identities[n])
    bname.configure(text = "clicked")

for i in range(5):
    # creating the buttons, assigning a unique argument (i) to run the function (change)
    button = Button(win, width=10, text=str(i), command=partial(change, i))
    button.pack()
    # add the button's identity to a list:
    button_identities.append(button)

# just to show what happens:
print(button_identities)

win.mainloop()

或者如果我们让它销毁按钮一旦被点击:

from tkinter import *
from functools import partial

win = Tk()
button_identities = []

def change(n):
    # function to get the index and the identity (bname)
    print(n)
    bname = (button_identities[n])
    bname.destroy()

for i in range(5):
    # creating the buttons, assigning a unique argument (i) to run the function (change)
    button = Button(win, width=10, text=str(i), command=partial(change, i))
    button.place(x=0, y=i*30)
    # add the button's identity to a list:
    button_identities.append(button)

# just to show what happens:
print(button_identities)

win.mainloop()

矩阵的简化代码 (python3):

在下面的示例中,我使用itertools's product() 来生成矩阵的坐标。

#!/usr/bin/env python3
from tkinter import *
from functools import partial
from itertools import product

# produce the set of coordinates of the buttons
positions = product(range(10), range(10))
button_ids = []

def change(i):
    # get the button's identity, destroy it
    bname = (button_ids[i])
    bname.destroy()

win = Tk()
frame = Frame(win)
frame.pack()

for i in range(10):
    # shape the grid
    setsize = Canvas(frame, width=30, height=0).grid(row=11, column=i)
    setsize = Canvas(frame, width=0, height=30).grid(row=i, column=11)

for i, item in enumerate(positions):
    button = Button(frame, command=partial(change, i))
    button.grid(row=item[0], column=item[1], sticky="n,e,s,w")
    button_ids.append(button)

win.minsize(width=270, height=270)
win.title("Too many squares")
win.mainloop()

更多选项,按坐标销毁按钮

由于product() 也产生按钮的x,y 坐标,我们可以额外存储坐标(在示例中为coords),并通过坐标识别按钮的身份。

在下面的例子中,函数hide_by_coords():通过坐标破坏按钮,这在minesweeper类游戏中很有用。例如,单击一个按钮也会破坏右侧的按钮:

#!/usr/bin/env python3
from tkinter import *
from functools import partial
from itertools import product

positions = product(range(10), range(10))
button_ids = []; coords = []

def change(i):
    bname = (button_ids[i])
    bname.destroy()
    # destroy another button by coordinates
    # (next to the current one in this case)
    button_nextto = coords[i]
    button_nextto = (button_nextto[0] + 1, button_nextto[1])
    hide_by_coords(button_nextto)

def hide_by_coords(xy):
    # this function can destroy a button by coordinates
    # in the matrix (topleft = (0, 0). Argument is a tuple
    try:
        index = coords.index(xy)
        button = button_ids[index]
        button.destroy()
    except (IndexError, ValueError):
        pass

win = Tk()
frame = Frame(win)
frame.pack()

for i in range(10):
    # shape the grid
    setsize = Canvas(frame, width=30, height=0).grid(row=11, column=i)
    setsize = Canvas(frame, width=0, height=30).grid(row=i, column=11)

for i, item in enumerate(positions):
    button = Button(frame, command=partial(change, i))
    button.grid(column=item[0], row=item[1], sticky="n,e,s,w")
    button_ids.append(button)
    coords.append(item)

win.minsize(width=270, height=270)
win.title("Too many squares")
win.mainloop()

【讨论】:

    【解决方案2】:

    如果您只想销毁 Button 小部件,简单的方法是在创建按钮后添加回调。例如,

    import Tkinter as tk
    
    grid_size = 10
    
    root = tk.Tk()
    blank = " " * 3
    for y in range(grid_size):
        for x in range(grid_size):
            b = tk.Button(root, text=blank)
            b.config(command=b.destroy)
            b.grid(column=x, row=y)
    
    root.mainloop()
    

    但是,如果您需要在回调中进行额外处理,例如更新按钮网格,将 Button 的网格索引存储为 Button 对象的属性会很方便。

    from __future__ import print_function
    import Tkinter as tk
    
    class ButtonDemo(object):
        def __init__(self, grid_size):
            self.grid_size = grid_size
            self.root = tk.Tk()
            self.grid = self.button_grid()
            self.root.mainloop()
    
        def button_grid(self):
            grid = []
            blank = " " * 3
            for y in range(self.grid_size):
                row = []
                for x in range(self.grid_size):
                    b = tk.Button(self.root, text=blank)
                    b.config(command=lambda widget=b: self.delete_button(widget))
                    b.grid(column=x, row=y)
                    #Store row and column indices as a Button attribute
                    b.position = (y, x)
                    row.append(b)
                grid.append(row)
            return grid
    
        def delete_button(self, widget):
            y, x = widget.position
            print("Destroying", (y, x))
            widget.destroy()
            #Mark this button as invalid 
            self.grid[y][x] = None
    
    ButtonDemo(grid_size=10)
    

    这两个脚本都与 Python 3 兼容,只需将导入行更改为

    import tkinter as tk
    

    【讨论】:

    • 使用您的第一个代码 - 当第一行按钮被移除时,寡妇会重新调整大小并将所有内容向上移动。 tkinter 中是否有 hide 而不是 destroy
    • @SiHa 好点! :oops: 据称,您可以使用grid_remove 隐藏通过.grid 方法添加到其父级的小部件。但我刚刚测试了它(在 Python 2.6 和 3.6 上),我们遇到了同样的问题。 :( 但是,有这个解决方法:在我的第二个脚本中,将 widget.destroy() 替换为 widget.config(state=tk.DISABLED, relief=tk.FLAT)
    【解决方案3】:

    尝试如下修改您的代码:

    self.b=[[0 for x in range(10)] for y in range(10)] #The 2 dimensional list
    xp = yp = 0
    for i in range(10):
        for j in range(10):
            self.b[i][j]=tkinter.Button(root,text="     ",command=lambda i=i,j=j: self.delete(i,j)) # creating the button
            self.b[i][j].place(x=xp,y=yp) # placing the button
            xp+=26 #because the width and height of the button is 26
        yp+=26
        xp=0
    

    和:

    def delete(self, i, j):
        self.b[i][j].destroy()
    

    【讨论】:

      【解决方案4】:

      以下代码生成 12 个按钮,每行 4 个。 需要编辑的特定按钮的调用类似于调用矩阵元素。例如 button[1,1] 已针对背景颜色进行了编辑,而 button[2,2] 已针对前景色和文本进行了编辑。程序在python3.6 pycharm控制台上测试过

      from tkinter import *
      root=Tk()
      Buts={}
      for r in range(3):
          for c in range(4):
              Buts[(r,c)]=Button(root,text='%s/%s'%(r,c),borderwidth=10)
              Buts[r,c].grid(row=r,column=c)
      Buts[1,1]['bg']='red'
      Buts[2,2]['text']=['BUTTON2']
      Buts[2,2]['fg']=['blue']
      root.mainloop()
      

      【讨论】:

        【解决方案5】:

        有一种方法可以将参数传递给按下按钮时执行的函数:

        from tkinter import *
        from functools import partial
        root = Tk()
        root.geometry("300x200")
        
        b = Button(root, text = "some text", command=partial(yourfunc, argument))
        b.pack()
        
        root.mainloop()
        

        【讨论】:

          【解决方案6】:
          # CREATE INDEX VARIABLE
          i = 0
          
          
          # MAKE BUTTON CLASS
          class YourButton:
              def __init__ (self):
                  global i
                  self.index = i
                  self.button = Button (root, command=self.getIndex)
                  self.button.grid (row=i)
                  i += 1
          
          # DEFINE FUNCTION PRINTING THE INDEX
              def getIndex (self):
                  print (self.index)
          
          
          # DO BUTTONS LOOP
          buttons = []
          for j in range (0, 5):
               buttons.append (YourButton ())
          
          # CLICK A BUTTON GETTING ITS INDEX
          

          变量 j 正在被循环使用。按钮类 init 正在使用变量 i。每次循环创建一个按钮时,变量 j 都会增加 +1,变量 j 将成为按钮列表的按钮索引,称为 buttons。在按钮类的末尾,每次类初始化按钮时,变量 i 都会增加 +1(全局 i;i += 1),因此变量 ji 相等。当您获得存储在 self.index = i 中的按钮 i 变量时,您将获得按钮索引。你必须使用全局变量,因为变量不在类中,因为如果你在一个类中创建它,你就不能增加它。

          您可能希望返回索引变量而不是 print (self.index)。您可以返回它,创建另一个类,并将其存储在 self 中,例如:

          class AnotherClass:
              def __init__ (self):
                  self.returnedVariable = None
          
              def returnVariable (self, variable):
                  self.returnVariable = variable
          

          调用 returnVariable 函数 (returnVariable (self.index)) 而不是 print (self.index)。

          【讨论】:

          • 虽然只有代码的答案可能会回答这个问题,但您可以通过为您的代码提供上下文、此代码工作的原因以及一些文档参考以供进一步阅读,从而显着提高您的答案质量.
          • 谢谢您的意见,先生,我将在代码 cmets 中添加您所写的内容。该代码正在我正在构建的国际象棋引擎中使用。它有效。
          猜你喜欢
          • 1970-01-01
          • 2017-10-20
          • 1970-01-01
          • 1970-01-01
          • 2022-11-04
          • 1970-01-01
          • 2013-01-08
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多