I am struggling with making DropDown opening bind properly to the releasing of the selection button. I tried using this:
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()
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
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.