Question

I've searched for solutions to this and have tried to implement them, but am not having any success getting a tkinter Scale widget to call a method by setting the command keyword argument.

I am storing the widget objects in lists in a dictionary. The method is called when creating the lists and does indeed contain the correct objects. I'm thinking scope might be the issue here.

import Tkinter as Tkinter
from Tkinter import *
import ttk as ttk

class App:

    prefs = {
        "Fred": "Dead", "Kims": "Limbs"
    }

    def __init__(self, root):
        self.root = root
        self.testiter=-1
        lControls = {}
        r=0  ;  p=-1  ;  self.pSlider = 0  ;  self.pLabel = 0
        for key, var in App.prefs.items():
            lControls[key] = [] #add empty list for this key 
            p+=1
            self.pSlider=p
            s=Scale(root, from_=1, to=16, orient="horizontal",\
                    command= lambda x:self.UpdateValues(key))    #trying with 1 or no arguments
            #s=Scale(root, from_=1, to=16, orient="horizontal",\
            #        command=self.UpdateValues(key))
            #s=Scale(root, from_=1, to=16, orient="horizontal",\
            #        command=self.UpdateValues())
            #s=Scale(root, from_=1, to=16, orient="horizontal",\
            #        command=self.UpdateValues)   # <<< gives error "TypeError: UpdateValues() takes exactly 1 argument (2 given)"
            #                                         
            lControls[key].append(s)
            s.grid(row=r,column=0)

            p+=1
            self.pLabel=p
            l=Label(root, text="AAArrrgggghhhH!")
            lControls[key].append(l)
            l.grid(row=r, column=1)
            r+=1


    def UpdateValues(self, key):  #trying this with just self too
        self.testiter+=1
        print(self.testiter)


root = Tk()
App(root) 
root.mainloop()
Was it helpful?

Solution

When UpdateValues is called, the lambda expression uses the value of key as it last existed. In particular, no matter which slider you click, key will be whatever value was iterated through last in your loop (probably "Kims").

The typical solution for this is to put a default argument on your lambda, so that the value binds early and does not change in the next iteration of the loop.

s=Scale(root, from_=1, to=16, orient="horizontal",\
        command= lambda x, key=key:self.UpdateValues(key))

You can confirm that this works by changing the body of UpdateValues:

def UpdateValues(self, key):
    print(key)

When you click the top slider, it prints "Kims", and when you click the bottom slider, it prints "Fred". (Or vice-versa, since the order of iteration of a dict is implementation-dependent)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top