Chuan Li
Chuan Li

Reputation: 19

How can I identify buttons, created in a loop?

I am trying to program a minesweeper game on python using tkinter. I started off by creating a grid of buttons using a two dimensional list of 10x10. Then I created each button using a loop just so I don't have to manually create every single button and clip them on.

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

Basically I want the button to disappear upon being pressed. The problem is that I don't know how to let the program delete specifically the button that I pressed, as all the buttons are exactly the same. When creating the delete function:

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

I don't know how to let the program know which button it was that the user presses, so it can delete that specific one.

The question: Is there a way to let each button have something unique that allows it to be differentiated from the other buttons? Say assign each button a specific coordinate, so when button (2,3) is pressed, the numbers 2 and 3 are passed onto the delete function, so the delete function can delete button (2,3)?

Upvotes: 1

Views: 11464

Answers (6)

Jarek
Jarek

Reputation: 11

# 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

The variable j is being used by the loop. The variable i is being used by the button class init. Every time the loop creates a button the variable j is being incremented +1 and the variable j is becoming the button index of the buttons list called buttons. In the end of the button class the variable i is being incremeted +1 (global i; i += 1) every time the class is initialising a button, so the variables j and i are equal. You are getting a button index when you get the button i variable stored in self.index = i. You must use a global, because the variable is out of the class, because if you had made it in a class, you couldn't have incremented it.

You shall probably want to return the index variable instead of print (self.index). You can return it, creating another class, and storing it in self for example:

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

    def returnVariable (self, variable):
        self.returnVariable = variable

Call returnVariable function (returnVariable (self.index)) instead of print (self.index).

Upvotes: 0

taras
taras

Reputation: 1

there is a way to pass arguments to a function that is executed when a button is pressed:

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()

Upvotes: 0

Aravinda Murthy
Aravinda Murthy

Reputation: 1

The following code generates 12 buttons ,4 in each row. The particular button required to be edited is called similar to calling a matrix element. As an example button[1,1] has been edited for background color and button[2,2] has been edited for foreground color and text. The programme is tested on on python3.6 pycharm console

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()

Upvotes: 0

Jacob Vlijm
Jacob Vlijm

Reputation: 3159

While creating buttons in a loop, we can create (actually get) the unique identity.

For example: if we create a button:

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

we can identify it immediately:

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

If we store this identity into a list and asign a command to the button(s), linked to their index during creation, we can get their specific identity when pressed.

The only thing we have to to then is to fetch the button's identity by index, once the button is pressed.

To be able to set a command for the buttons with the index as argument, we use functools' partial.

Simplified example (python3)

In the simplified example below, we create the buttons in a loop, add their identities to the list (button_identities). The identity is fetched by looking it up with: bname = (button_identities[n]).

Now we have the identity, we can subsequently make the button do anything, including editing- or killing itself, since we have its identity.

In the example below, pressing the button will change its label to "clicked"

enter image description here

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()

Or if we make it destroy the buttons once clicked:

enter image description here

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()

Simplified code for your matrix (python3):

In the example below, I used itertools's product() to generate the coordinates for the matrix.

enter image description here

#!/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()

More options, destroying a button by coordinates

Since product() also produces the x,y coordinates of the button(s), we can additionally store the coordinates (in coords in the example), and identify the button's identity by coordinates.

In the example below, the function hide_by_coords(): destroys the button by coordinates, which can be useful in minesweeper -like game. As an example, clicking one button als destroys the one on the right:

#!/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()

Upvotes: 15

PM 2Ring
PM 2Ring

Reputation: 55499

If you just want to destroy the Button widget, the simple way is to add the callback after you create the button. Eg,

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()

However, if you need to do extra processing in your callback, like updating your grid of buttons, it's convenient to store the Button's grid indices as an attribute of the Button object.

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)

Both of these scripts are compatible with Python 3, just change the import line to

import tkinter as tk

Upvotes: 2

acw1668
acw1668

Reputation: 47173

Try modifying your code as below:

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

and:

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

Upvotes: 1

Related Questions