I am struggling with making DropDown opening bind properly to the releasing of the selection button. I tried using this:
selection_button.bind(on_release=drpdn.open)
The binding seems to work rather randomly when I create more than one DropDown (some DropDowns open others do not and I cannot see any logic in it). However it shown exactly like this in the example in Kivy documentation.
I was able to overcome this issue by creating my own callback function for DropDown opening where I pass the selection button and the DropDown instance (using functools.partial) and open the DropDown simply by:
def open_dropdown(self, wid, drpdn, *largs): drpdn.open(wid)
Although this workaround works, it really bothers me since I evidently do not correctly understand how binding and callbacks work. Can anyone point me in the right direction? I expected that selection_button.bind(on_release=drpdn.open)
passes the seletion button to DropDown open method and the DropDown opens on it but evidently it does not. The most mindboggling thing is that it seems to randomly work (e.g. when I create four selection buttons, some of them will work).
This is my test app code. create_dropdown
method works flawlessly whereas create_dropdown2
method causes strange behavior (only the last DropDown actually opens).
from functools import partial from kivy.uix.button import Button from kivy.uix.dropdown import DropDown from kivy.app import App from kivy.uix.boxlayout import BoxLayout class TestApp(App): def create_dropdown(self, text, options): selection_button = Button(text=text) drpdn = DropDown() for o in options: btn = Button(text=o, size_hint_y=None, height=44) btn.bind(on_release=partial(self.change_select, selection_button, btn.text ) ) btn.bind(on_release=drpdn.dismiss) drpdn.add_widget(btn) selection_button.bind(on_release=partial(self.open_dropdown, selection_button, drpdn ) ) return selection_button def create_dropdown2(self, text, options): selection_button = Button(text=text) drpdn = DropDown() for o in options: btn = Button(text=o, size_hint_y=None, height=44) btn.bind(on_release=partial(self.change_select, selection_button, btn.text ) ) btn.bind(on_release=drpdn.dismiss) drpdn.add_widget(btn) selection_button.bind(on_release=drpdn.open) return selection_button def change_select(self, wid, text, *largs): wid.text = text def open_dropdown(self, wid, drpdn, *largs): drpdn.open(wid) def build(self): root = BoxLayout(orientation="vertical") for _ in range(3): root.add_widget(self.create_dropdown("Works", ["yes", "no"] ) ) for _ in range(4): root.add_widget(self.create_dropdown2("Does not work", ["yes", "no"] ) ) return root if __name__ == '__main__': TestApp().run()
Advertisement
Answer
In your create_dropdown2()
method you are binding on_release
to a local variable (drpdn
). The binding creates a weak reference to drpdn
. That means that when drpdn
goes out of scope, the binding will not prevent it from being deleted and garbage collected. Then, when the button is released, the method that the on_release
is bound to is now None
.
The partial
call in create_dropdown()
freezes the supplied args and avoids the out of scope issue.
Note that in the documentation, the dropdown
is not a local variable.