2

I run into small problem trying to create 0-9 number buttons. I want to define 10 buttons for numbers from 0 to 9 in single loop. Each of them is supposed to add its value to self.user_input = tk.StringVar() which will be printed in label. Clicking 5 button, 7 button and then 0 button will give output 570. I try to use lambda to create command for each button, but instead of getting different values I have 9 everywhere. Here is my code:

import tkinter as tk
import tkinter.ttk as ttk

class Gui(tk.Tk):
    def __init__(self):
        super().__init__()
        self.user_input = tk.StringVar()
        tk.Label(self, textvariable=self.user_input).grid()
        self.create_buttons()

    def create_buttons(self):
        for x in range(10):
            ttk.Button(self, text=x, command=lambda: self.user_input.set(self.user_input.get() + str(x))).grid()

app = Gui()
app.mainloop()

How can I fix code above to make it work as expected (description up)?

Ethr
  • 431
  • 1
  • 3
  • 17
  • Are you sure the code you posted is faulty ? It show the 9 different buttons on my machine (Ubuntu 19.10, python 3.7.5) – Grégoire Roussel Nov 22 '19 at 09:55
  • @Sayse Okay thanks, I will add create_buttons method to this class, but it will still include lambdas. How can I correct it? I want to have buttons from 0 to 9 where each of them update one label which refers to StringVar – Ethr Nov 22 '19 at 09:56
  • I'm not sure which you're asking, asking 4 different questions is very broad. #3 would be `(y := self.user_input).set(f'{y.get()}x')` – Sayse Nov 22 '19 at 09:58
  • @Sayse I edited this question and made it more precise. – Ethr Nov 22 '19 at 10:10
  • 1
    Change `lambda: ...` to `lambda x=x: ...`. – acw1668 Nov 22 '19 at 10:13
  • 1
    @acw1668 Thank you, it solved my problem. Can you add it as answer and describe a little to me why it is working like that or link to some lecture. I will accept it as answer to this question. – Ethr Nov 22 '19 at 10:19
  • 1
    @Ethr ***"why it is working "***: Read [Python and Tkinter lambda function](https://stackoverflow.com/a/11005426/7414759) – stovfl Nov 22 '19 at 11:12

1 Answers1

2

Put the bulk of your code in a proper function. A best practice is that a lambda for a widget callback should only ever call a single function. Complex lambdas are difficult to read and difficult to debug.

The second part of the solution is to create a closure. The trick for that is to make the variable you're passing in bound to the lambda as a default argument.

The callback looks something like this:

def callback(self, number):
    new_value = self.user_input.get() + str(number)
    self.user_input.set(new_value)

Defining each button looks something like this:

def create_buttons(self):
    for x in range(10):
        button = ttk.Button(self, text=x, command=lambda number=x: self.callback(number))
        button.grid()

Pay particular attention to number=x as part of the lambda definition. This is where the current value of x is bound to the number parameter inside the lambda.

Bryan Oakley
  • 370,779
  • 53
  • 539
  • 685