
Question:
I've reproducable difficulties using ScreenManager and DropDown, if there are screens before the screen with the DropDown. I'm struggling with this for days now, since I'm a beginner, I assumed it's my fault here.<br /> I melted the code down to the core of the problem, so that my intended functionality is lost. Intended is an incremental search field, such that only appropriate options show up in the dropdown list. That's why I need an input widget bound to the dropdown-buttons. I've a solution for that, it's just not working as expected with the screenmanager.<br /> Consider this code, please:
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.textinput import TextInput
from kivy.properties import ListProperty, StringProperty
import re
from kivy.lang import Builder
Builder.load_string('''
<Intro>:
BoxLayout:
Button:
text: 'Press to go to SecondScreen'
font_size: '20px'
on_release: root.manager.current = 'SecondScreen'
<SecondScreen>:
ComboLayout:
Label:
text: "Dropdown on SecondScreen \\n\\n if ComboEdit.text doesn't have \\n a non-empty string assigned \\n code is broken!"
font_size: '20px'
ComboEdit:
size_hint: .5, .5
pos_hint: {'center':(.5, .5)}
on_text: self.parent.on_text(self, args[1])
text: 'X' ## <<<=== THIS IS NECESSARY, REALLY!?!
font_size: '100px'
multiline: False
''')
class ComboEdit(TextInput):
options = ListProperty(('',))
def __init__(self, **kw):
ddn = self.drop_down = DropDown()
ddn.bind(on_select=self.on_select)
super(ComboEdit, self).__init__(**kw)
def on_options(self, instance, value):
ddn = self.drop_down
ddn.clear_widgets()
for option in value:
but = Button(text=option,
size_hint_y=None,
height='36sp',
on_release=lambda btn: ddn.select(btn.text))
ddn.add_widget(but)
def on_select(self, instance, value):
self.text = value
class ComboLayout(BoxLayout):
def on_text(self, instance, value):
instance.options = [str(i) for i in range(0,8)]
instance.drop_down.open(instance)
class Intro(Screen):
pass
class SecondScreen(Screen):
pass
class BugDemoApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(Intro(name='Intro'))
sm.add_widget(SecondScreen(name='SecondScreen'))
return sm
if __name__ == '__main__':
BugDemoApp().run()
By accident I found: I need to assign some non-empty string to ComboEdit.text, if not - making it a comment or assigning "" - I get this traceback:
File "C:/Users/ORANG/PycharmProjects/waldi/playground/widgets.py", line 56, in on_text
instance.drop_down.open(instance)
File "C:\Kivy-1.9.0-py2.7-win32-x64\kivy27\kivy\uix\dropdown.py", line 215, in open
'Cannot open a dropdown list on a hidden widget')
kivy.uix.dropdown.DropDownException: Cannot open a dropdown list on a hidden widget
My questions are:
1) is it maybe a bug or am I doing something wrong here?
2) if it is a bug, where is the right place to announce it?
3) at the moment most important for me actually: now the user has to delete that given string in ComboEdit.text from the editfield by hand to replace it with his own input. This is so ugly. Do you have an idea for a workaround? Is it possible to make the dummy-text selected, so that if the user starts an input it will immediately be overridden?
I tried to do it like so
ComboEdit:
...
focus: True
select_all: True
but without effect?
Any acknowledment for the bug, any hint to get around it, any explanation what I'm doing wrong here would be very very very welcome!
Answer1:Can be done in many (better?) ways, but this works.
from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.textinput import TextInput
from kivy.properties import ListProperty, StringProperty
import re
from kivy.lang import Builder
Builder.load_string('''
<Intro>:
BoxLayout:
Button:
text: 'Press to go to SecondScreen'
font_size: '20px'
on_release: root.manager.current = 'SecondScreen'
<SecondScreen>:
ComboLayout:
Label:
text: "working?"
font_size: '20px'
ComboEdit:
size_hint: .5, .5
pos_hint: {'center':(.5, .5)}
font_size: '100px'
multiline: False
''')
class ComboEdit(TextInput):
options = ListProperty([])
def __init__(self, **kw):
super(ComboEdit, self).__init__(**kw)
self.ddn = DropDown()
self.ddn.bind(on_select=self.on_select)
def on_options(self, instance, value):
for option in value:
but = Button(text=option,
size_hint_y=None,
height='36sp',
on_release=lambda btn: self.ddn.select(btn.text))
self.ddn.add_widget(but)
def on_select(self, instance, value):
self.text = value
def on_text(self, instance, value):
self.options = [str(i) for i in range(0,8)]
if not self.get_root_window():
return # do proceed if I'm not displayed <=> If have no parent
self.ddn.open(self)
class ComboLayout(BoxLayout):
pass
class Intro(Screen):
pass
class SecondScreen(Screen):
pass
class BugDemoApp(App):
def build(self):
sm = ScreenManager()
sm.add_widget(Intro(name='Intro'))
sm.add_widget(SecondScreen(name='SecondScreen'))
return sm
if __name__ == '__main__':
BugDemoApp().run()
Hope it helps.