1

I've looked around SO and tried the solutions offered, but I can't seem to change the text of any button I've made using a double for loop.

The loops are there so I can add them to a list of lists of buttons so I can (supposedly) access them conveniently through the nested list by calling board_button[2][3] or something. They are also dynamically created after the user inputs the board_size such that it generates a grid of nxn buttons so there's that. These are all done inside a class method, and there's another class method that should change the button's text when it's called by the button.

I've tried using the solutions offered here, but none of them actually worked for my problem.

Pardon the long block of code, but I honestly think the way I've made it may have contributed to the problem, and give more insight as result.

from tkinter import filedialog
from tkinter import *

class MainWindow(Frame):

    board_size = None
    file_input = None
    board_buttons = None
    board_strvars = None

    row = []

    def __init__ (self, parent):
        Frame.__init__(self, parent)

        # initialize widgets but don't show them yet
        self.initWidgets()
        # show the starting window
        self.startWindow()

    def generateBoard(self, to_forget=None):
        # hides the groups of the method that called it
        if to_forget != None:
            for i in to_forget:
                self.row[i].forget()

        # get the board_size from user
        self.board_size = int(self.size_entry.get())

        # initialize text variables for each button
        self.board_strvars = []
        for i in range(self.board_size):
            self.row_strvars=[]
            for j in range(self.board_size):
                var = StringVar()
                var.set("   ")
                self.row_strvars.append(var)

            self.board_strvars.append(self.row_strvars)

        # insert list of lists of buttons here
        self.row[1].pack(fill=X)

        self.board_buttons = []
        for i in range(self.board_size):
            self.row_buttons=[]
            for j in range(self.board_size):
                self.row_buttons.append(Button(self.row[1], textvariable=self.board_strvars[i][j], command=lambda:self.place(i, j)))
                self.row_buttons[j].grid(row=i, column=j)

            self.board_buttons.append(self.row_buttons)

        # for solve and back button
        self.row[2].pack(fill=X)

    def initWidgets(self):
        # create the rows or groups of widgets
        for i in range(3):
            self.row.append(Frame())

        # row 0; startWindow
        self.size_entry = Entry(self.row[0])
        self.size_entry.pack(fill=X, side=LEFT)

        self.size_button = Button(self.row[0], text="Enter", command=lambda:self.generateBoard([0]))
        self.size_button.pack(fill=X, side=LEFT)

        self.load_button = Button(self.row[0], text="Load", command=self.loadFile)
        self.load_button.pack(fill=X, side=LEFT)


        # row 2; generateBoard
        self.solve_button = Button(self.row[2], text="Solve", command=self.showSolutions)
        self.solve_button.pack(fill=X, side=LEFT)

        self.back_button = Button(self.row[2], text="Back", command=lambda:self.startWindow(to_forget=[0,2], to_destroy=[1]))
        self.back_button.pack(fill=X, side=RIGHT)

    def loadFile(self):
        print("file loaded!")

    def place(self, i, j):
        if self.board_strvars[i][j].get() == "   ":
            self.board_strvars[i][j].set("C")
        else:
            self.board_strvars[i][j].set("   ")

    def showSolutions(self):
        print("solutions shown!")


    def startWindow(self, to_forget=None, to_destroy=None):
        # hides the groups of the method that called it
        if to_forget != None:
            for i in to_forget:
                self.row[i].forget()

        # destroys the groups' child widgets and hides the group
        if to_destroy != None:
            for i in to_destroy:
                for child in self.row[i].winfo_children():
                    child.destroy()
                self.row[i].forget()

        self.row[0].pack(fill=X)

if __name__ == "__main__":
    root=Tk()
    root.title("test")
    app = MainWindow(root)
    root.mainloop()

I originally wanted to define a function that will change the text of the button that called it. But so far I've found no way to do so.

Doing the solutions offered in the post I linked changes nothing to the buttons. In the code I provided though, I used the StringVar() to be assigned as textvariable of the button. However, it only changes the last row, last column button element no matter which button you click. It's supposed to work in a way that the button that was clicked, will get its text changed.

Thanks!

Artemis
  • 2,553
  • 7
  • 21
  • 36
mashedpotatoes
  • 395
  • 2
  • 20

1 Answers1

1

Change your lambda function to force a closure:

def generateBoard(self, to_forget=None):
    ...

    for i in range(self.board_size):
        self.row_buttons=[]
        for j in range(self.board_size):
            self.row_buttons.append(Button(self.row[1], textvariable=self.board_strvars[i][j], command=lambda i=i, j=j:self.place(i, j)))
            self.row_buttons[j].grid(row=i, column=j)

        self.board_buttons.append(self.row_buttons)

Also note that its better to not call your own method place since there is already a place method in Tk.

Henry Yik
  • 22,275
  • 4
  • 18
  • 40