0

I am using a for loop and I would like to pass an individual argument to each separate button (in tkinter), this is for a calculator. However, I am unable to find a solution, to make my problem clear I will show the relevant code:

import tkinter as tk

entry_box = tk.Entry(master,width=35)

def insert_value(value):
    entry_box.insert(0,value)   

buttons = []
for i in range(0,10):
    a = tk.Button(master,text=str(i),command=lambda:insert_value(str(i)))
    buttons.append(a)

Note: I have removed the .grid() statements in the loop as it removes quite a large and unnecessary portion of code. And also I want each button to insert its displayed value so button 1 would insert 1, 2: 2 etc.

The problem occurs when passing "i" to insert value, as the value inserted into the entry box is always 9. I think this is because "i" will always end on 9 in the loop.

However, I can not find a solution to this. I have tried getting values from lists. I can't think of any other way to do this( in a loop that is). I know I could just write the statements out 10 times but I am interested to know whether their is a solution.

TL;DR The value passed to insert value is always 9. Is there a way to insert the value that each button displays (1 to 9) into the entry box using a for loop?

Regardless of whether it's right all suggestions would be interesting and nice to see. Thankyou.

2 Answers2

2

Since i is a free variable, it isn't evaluated until the callback is actually called, not when the function is first defined.

One hack is to pass the loop index as the default value of an otherwise unused function parameter. Note that you never need the integer i, only the string str(i), so we make the replacement before we call tk.Button.

buttons = []
for i in range(0,10):
    i = str(i)
    a = tk.Button(master,text=i, command=lambda i=i:insert_value(i))
    buttons.append(a)

Another solution is to explicitly define a factory function to create the callback. make_callback is almost identical to the function created by your lambda expression, except it returns a closure around its argument rather than using default parameter values.

def make_callback(i):
    return lambda: insert_value(i)

buttons = []
for i in range(0,10):
    i = str(i)
    a = tk.Button(master,text=i, command=make_callback(i))
    buttons.append(a)

In fact, if you wanted, you could "inline" make_callback using nested lambda expressions, though I wouldn't recommend it for readability.

buttons = []
for i in range(0,10):
    i = str(i)
    a = tk.Button(master,text=i, command=(lambda x: lambda: insert_value(x))(i))
    buttons.append(a)
chepner
  • 497,756
  • 71
  • 530
  • 681
0

below an oo solution.

#!/usr/bin/python3
import tkinter as tk
from tkinter import ttk
from tkinter import messagebox

class App(tk.Tk):

    def __init__(self):
        super().__init__()

        self.protocol("WM_DELETE_WINDOW", self.on_exit)

        self.count = tk.IntVar()
        self.set_title()
        self.set_style()
        self.init_ui()

    def init_ui(self):

        self.f = ttk.Frame()

        self.label = ttk.Label(self.f, text="Hello, world!")
        self.entry_box = ttk.Entry(self.f,textvariable=self.count)
        self.entry_box.bind('<Return>', self.on_load_buttons)

        self.label.grid(row=0,column=1, sticky=tk.W)
        self.entry_box.grid(row=1, column=1, sticky=tk.W)

        self.f.grid(row=0, column=0, sticky=tk.N+tk.W+tk.S+tk.E)

    def on_load_buttons(self, evt=None):

        if self.count.get():
            for i in range(0,self.count.get()):        
                ttk.Button(self.f, text=str(i),
                           command=lambda:insert_value(str(i))).grid(row=i,
                                                                     column=3,
                                                                     sticky=tk.W+tk.E,
                                                                     padx=5, pady=5)

    def set_style(self):
        self.style = ttk.Style()
        self.style.theme_use("clam")

    def set_title(self):
        s = "{0}".format('Hello World')
        self.title(s)

    def on_exit(self):
        """Close all"""
        if messagebox.askokcancel(self.title(), "Do you want to quit?", parent=self):
            self.destroy()               

if __name__ == '__main__':
    app = App()
    app.mainloop()
1966bc
  • 1,148
  • 1
  • 6
  • 11