0

I am trying to develop a GUI in Python with TKinter. I have a grid of buttons and I want to write some code which enables me to write callback functions in a systematic way, rather than having to manually define 13 callback functions for each of the buttons. (There are 13 of them in this grid.)

I am fairly sure this is a standard pattern, I just don't know how to do it in Python.

I guess that it involves some way of passing data to a single callback function? I searched how to do this and the easiest method appeared to be to use a lambda. (Which I am familiar with from C++.)

I'm not quite as hot on Python however, and this first attempt didn't work. If you run it you should find that it always prints "Button 12" regardless of which button is pressed.

I think it should be fairly obvious from the code what this is supposed to do?

Perhaps the callback function / lambda has to refer to a non-member function? Decided to ask a question as I'm not sure how to proceed here and I don't know enough about the details of the language to figure it out.

#!/usr/bin/env python3
   
import tkinter


# this class manages the GUI
class Application(tkinter.Frame):

    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.createWidgets()


    def createWidgets(self):

        self.master.columnconfigure(0, weight=1)
        self.master.columnconfigure(1, weight=1)
        self.master.columnconfigure(2, weight=1)
        self.master.columnconfigure(3, weight=1)
        self.master.columnconfigure(4, weight=1)
        self.master.columnconfigure(5, weight=1)
        self.master.rowconfigure(0, weight=1)
        self.master.rowconfigure(1, weight=1)
        self.master.rowconfigure(2, weight=1)
        self.master.rowconfigure(3, weight=1)
        self.master.rowconfigure(4, weight=1)

        self.num_buttons = 13

        button_text = []
        for i in range(self.num_buttons):
            text = "Button " + str(i)
            button_text.append(text)

        self.buttons = []
        for i in range(self.num_buttons):
            self.buttons.append(None)
            self.buttons[i] = tkinter.Button(self.master, text=button_text[i], command=lambda: self.clickButton(i), height=1, width=8)
            c = i % 6
            r = i // 6 + 1
            self.buttons[i].grid(row=r, column=c)



    def clickButton(self, args):
    
        print(self.buttons[args].cget("text"))



def main():

    root = tkinter.Tk()
    root.title("Application")
    root.geometry("800x600")
    app = Application(master = root)

    while True:
        app.update_idletasks()
        app.update()


if __name__ == '__main__':
    main()
FreelanceConsultant
  • 13,167
  • 27
  • 115
  • 225
  • Try changing `command=lambda: self.clickButton(i)` to `command=lambda i=i: self.clickButton(i)` – TheLizzard May 03 '21 at 19:23
  • @TheLizzard vey interesting, I guess that has something to do with capturing local variables, but why does it work exactly? – FreelanceConsultant May 03 '21 at 20:16
  • Python's `lambda` resolves variable names when it is called. That is why your method doesn't work. The `i=i` forces the `lambda` to store the value of `i` in itself. Think of it as a function definition and it uses the value for `i` in the `createWidgets` method. I am not 100% sure if my explanation is correct so someone needs to double check me. – TheLizzard May 03 '21 at 20:19
  • @TheLizzard seems like a sensible explanation - would make sense if this is the case – FreelanceConsultant May 03 '21 at 21:22
  • Loot at [this](https://stackoverflow.com/a/938493/11106801) if you still need more info on this problem. – TheLizzard May 04 '21 at 09:15

0 Answers0