pythontkinterframes

left and right frame with buttons in them


I want to create two frames next to each other with widgets in a mainframe, but I don't know how to make them both clickable, and I'm sure my code can be cleaned up. Any help is appreciated!

I started off with just making a single frame with widgets and than go from there, but my main problem is that the list constructor can only take one argument. I want to be able to use the self.squares variable for both frames but I'm not sure how to do that so I set up another square dictionary for the second frame but I think that makes things unnecessarily long. Here is my code below. The #here is where I'm having the most trouble.

class Board(tk.Frame):

    def __init__(self, parent, length, width):  # self=Frame, parent=root

        tk.Frame.__init__(self, parent)
        self.parent = parent
        self.length = length
        self.width = width
        self.config(height=100 * self.length, width=100 * self.width)
        self.frame1 = tk.Frame()
        self.frame2 = tk.Frame()
        self.pack()

        self.squares = {}
        self.squares2 = {}
        self.ranks = string.ascii_lowercase
        self.buttons_pressed = 0
        self.turns = 0
        self.sq1 = None  # first square clicked
        self.sq2 = None
        self.sq1_button = None  # button associated with the square clicked
        self.sq2_button = None
        self.set_squares()

    def select_piece(self, button):
        if self.buttons_pressed == 0:
            self.sq1 = list(self.squares.keys())[list(self.squares.values()).index(button)] #here
            self.sq1_button = button
            self.buttons_pressed += 1

        elif self.buttons_pressed == 1:  # stores square and button of second square selected
            self.sq2 = list(self.squares.keys())[list(self.squares.values()).index(button)]
            self.sq2_button = button
            if self.sq2 == self.sq1:  # prevents self-destruction and allows the user to choose a new piece
                self.buttons_pressed = 0
                return
            if True:
                self.squares[self.sq2].config(image=self.sq1_button["image"])
                self.squares[self.sq2].image = self.sq1_button["image"]
                self.squares[self.sq1].config(image=self.white_images["blank.png"])  # clears sq1
                self.squares[self.sq1].image = self.white_images["blank.png"]
                self.buttons_pressed = 0
                return

    def set_squares(self):  # fills frame with buttons representing squares

        for x in range(5):
            for y in range(5):
                b = tk.Button(self.frame1, bg=self.square_color, activebackground="lawn green")
                b.grid(row=8 - x, column=y)
                b2 = tk.Button(self.frame2, bg=self.square_color, activebackground="lawn green")
                b2.grid(row=8 - x, column=y)
                pos = self.ranks[x] + str(y + 1)
                self.squares.setdefault(pos, b,)  # creates list of square positions
                self.squares2.setdefault(pos, b2)
                self.squares[pos].config(command=lambda key=self.squares[pos]: self.select_piece(key))
                self.squares2[pos].config(command=lambda key=self.squares2[pos]: self.select_piece(key))

    def set_pieces(self):  # places pieces in starting positions
        dict_rank1_pieces = {"a1": "dirt.png", "b1": "fire.png", "c1": "metal.png", "d1": "water.png", "e1": "wood.png"}

        for key in dict_rank1_pieces:  # inserts images into buttons
            starting_piece = dict_rank1_pieces[key]
            self.squares[key].config(image=self.white_images[starting_piece])
            self.squares[key].image = self.white_images[starting_piece]
            self.squares2[key].config(image=self.white_images[starting_piece])
            self.squares2[key].image = self.white_images[starting_piece]
            self.frame1.pack(side='left')
            self.frame2.pack(side='right')

        for rank in range(2, 6):  # fill rest with blank pieces
            for file in range(5):
                starting_piece = "blank.png"
                pos = self.ranks[file] + str(rank)
                self.squares[pos].config(image=self.white_images[starting_piece])
                self.squares[pos].image = self.white_images[starting_piece]
                self.squares2[pos].config(image=self.white_images[starting_piece])
                self.squares2[pos].image = self.white_images[starting_piece]


root = tk.Tk()
root.geometry("800x800")
board = Board(root, 5, 5)
board.import_pieces()
board.set_pieces()
board.mainloop()

Solution

  • If you want to use the same self.squares for both the frames, you need to make the keys (currently they are something like "a1", "b1", etc) unique. One of the suggestion is to use a tuple of (idx, key) instead as the new key:

    Below is the required modifications:

    class Board(tk.Frame):
        ...
    
        def set_squares(self):  # fills frame with buttons representing squares
    
            for x in range(5):
                for y in range(5):
                    pos = self.ranks[x] + str(y + 1)
                    for i, frame in enumerate((self.frame1, self.frame2)):
                        b = tk.Button(frame, bg=self.square_color, activebackground="lawn green")
                        b.grid(row=8 - x, column=y, sticky="nsew")
                        b.config(command=lambda key=b: self.select_piece(key))
                        # use tuple as key
                        self.squares[i,pos] = b
    
        def set_pieces(self):  # places pieces in starting positions
            dict_rank1_pieces = {"a1": "dirt.png", "b1": "fire.png", "c1": "metal.png", "d1": "water.png", "e1": "wood.png"}
    
            for key in dict_rank1_pieces:  # inserts images into buttons
                starting_piece = dict_rank1_pieces[key]
                # use tuple as key
                self.squares[0,key].config(image=self.white_images[starting_piece])
                self.squares[1,key].config(image=self.white_images[starting_piece])
                # suggest to move below two lines to __init__()
                self.frame1.pack(side='left')
                self.frame2.pack(side='right')
    
            for rank in range(2, 6):  # fill rest with blank pieces
                for file in range(5):
                    starting_piece = "blank.png"
                    pos = self.ranks[file] + str(rank)
                    # use tuple as key
                    self.squares[0,pos].config(image=self.white_images[starting_piece])
                    self.squares[1,pos].config(image=self.white_images[starting_piece])
    
    ...
    

    Note that currently you have created the two frames with buttons as children of root window, not Board. Is it what you want actually?


    Updated set_pieces() to put images at the very left column of left frame and the very right column of right frame:

    def set_pieces(self):  # places pieces in starting positions
        dict_rank_pieces = {"a": "dirt.png", "b": "fire.png", "c": "metal.png", "d": "water.png", "e": "wood.png"}
    
        blank_piece = "blank.png"
        for rank in range(1, 6):  # fill rest with blank pieces
            for file in range(5):
                starting_piece = dict_rank_pieces[self.ranks[file]]
                pos = self.ranks[file] + str(rank)
                self.squares[0,pos].config(image=self.white_images[starting_piece if rank == 1 else blank_piece])
                self.squares[1,pos].config(image=self.white_images[starting_piece if rank == 5 else blank_piece])
    
        self.frame1.pack(side='left')
        self.frame2.pack(side='right')