I am trying to build a drop down list using DropDown
, the widget should behave similar to the one seen on windows File Explorer.
I have a TextInput
and a Button
to which I associate the DropDown
. When I run the following code, I get the error:
Exception has occurred: AttributeError
'NoneType' object has no attribute 'text'
File "C:\Users\hanne\Documents\_Educational\_Learning\Python\Kivy\DropDown\B.ii\main.py", line 34, in on_select
setattr(self.user_choice, 'text', data)
File "C:\Users\hanne\Documents\_Educational\_Learning\Python\Kivy\DropDown\B.ii\main.py", line 45, in on_touch_down
self.dropdown.select(self.text)
File "C:\Users\hanne\Documents\_Educational\_Learning\Python\Kivy\DropDown\B.ii\main.py", line 69, in <module>
MyApp().run()
main.py
import kivy
kivy.require('2.0.0')
from kivy.app import App
from kivy.uix.anchorlayout import AnchorLayout
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.uix.label import Label
from kivy.properties import ObjectProperty
class TextEntryDropDown(BoxLayout):
user_choice = ObjectProperty(None)
drop_button = ObjectProperty(None)
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.dropdown = DropDownList(self.user_choice)
for item in ['Option 1', 'Option 2', 'Option 3']:
label = DropDownItem(self.dropdown, text=item, size_hint_y=None, height=30)
self.dropdown.add_widget(label)
class DropDownList(DropDown):
def __init__(self, user_choice, **kwargs):
super().__init__(**kwargs)
self.user_choice = user_choice
def on_select(self, data):
setattr(self.user_choice, 'text', data)
class DropDownItem(Label):
def __init__(self, dropdown, **kwargs):
super().__init__(**kwargs)
self.dropdown = dropdown
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
self.dropdown.select(self.text)
return super().on_touch_down(touch)
class DropButton(Button):
def on_touch_down(self, touch):
if self.collide_point(*touch.pos):
dropdown = self.parent.dropdown
dropdown.open(self)
return super().on_touch_down(touch)
class MainApp(AnchorLayout):
pass
class MyApp(App):
def build(self):
return MainApp()
if __name__ == '__main__':
MyApp().run()
my.kv
<MainApp>:
anchor_x: 'left'
anchor_y: 'top'
TextEntryDropDown:
<TextEntryDropDown>
size_hint_y: None
height: 30
user_choice: user_choice
drop_button: drop_button
TextInput:
id: user_choice
text: 'Select an option..'
size_hint: (None, None)
height: 30
width: root.width - drop_button.width
DropButton:
id: drop_button
size_hint: (None, None)
height: user_choice.height
width: user_choice.width
I'm trying to trace down why user_choice
is None
, I'm expecting it to be a reference to the TextInput
defined in my.kv
. Any idea why this might be? I can't put my finger on it.
Thank you!
The problem is that the ObjectProperties
(user_choice
and drop_button
) of the TextEntryDropDown
are not yet available in the __init__()
method of TextEntryDropDown
. A work around is to pass a reference to the TextEntryDropDown
when you create the DropDownList
, instead of passing the not yet defined user_choice
. Like this:
class TextEntryDropDown(BoxLayout):
user_choice = ObjectProperty(None)
drop_button = ObjectProperty(None)
def __init__(self, **kwargs):
print('TextEntryDropDown.__init__(), self.user_choice =', self.user_choice)
super().__init__(**kwargs)
self.dropdown = DropDownList(self) # pass reference to this TextEntryDropDown
for item in ['Option 1', 'Option 2', 'Option 3']:
label = DropDownItem(self.dropdown, text=item, size_hint_y=None, height=30)
self.dropdown.add_widget(label)
Then in the DropDownList
:
class DropDownList(DropDown):
def __init__(self, ted, **kwargs):
super().__init__(**kwargs)
self.textEntryDropDown = ted # save reference to the TextEntryDropDown
# self.user_choice = user_choice
def on_select(self, data):
self.textEntryDropDown.user_choice.text = data # use TextEntryDropDown to access now available user_choice ObjectProperty
# setattr(self.user_choice, 'text', data)