I want to have multiple dropdown lists as a part of my application but some of them do not work and I don't know why. The code for that is:
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.app import App
class MidTopBar():
def __init__(self):
self.dropDownLabels = {
"File" : ['Example1', 'Example2','Example3', 'Example4'],
"Edit" : ['Example5', 'Example6','Example7', 'Example8'],
"View" : ['Example9', 'Example10','Example11', 'Example12'],
"Help" : ['Example13', 'Example14','Example15', 'Example16'],
}
self.generate()
def _generateDropdown(self, name, list):
dropdown = DropDown(auto_width = True,
width = 300)
for index in range(len(list)):
btn = Button(text=list[index],
halign = 'left',
valign = 'center',
padding_x= 20,
size_hint_y=None,
background_color = (1, 1, 1, 1))
btn.bind(on_release=lambda btn: dropdown.select(list[index]))
btn.bind(size= btn.setter('text_size'))
dropdown.add_widget(btn)
mainbutton = Button(text = name)
dropdown.bind(on_select=lambda instance, x: setattr(mainbutton, 'text', x))
mainbutton.bind(on_release=dropdown.open)
return mainbutton
def _dropDowns(self):
dropDownList = []
for key in self.dropDownLabels:
dropDownList.append(self._generateDropdown(name= key, list = self.dropDownLabels[key]))
return dropDownList
def generate(self):
dropDownList = self._dropDowns()
self.Box = BoxLayout()
for dropDown in dropDownList:
self.Box.add_widget(dropDown)
self.emptyBox = BoxLayout()
self.superBox = BoxLayout(orientation='vertical')
self.superBox.add_widget(self.Box)
self.superBox.add_widget(self.emptyBox)
class Example(App):
def build(self):
midTopBar :object = MidTopBar().superBox
return midTopBar
# Run the App of TextInput Widget in kivy
if __name__ == "__main__":
Example().run()
I'm expecting all 4 lists in the constructor to be shown in their respective key buttons but only one or occasionally 2 works.
I believe you are suffering from trash collection. Python automatically frees objects that have no saved reference in the code. Examples are your MidTopBar
instance in your build()
method, and the dropdown
in your _generateDropdown()
method. I have modified your code to save references to those objects, and it seems to fix it. First, I modified your build()
method:
def build(self):
# midTopBar: object = MidTopBar().superBox
# return midTopBar
self.mtb = MidTopBar() # prevent trash collection of MidTopBar instance
return self.mtb.superBox
And added a list to hold references to the DropDown
instances. I the 'init()` method:
class MidTopBar():
def __init__(self):
self.dds = []
Then in the _generateDropdown()
method, actually save the references:
def _generateDropdown(self, name, list1):
dropdown = DropDown(auto_width=True,
width=300)
self.dds.append(dropdown) # just to protect from trash collection
There is another issue with the code line:
btn.bind(on_release=lambda btn: dropdown.select(list[index]))
Because of the nature of lambda
, this will always result in the last element of the list
being selected. To correct this, use a temporary variable:
btn.bind(on_release=lambda btn, t=text: dropdown.select(t))
This will result in the correct item being selected and shown in the mainbutton
.
This is how Dropdowns
are typically used, with the mainbutton
showing the current selection. If you don't want the selection shown, you can replace the line:
btn.bind(on_release=lambda btn, t=text: dropdown.select(t))
with:
btn.bind(on_release=dropdown.dismiss)