Kivy: iterate list with lambda - python

I want to iterate through a list of button and bind each button with a different function. But the result is always the last Button's function, not all in the list.
Here's my code:
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.label import Label
from kivy.uix.stacklayout import StackLayout
from kivy.uix.button import Button
class MyApp(App):
def build(self):
parent = StackLayout()
button_list = []
for i in range(0,11):
button_list.append( Button(text = str(i), size_hint = (None,0.15)) )
print i
for a_button in button_list:
parent.add_widget(a_button)
a_button.bind( on_press = lambda x: parent.add_widget( Label(text = a_button.text) ) )
return parent
if __name__ == "__main__":
MyApp().run()
There is some problem with my lambda function, I guessed.

This is a classic python problem, not actually related to kivy. It is discussed e.g. here.

For those still running into this problem 6 years later, here is my solution:
def buttongenerator(layout):
hellodict = {}
for k in range(50):
hellodict[str(k)] = 'Hello' + str(k)
btn = Button(text=str(k),
size_hint_y=None,
height='40dp')
btn.bind(on_release=(lambda instance, var=hellodict[str(k)]: print(var)))
layout.add_widget(btn)
Layout simply refers to a grid/box layout.
The instance variable is necessary because otherwise the button object will be passed into the lambda function and (in this case) will be printed to the console as a string. The instance variable sort of "absorbs" the button object, if that makes any sense. (The variable does not have to be named instance, it just has to be present)

Related

Kivy Buttons on_press method doesn't work

So I have written this basic kivy code. I want to read sentences from a file, and dynamically create buttons for each one of them. Then, I want these buttons to disappear when they are clicked. In a for loop, I create my buttons, and put my buttons with the index i in a list. Then with the on_press method, it should delete itself.
Button_List[i].bind(on_press= lambda x: self.remove_widget(Button_List[i]))
So there is a Button in Button_List[i] , and when it is clicked, it should run:
self.remove_widget(Button_List[i])
so it should delete itself
I have 5 buttons for example, the problem is, that it whichever button I click, it deletes the button with the highest index. And the other buttons dont get deleted. I feel like kivy is only executing the last index, but I am not sure.
Here is my code:
new.py:
import kivy.uix.button as kb
from kivy.app import App
from kivy.uix.widget import Widget
from Nils_Programm_verkürzt import lektionstextlesen
from kivy.uix.gridlayout import GridLayout
sentences = ['example_sentence1','example_sentence2','example_sentence3','example_sentence4','example_sentence5',]
class Button_Widget(Widget):
def __init__(self, **kwargs):
super(Button_Widget, self).__init__(**kwargs)
global Button_List
Button_List = []
for i in range(len(sentences)):
print(i)
Button_List.append(kb.Button(text=sentences[i],pos=(self.width * i, self.height * i)))
Button_List[i].size = 50, 50
print('binding'+ str(i))
Button_List[i].bind(on_press= lambda x: self.remove_widget(Button_List[i]))
print(Button_List[i])
self.add_widget(Button_List[i])
class ButtonApp(App):
def build(self):
return Button_Widget()
if __name__ == "__main__":
ButtonApp().run()
Your help is very much appreciated :).
That's a common problem when using lambda in a loop. They all get the last value of the loop. A fix is to create a new variable that holds the current loop variable value. So, try replacing:
Button_List[i].bind(on_press= lambda x: self.remove_widget(Button_List[i]))
with:
Button_List[i].bind(on_press= lambda x, j=i: self.remove_widget(Button_List[j]))

How to access a button in a for loop and bind it to on_press in kivy

I run into an error while trying to access two buttons from a group of buttons created in for loop and binding the button to on_press. Please what is the right way to do this (without .kv). How can I access individual button and bind them to different on_press event
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
class TestApp(App):
def build(self):
layout=GridLayout(cols=1)
for i in range(6):
btn = Button(text= str(i))
layout.add_widget(btn)
# error occurred here
btn[0].bind(on_press=first)
btn[1].bind(on_press=second)
def first(self):
pass
def second(self):
pass
return layout
if __name__ == '__main__':
TestApp().run()
Code involving btn[0] implies a btn list, but there is no such list in your code. Here is a modified version of your code that does what you want:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.gridlayout import GridLayout
class TestApp(App):
def build(self):
layout=GridLayout(cols=1)
for i in range(6):
btn = Button(text= str(i))
layout.add_widget(btn)
if i == 0:
btn.bind(on_press=self.first)
elif i == 1:
btn.bind(on_press=self.second)
return layout
def first(self, button_instance):
print('first')
def second(self, button_instance):
print('second')
if __name__ == '__main__':
TestApp().run()
I still haven't fully solved my problem, however, the following code returned the object created on the button and the value assigned to it. So I believe that this solution can help you.
from functools import partial
for x in test:
btn = Button(text=x["CustomerName"], background_color=[0.9, 0.9, 0.9, 0.9], size_hint_y=None, height=40)
btn.bind(on_press=partial(self.mostra,x["CustomerName"]))
self.ids.grd1.add_widget(btn)
def mostra(self,*args):
print(args)
Looking at the code I posted I used bind after creating the button (btn.bind) and in it I passed the function that shows on the screen the value saved in the x of my loop. In the function (mostra) the use of self and * args is mandatory.
At the beginning there is the line (from functools import partial) that line is necessary to execute the function show inside the bind.

(Python Kivy) Indexing Which Button Was Pressed

