I'm having the problem where the application is running the on_press command of a button immediately before anything else happens. If I us a .kv for the layout it works fine, but I want to be able to manage the buttons using a simple list.
class AppBase(Widget):
def Launcher(self, launchapp):
os.system(launchapp)
def BuildLayout(self):
layout = GridLayout( rows=4, row_force_default = True, row_default_height = 100, col_force_default = True, col_default_width = 300 )
with open('config.txt', 'rb') as f:
reader = csv.reader(f, delimiter="|")
for row in reader:
launchbutton = Button( text = row[0], background_normal = 'tile.png', on_press = self.Launcher(row[1]) )
layout.add_widget(launchbutton)
return layout
class MyApp(App):
def build(self):
Config.set('graphics', 'width', 1920)
Config.set('graphics', 'height', 400)
return AppBase().BuildLayout()
if __name__ == '__main__':
MyApp().run()
You're not passing a callback into Button
, you're actually executing the function at that point. Change this:
launchbutton = Button( text = row[0], background_normal = 'tile.png',
on_press = self.Launcher(row[1])
)
To this:
launchbutton = Button( text = row[0], background_normal = 'tile.png',
on_press = lambda: self.Launcher(row[1])
)
Now you're passing in an unnamed function that will call self.Launcher
when an on_press
event is raised, rather than setting it to the return result of self.Launcher
when the Button
is created.
Update: for some reason, on_press
and on_release
events aren't actually assigned to callbacks in Button.__init__
, the events themselves are just registered with no outcome. (This seems a bug to me, but I'm not familiar enough with Kivy to say for certain.) You need to explicitly bind
the callback for it to work:
launchbutton = Button( text = row[0], background_normal = 'tile.png' )
launchbutton.bind( on_press = lambda widget: self.Launcher( row[1] ) )
Note that the callback actually receives an argument, which I've included as widget
in the lambda.
Update 2: I should have caught this earlier, sorry, but I had reduced my local test case down to one button. When you do this in a loop:
funcs = []
for x in xrange(10):
funcs.append( lambda: x)
Every call to funcs[n]()
where n in [0..9]
will return 9
, and not the value of n
as expected. The lambda has created a closure which includes x
from the surrounding scope. However, the value of that x
changes over the course of the loop, and by the end it is 9
. Now all lambdas in funcs
are holding a reference to 9
. You can avoid this by adding the value you want to the lambda's local scope:
funcs.append( lambda x=x: x)
This points the lambda local variable x
at the same object as is referred to by the loop variable x
in the outer scope. It's more obvious what happens if we use different variable names:
funcs.append( lambda inner_x=x: inner_x)
But the x=x
form is very common in this case. So, to ensure that each button uses the correct value, you should be able to do:
launchbutton.bind( on_press = lambda widget, appname=row[1]: self.Launcher( appname ) )
Here, you bind the current value of row[1]
to appname
in the lambda's local scope, so that's what it will pass to Launcher
when it's called.