2

I am making a python app with a Tkinter GUI. So far it has some dynamically created listboxes which I will link all to one scrollbar. I need a way to let yscroll() know which listbox has been scrolled. Passing the i variable to yscroll() does not work.

from Tkinter import *

class MyApp(Tk):
    def __init__(self):
        Tk.__init__(self)
        self.title(' - My App - ')

        self.listboxes = []
        for i in xrange(5):
            lb = Listbox(self, yscrollcommand=lambda i, *args: self.yscroll(i, *args))
            for x in xrange(30):
                lb.insert('end', x)

            lb.pack(fill='y', side='left')
            self.listboxes.append( lb )

        self.scrollbar = Scrollbar(self, orient='vertical')
        self.scrollbar.pack(side='right', fill='y')

    def yscroll(self, i, *args):
        print i
        print args

ma = MyApp()
ma.mainloop()
D.Lion
  • 137
  • 1
  • 6
  • **TL;DR**: try `yscrollcommand=lambda i=i, *args: ...` – jonrsharpe Jun 05 '15 at 10:41
  • your suggestion `yscrollcommand=lambda i=i, *args: ...` does not work but `lb = eval( 'Listbox(self, yscrollcommand=lambda *args: ma.yscroll(%d, *args) )' % i )` does. Is there a cleaner solution? – D.Lion Jun 05 '15 at 11:46
  • Have you looked at e.g. http://effbot.org/tkinterbook/listbox.htm? This gives a recipe for scrolling multiple list boxes with one scroll bar. – jonrsharpe Jun 05 '15 at 11:49
  • yes, but those listboxes are not created dynamically – D.Lion Jun 05 '15 at 12:50
  • 1
    I don't see why that would be a problem; just iterate over `self.listboxes` in `yview`. – jonrsharpe Jun 05 '15 at 12:51
  • the effbot.org example is good for moving more than one listbox with one scrollbar but if you scroll a listbox using arrows or mouse, the others do not follow. hence the need for a second function. – D.Lion Jun 05 '15 at 13:12

1 Answers1

2

It seems like the usual recipe for using a variable from a loop in a lambda does not work properly when used together with parameter unpacking with *args. But since *args seems to always consist of two values in this case (the beginning and the end of the viewport, unless I'm mistaken), you could rewrite your lambda like this:

lb = Listbox(self, yscrollcommand = lambda x, y, i=i: self.yscroll(i, x, y))

Or you can create a sort of "meta lambda", creating a lambda with the correct value for i

make_callback = lambda i: lambda *args: self.yscroll(i, *args)
lb = Listbox(self, yscrollcommand = make_callback(i))

Both will bind i to the value of i in the iteration when the lambda was defined.

Community
  • 1
  • 1
tobias_k
  • 81,265
  • 12
  • 120
  • 179