I am trying to figure out a way to index which button was pressed in a GridLayout so that, for instance, I can put a specific image in that button's background when it is pressed. Here is what I am currently doing, using a function just to try to print the index number as a test before adding more functionality:
for x in range(15):
self.buttons.append(Button())
self.ids.grid_1.add_widget(self.buttons[x])
self.buttons[x].background_normal = 'YOUTUBE.png'
self.buttons[x].background_down = 'opacity.png'
# Make the button switch screens to input from calling the function above
if edit_mode is True:
self.buttons[x].bind(on_release=self.SwitchScreenInput)
self.buttons[x].bind(on_release=self.HoldButtonNum(x))
def HoldButtonNum(x):
print(x)
Im getting the error:
TypeError: HoldButtonNum() takes 1 positional argument but 2 were
given
Process finished with exit code 1
I will make some observations:
If HoldButtonNum is an instance method its first parameter must be self.
You must use functools.partial or lambda functions to pass arguments to event handler.
The function must receive a third parameter which is the instance of the button that launches the event.
An example:
from kivy.app import App
from kivy.uix.gridlayout import GridLayout
from kivy.uix.button import Button
from functools import partial
class MyGridLayout(GridLayout):
cols = 5
def __init__(self):
super(MyGridLayout, self).__init__()
self.buttons = []
for x in range(15):
self.buttons.append(Button())
self.add_widget(self.buttons[x])
self.buttons[x].bind(on_release=partial(self.HoldButtonNum, x))
def HoldButtonNum(self, x, instance):
print('Button instance:', instance)
print('Button index in list:', x)
class MyKivyApp(App):
def build(self):
return MyGridLayout()
def main():
app = MyKivyApp()
app.run()
if __name__ == '__main__':
main()
When a button is pressed the output is like:
Button index in list: 1
Button instance: <kivy.uix.button.Button object at 0x0000018C511FC798>

How to change the string value in multiple places?

Let's say I have a string that I used to set a value for a few Kivy widgets. How could I make all the widgets react to the change of that string?
I could make that string a property for each widget, but if I change the property of one widget, the others with the same property aren't going to change accordingly.
The behaviour I want is like list referencing:
st = ["1"]
st1 = st
st.append("2")
print(st1, st) # output: ['1', '2'] ['1', '2']
As you can see, when I change the list st, the list st1 also changes. That's because st1 is a reference to a list. How can I make this behaviour apply to strings? Possibly using Kivy, if Python's standard library does not have a way to achieve what I want
And here is the demonstration in Kivy:
from kivy.app import App
from kivy.uix.label import Label
from kivy.uix.button import Button
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.textinput import TextInput
class TestApp(App):
def change_text(self, bt):
self.string += "!"
print(self.string)
def build(self):
self.string = "Hello"
lb = Label(text = self.string)
bt = Button(on_press = self.change_text)
tx = TextInput(text = self.string)
bl = BoxLayout()
bl.add_widget(lb)
bl.add_widget(tx)
bl.add_widget(bt)
return bl
TestApp().run()
So when the button gets pressed, the App's attribute string changes, but I want that change to be reflected on the widgets to left(Label, TextInput)
In kivy you can use properties for that (not to be confused with python properties, which are a complete different thing). In kv language you should be able to do the following:
MainWidget:
id: "main"
mystring: "asdf"
widget1:
content: main.mystring
widget2:
content: main.mystring
...
Read more in the kivy documentation for properties.

Kivy: Limit size of dropdown

Is there any way to limit the size of DropDown box? I would like to limit it to size of maybe 4-5 choices, instead of having it running all the way to the edge of screen.I started experimenting with DropDown as Spinner didn't have any interface for doing so.. Both elements in my use case should do the same, but it seemed like DropDown would provide more flexibility as I had to compose the picker myself. Unfortunately, changing hint or size for DropDown seems to do nothing, as the list still gets fully expanded.
Here is code for what I got now:
from kivy.app import App
from kivy.uix.button import Button
from kivy.uix.dropdown import DropDown
class DropDownPicker(Button):
def __init__(self, listOfChoices, **kwargs):
result = super(DropDownPicker, self).__init__(**kwargs)
self.text='Select'
pickerList = DropDown()
for item in listOfChoices:
choiceButton = Button(text=item, size_hint_y=None, height=25)
choiceButton.bind(on_release=lambda btn: pickerList.select(btn.text))
pickerList.add_widget(choiceButton)
self.bind(on_release=pickerList.open)
pickerList.bind(on_select=lambda instance, x: setattr(self, 'text', x))
pickerList.size_hint_y = None
pickerList.size = (0, 100)
return result
class TestApp(App):
def build(self):
choices = []
for x in range(50):
choices.append("Choice " + str(x))
return DropDownPicker(choices)
if __name__ == '__main__':
TestApp().run()
Those last 2 lines before return should be doing the trick, but they aren't. I also tried to specify this in DropDown constructor, however I changed this because at that time there might have been problem that final size is not yet known. This wasn't the case as it still doesn't work.
Try modyfing size_hint_y property, but not to None (it's default) but to the decimal value, for example:
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.base import runTouchApp
dropdown = DropDown()
dropdown.size_hint_y = 0.5
for index in range(100):
btn = Button(text='Value %d' % index, size_hint_y=None, height=44)
btn.bind(on_release=lambda btn: dropdown.select(btn.text))
dropdown.add_widget(btn)
mainbutton = Button(text='Hello', size_hint=(None, None))
mainbutton.bind(on_release=dropdown.open)
dropdown.bind(on_select=lambda instance, x: setattr(mainbutton, 'text', x))
runTouchApp(mainbutton)

Categories

Resources