MookiBar
MookiBar

Reputation: 183

simple Kivy DropDown errors with 'NoneType' object has no attribute 'bind'

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

Answers (2)

MookiBar
MookiBar

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

FJSevilla
FJSevilla

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:

enter image description here

Upvotes: 1

Related Questions