Reputation: 183
I have been using kivy 1.7.2
on Ubuntu 14.04 for some time; very easy to use and kv
language makes design a breeze....
...except the DropDown has proven near impossible to setup without running it with kv
and even has issues when initiated with more python code.
Here is what I start with....
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.button import Button
from kivy.uix.dropdown import DropDown
kv_text = """#:kivy 1.7.2
<MyWidget>
DropDown:
id: menupanel
on_parent: self.dismiss()
on_select: lambda: None
Button:
id: button_a
size_hint_y: None
text: "A"
Button:
id: button_b
size_hint_y: None
text: "B"
Button:
id: button_opendd
pos_hint: {"top":1, "right":1}
on_release: root.ids.menupanel.open(self)
text: "open"
"""
class MyWidget(FloatLayout):
def __init__(self, **kwargs):
super(FloatLayout, self).__init__(**kwargs)
class MyWidgetApp(App):
def build(self):
return MyWidget()
def main():
Builder.load_string(kv_text)
app = MyWidgetApp()
app.run()
if __name__ == '__main__':
main()
... ....which produces this error.... ...
Traceback (most recent call last):
File "wtfkivy.py", line 60, in <module>
main()
File "wtfkivy.py", line 57, in main
app.run()
File "/usr/lib/python2.7/dist-packages/kivy/app.py", line 577, in run
root = self.build()
File "wtfkivy.py", line 52, in build
return MyWidget()
File "wtfkivy.py", line 48, in __init__
super(FloatLayout, self).__init__(**kwargs)
File "/usr/lib/python2.7/dist-packages/kivy/uix/layout.py", line 61, in __init__
super(Layout, self).__init__(**kwargs)
File "/usr/lib/python2.7/dist-packages/kivy/uix/widget.py", line 163, in __init__
Builder.apply(self)
File "/usr/lib/python2.7/dist-packages/kivy/lang.py", line 1429, in apply
self._apply_rule(widget, rule, rule)
File "/usr/lib/python2.7/dist-packages/kivy/lang.py", line 1531, in _apply_rule
child = cls(__no_builder=True)
File "/usr/lib/python2.7/dist-packages/kivy/uix/dropdown.py", line 160, in __init__
self.container.bind(minimum_size=self._container_minimum_size)
AttributeError: 'NoneType' object has no attribute 'bind'
The DropDown's self.container
is supposed to default to a GridLayout
but somehow that doesn't happen when set up via kv
file.
I have gotten around this error by changing DropDown:
in the kv to MyDropDown:
and putting the <MyDropDown>:
definition below everything. Then, in the python, I had to add the class with a super(MyDropDown,self).__init__()
However! It just put a duplicate set of widgets into the dropdown. Woops? (If I change that init to __init__(**kwargs)
, I end up with the same bind error. Huh?)
I have honestly scoured the internet numerous times for good examples of DropDown
set up mostly via kv
, but nothing that really hit the mark.
Even if the implementation in python worked out (with super(
), it would not be preferred. I need the DropDown's widgets to be able to access attributes of the main app widget.
So the question: is there either a way to implement a DropDown almost entirely in kv
(which makes design so clean and accessible) or something else that still makes the main app widget accessible?
EDIT As indicated by FJSevilla and verified by testing, DropDown is broken in version 1.7.2. I added an answer to elaborate, since many others may be using the version supplied from their distro.
Upvotes: 0
Views: 810
Reputation: 183
DropDown
(and maybe other widgets) are broken in Kivy 1.7
. DropDown specifically does not initiate properly from kv
code.
If anyone needs to use a version this old, implement dropdowns using python alone and not kv (and expect trouble):
dd = DropDown()
b1 = Button(text="a")
b2 = Button(text="b")
dd.add_widget(b1)
dd.add_widget(b2)
Or try getting the kivy you wanted by upgrading to at least 1.9.x
. (I did not test 1.8.)
First off, if you use an older distro, you will probably have to upgrade kivy beyond what your distro provides.
On Ubuntu 14.04:
sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys A863D2D6
sudo add-apt-repository ppa:kivy-team/kivy-daily
sudo apt-get update
sudo apt-get install python-kivy python3-kivy
Note that this upgrade seems to change certain behaviors (read: new errors)...
First change...
For DropDown, on_parent:self.dismiss()
was a bound attribute on the DropDown itself. After upgrading, this caused the DropDown to immediately dismiss itself on open. (You see it flicker for only a split-second.)
If you now encounter this error, moving menupanel.dismiss()
(substituting your id
in place of menupanel
) to the root widget alleviated that problem.
Second change...
Somehow, having the menupanel.dismiss()
OR self.ids.menupanel.dismiss()
on the root widget was breaking the refs to the dropdown (essentially deleting the strong refs / getting garbage-collected) and causing this:
on_release: menupanel.open(self)
File "weakproxy.pyx", line 30, in kivy.weakproxy.WeakProxy.__getattr__ (kivy/weakproxy.c:1145)
File "weakproxy.pyx", line 26, in kivy.weakproxy.WeakProxy.__ref__ (kivy/weakproxy.c:1069)
ReferenceError: weakly-referenced object no longer exists
(NOTE: The only time I see this error on the interwebs are for named classes (i.e. class MyButton(Button)
that are instantiated by kv
code. This is just a normal generic DropDown, so why cause an issue here?)
The solution, if you receive that error, is to add a reference to your DropDown instance...anywhere, to any object, when instantiated or in the function where the DropDown is opened. It just has to happen before the other reference is removed. (In my case, getting removed while switching to new Screen
and back.)
class ...
def ...
self.backup_reference = self.ids.menupanel
Why? I have no idea.
....Or, if your DropDown was going to be very simple, try using 1 Spinner
instead.
Upvotes: 1
Reputation: 4543
The use of DropDown
in Kivy-language is not too intuitive. However, it is possible to create your own DropDown menu using kv-language only:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.floatlayout import FloatLayout
kv_text = """\
#:kivy 1.7.2
<MyWidget>
Widget:
on_parent: menupanel.dismiss()
DropDown:
id: menupanel
on_select: button_opendd.text = '{}'.format(args[1])
Button:
text: 'A'
size_hint_y: None
height: 30
on_release: menupanel.select('A')
Button:
text: 'B'
size_hint_y: None
height: 30
on_release: menupanel.select('B')
Button:
id: button_opendd
pos_hint: {"top":1, "right":1}
text: 'Press'
on_release: menupanel.open(self)
size_hint_y: None
height: 40
"""
class MyWidget(FloatLayout):
def __init__(self, **kwargs):
super(MyWidget, self).__init__(**kwargs)
class MyWidgetApp(App):
def build(self):
return MyWidget()
def main():
Builder.load_string(kv_text)
app = MyWidgetApp()
app.run()
if __name__ == '__main__':
main()
Warning: This code is tested in Kivy 1.9.3 and Kivy 1.10.0, i don't know if it will be backwards compatible with version 1.7.
If you put on_parent
event inside DropDown
instance the code run in Kivy 1.9 correctly but fail in Kivy 1.10 (menu is always hidden).
Running example:
Upvotes: 1