Search code examples
pythontkintercanvassliderscrollbar

Tkinter problem to get the values for more than one slider in a scrollable canvas


I can not fetch the values correctly for the different scalebars I created in an scrollable canvas. If I move the sliders it changes allways the last entry. Please advise if you have ideas to that code.

It prints out the changed number but is not placed in the correct position of u[x]

Print out example changed first slider to 1

1 correct (print(val)) {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: '1'} false should be (print(str(u)) {0: '1', 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0}

import tkinter as tk
from tkinter import ttk
global II
label={}
scale={}
u={}

class Scrollable(tk.Frame):

    def __init__(self, frame, width=32):

        scrollbar = tk.Scrollbar(frame, width=width)
        scrollbar.pack(side=tk.RIGHT, fill=tk.Y, expand=False)

        self.canvas = tk.Canvas(frame, yscrollcommand=scrollbar.set)
        self.canvas.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

        scrollbar.config(command=self.canvas.yview)

        self.canvas.bind('<Configure>', self.__fill_canvas)

        # base class initialization
        tk.Frame.__init__(self, frame)

        # assign this obj (the inner frame) to the windows item of the canvas
        self.windows_item = self.canvas.create_window(0,0, window=self, anchor=tk.NW)


    def __fill_canvas(self, event):
        canvas_width = event.width
        self.canvas.itemconfig(self.windows_item, width = canvas_width)

    def update(self):
        print(scale)
        self.update_idletasks()
        self.canvas.config(scrollregion=self.canvas.bbox(self.windows_item))

root = tk.Tk()
body = ttk.Frame(root)
body.pack()

def print_value(val):
    print(val)
    u[II]=val
    print(str(u))

scrollable_body = Scrollable(body, width=32)
#scrollable_body2 = Scrollable(body, width=32)
print(scrollable_body)
for II in range(10):
    x={II:0}
    u.update(x)
    label[II]=tk.Label(scrollable_body, text="Hello "+str(II)).grid(column=0)
    scale[II]=tk.Scale(scrollable_body, from_=-10, to=20,sliderlength=60, length=300, orient=tk.HORIZONTAL, command=print_value).grid(column=2)

scrollable_body.update()
root.mainloop()

Solution

  • Note that variable II will be 9 after the for loop, so u[II] inside print_value() will be the same as u[9] when the function is executed.

    One of the workaround is binding default value of lambda argument to the required value as below:

    def print_value(idx, val):
        print(idx, val)
        u[idx] = int(val)
        print(u)
    
    for II in range(10):
        x={II:0}
        u.update(x)
        #u[II] = 0    # the above two lines can be replaced by this line actually
        label[II]=tk.Label(scrollable_body, text="Hello "+str(II))
        label[II].grid(row=II, column=0)
        scale[II]=tk.Scale(scrollable_body, from_=-10, to=20, sliderlength=60, length=300, orient=tk.HORIZONTAL,
                           # use default value of argument to bind the required value and pass it to print_value()
                           command=lambda val, idx=II: print_value(idx, val)) 
        scale[II].grid(row=II, column=2)