[Tkinter] Programmatically creating buttons that remember their positions - Printable Version +- Python Forum (https://python-forum.io) +-- Forum: Python Coding (https://python-forum.io/forum-7.html) +--- Forum: GUI (https://python-forum.io/forum-10.html) +--- Thread: [Tkinter] Programmatically creating buttons that remember their positions (/thread-40215.html) |
Programmatically creating buttons that remember their positions - Clunk_Head - Jun-21-2023 I'm looking to create buttons that remember where they are are in a 2D list. This way they can share a single function that takes their 2D list indices as parameters. Ultimately, I want the button to be able to manipulate its own text without having to have a separate reference for each button outside of the list and without having to have a separate function associated with each button. The buttons display properly with the correct text, but the lambdas reference only the final values of grid_row and grid_column, so all buttons print "3, 3" when pressed. from tkinter import * root = Tk() root.title(" ") root.geometry("250x250+0+20") def button_print(row, column): print(f"{row + 1}, {column + 1}") buttons = [] for grid_row in range(3): button_row = [] for grid_column in range(3): button = Button(root, text = f"{grid_row + 1} {grid_column + 1}", command = lambda: button_print(grid_row, grid_column), width = 4, height = 3) button.grid(row = grid_row, column = grid_column, padx = (10, 10), pady = (10, 10)) button_row.append(button) buttons.append(button_row) RE: Programmatically creating buttons that remember their positions - DeaD_EyE - Jun-21-2023 With a trick, you could use command with partial, to call the callback together with the instance of the button itself as the first argument. Example: from tkinter import Tk, Button from tkinter.messagebox import showinfo from itertools import product from functools import partial class Gui(Tk): def __init__(self): super().__init__() self.setup() self.selected: None | Button = None def setup(self): for number, (col, row) in enumerate(product(range(10), range(5))): button = Button(self, text=f"Button {number:02d}") # callback, which also submits the button innstance to the function button["command"] = partial(self.clicked, button) button.grid(row=row, column=col) def clicked(self, button: Button): print(button["text"]) if self.selected: self.selected, selected = None, self.selected grid1 = button.grid_info() grid2 = selected.grid_info() button.grid_configure(row=grid2["row"], column=grid2["column"]) selected.grid_configure(row=grid1["row"], column=grid1["column"]) showinfo( "Button position changed", f"{selected['text']} swapped with {button['text']}", ) else: self.selected = button if __name__ == "__main__": Gui().mainloop() RE: Programmatically creating buttons that remember their positions - Clunk_Head - Jun-21-2023 (Jun-21-2023, 05:09 PM)DeaD_EyE Wrote: With a trick, you could use command with partial, to call the callback together with the instance of the button itself as the first argument. Thank you. Looks like I can't get there from here without reinventing the wheel. Guess that rules out tkinter. Is there a GUI alternative that is natively capable of achieving my goal? RE: Programmatically creating buttons that remember their positions - Gribouillis - Jun-21-2023 You could also store the row and column as attributes in the Button instance from functools import partial from tkinter import * root = Tk() root.title(" ") root.geometry("250x250+0+20") def button_print(button): print(f"{button.row + 1}, {button.column + 1}") buttons = [] for grid_row in range(3): button_row = [] for grid_column in range(3): button = Button(root, text = f"{grid_row + 1} {grid_column + 1}", width = 4, height = 3) button.row = grid_row button.column = grid_column button.configure(command=partial(button_print, button)) button.grid(row = grid_row, column = grid_column, padx = (10, 10), pady = (10, 10)) button_row.append(button) buttons.append(button_row) root.mainloop() (Jun-21-2023, 06:29 PM)Clunk_Head Wrote: Is there a GUI alternative that is natively capable of achieving my goal?I think all GUIs including tkinter allow you to attach client data to a widget and to retrieve this client data when a callback is invoked. RE: Programmatically creating buttons that remember their positions - Clunk_Head - Jun-21-2023 (Jun-21-2023, 06:54 PM)Gribouillis Wrote: You could also store the row and column as attributes in the Button instance That's fantastic, but both solutions provided had a commonality that ended up being the key to the solution that I most desired: from functools import partial from tkinter import * root = Tk() root.title(" ") root.geometry("250x250+0+20") def button_print(row, column): print(f"{row + 1}, {column + 1}") buttons = [] for grid_row in range(3): button_row = [] for grid_column in range(3): button = Button(root, text = f"{grid_row + 1} {grid_column + 1}", width = 4, height = 3, command = partial(button_print, grid_row, grid_column)) button.grid(row = grid_row, column = grid_column, padx = (10, 10), pady = (10, 10)) button_row.append(button) buttons.append(button_row) root.mainloop()The use of partial returned a unique function with the parameters hard wired, which is what I failed to do with the lambda. Thank you both very much for your help. I guess the last question for completeness, did my first code create one lambda that was referenced by all buttons, or did it crate a lambda for each function that all referenced the same variables? RE: Programmatically creating buttons that remember their positions - Gribouillis - Jun-22-2023 (Jun-21-2023, 08:20 PM)Clunk_Head Wrote: did my first code create one lambda that was referenced by all buttons, or did it crate a lambda for each function that all referenced the same variables?It created a lambda for each button that all referenced to the same variables as in this example >>> def func(): ... var = 3 ... spam = lambda: var ... var = 4 ... ham = lambda: var ... return spam, ham ... >>> f, g = func() >>> f is g False >>> f.__closure__ (<cell at 0x7f3135163160: int object at 0x7f3135374150>,) >>> g.__closure__ (<cell at 0x7f3135163160: int object at 0x7f3135374150>,) >>> f.__closure__[0].cell_contents 4 >>> g.__closure__[0].cell_contents 4 >>> RE: Programmatically creating buttons that remember their positions - deanhystad - Jun-22-2023 When you use a variable in a lambda expression, the expression creates a closure, an expression, and the relevant context required to evaluate the expression. In this example, the lambda expression "p" creates a closure that contain the variables x and y. A partial function does something different. When you build a partial function, any variables passed to the constructor are evaluated, and the value is passed to the constructor (just like any Python function call). The partial never knew about x or y. The partial() constructor was passed 0 and 1, not x and y.
|