0
def EditItem(product):
   print(product)

editbase = Tk()
editbase.title("Edit Item")
editbase.eval('tk::PlaceWindow . center')

main_frame = Frame(editbase)
main_frame.pack(fill=BOTH, expand=1)

my_canvas = Canvas(main_frame)
my_canvas.pack(side=LEFT, fill=BOTH, expand=1)

my_scrollbar = ttk.Scrollbar(main_frame, orient=VERTICAL, command=my_canvas.yview)
my_scrollbar.pack(side=RIGHT, fill=Y)

my_canvas.configure(yscrollcommand=my_scrollbar.set)
my_canvas.bind('<Configure>', lambda e: my_canvas.configure(scrollregion=my_canvas.bbox("all")))

second_frame = Frame(my_canvas)

my_canvas.create_window((0, 0), window=second_frame, anchor="nw")

This is where I'm troubled.

text_file = open('Cashier.txt') # I know it's better to use [with open()]
productname = []
counter = 0
for line in text_file:
  print(counter)
  line = line.strip('\n')
  product = line.split("=")
  productname.append(product[0])
  productprice = product[1]
  Button(second_frame, text=productname[counter], width=35, height=2, font=('Arial', 13, 'bold'), 
  command = lambda: EditItem(productname[counter])).grid(
                            row=counter, column=0, pady=10, padx=10)
  counter += 1
counter = 0

exitbutton = Button(editbase, text="Exit", font=('Arial', 12), width=20, command=editbase.destroy)
exitbutton.pack(pady=10)

editbase.mainloop()

when you open this code there will be rows of product button names for example

Milk

Coffee

Chocolate

I want to make it so that when I press the Coffee Button then the product in the EditItem will be Coffee and not Milk because the counter after the for loop will always be 0 and it seems that the button will not pass down its current counter. And so the command will pass EditItem(productname[0]) instead of the EditItem(productname[currentcounter #Example only].

I also can't make an individual button because the amount of product is dynamic.

PCM
  • 2,881
  • 2
  • 8
  • 30
ronron681
  • 29
  • 4
  • Please provide a runnable [mre]. I also see nothing related to a scrollbar in it now. – martineau Dec 11 '21 at 07:38
  • @martineau It is updated. – ronron681 Dec 11 '21 at 07:45
  • Sorry, that's still not runnable — which means complete enough that someone else can copy and paste the code on their own machine and execute it. For a tkinter program that implies there will be a call to `mainloop()` somewhere in it, for example. – martineau Dec 11 '21 at 07:48

2 Answers2

0

I have found the answer! Sorry, this was just a redundant question. Here's the answer tkinter creating buttons in for loop passing command arguments

lambda i = counter: EditItem(productname[i])

ronron681
  • 29
  • 4
0

The problem is due to closure which means that a reference to the value of the variable counter is what's getting used by your lambda callback function and that value will always be the final value of the variable at the end of the for loop when it finishes executing.

The usual way to avoid the issue is to assign a default value to the function argument which will "capture" the variable's value at the time the function is created. The code below shows how to do that in the for loop at the end of your code.

Note I have also streamlined a few other things I noticed being done inefficiently / awkwardly in the block.

...

with open('Cashier.txt') as text_file:
    productname = []
    for counter, line in enumerate(text_file):
      print(counter)
      line = line.strip('\n')
      product = line.split("=")
      productname.append(product[0])
      productprice = product[1]
      Button(second_frame, text=productname[counter], width=35, height=2,
             font=('Arial', 13, 'bold'),
             command=lambda counter=counter:  # NOTE DEFAULT ARGUMENT VALUE ADDED.
                EditItem(productname[counter])).grid(row=counter, column=0, pady=10, padx=10)

exitbutton = Button(editbase, text="Exit", font=('Arial', 12), width=20,
                    command=editbase.destroy)
exitbutton.pack(pady=10)
editbase.mainloop()

martineau
  • 119,623
  • 25
  • 170
  • 301