DropDown list: How to open by default?

129 views
Skip to first unread message

Henrik R.

unread,
Jun 24, 2024, 4:23:35 PM6/24/24
to Kivy users support
Hi!
I want to include a DropDown list in my Kivy app, but I want the list of choices to be open (displayed) by default, so the user does not have to press the 'main button', to see the list. 
I cannot see how to do that?
I tried:
dropdown = MyDropDown(is_open=True)
And:
# create a big main button:
dropdown_headline = ScalableButton(text="Choose target from the list") # , size_hint=(None, None)
dropdown_headline.bind(dropdown.open) # was on_release=dropdown.open
None of that worked...

ElliotG

unread,
Jun 24, 2024, 6:42:33 PM6/24/24
to Kivy users support
Here is an example that creates a DropDown(), and shows it in the open state.

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.metrics import dp
from kivy.clock import Clock


kv = """
AnchorLayout:
    anchor_y: 'top'
    ButtonDropDown:
        text: 'Big Button Drop Down'
        size_hint: None, None
        size: dp(300), dp(100)
"""

class ButtonDropDown(Button):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.dropdown = DropDown()
        Clock.schedule_once(self.add_buttons)

    def add_buttons(self, _):
        for i in range(10):
            button = Button(text=f'Item {i}', size_hint_y=None, height=dp(48))
            button.bind(on_release=lambda btn: self.dropdown.select(btn.text))
            self.dropdown.add_widget(button)
        self.bind(on_release=self.dropdown.open)
        self.dropdown.bind(on_select=lambda instance, x: setattr(self, 'text', x))
        self.dropdown.open(self)  # open the dropdown


class DDApp(App):
    def build(self):
        return Builder.load_string(kv)


DDApp().run()

Henrik R.

unread,
Jun 26, 2024, 10:11:22 AM6/26/24
to Kivy users support
Great. Thank you! That code works. :-)

But why do you have to:
    Clock.schedule_once(self.add_buttons)
And why do you NOT:
    self.add_widget(self. dropdown)

ElliotG

unread,
Jun 26, 2024, 12:00:20 PM6/26/24
to Kivy users support
I added the clock because the Button was not rendering, so I added to clock to see if that was needed - and it worked.
I did not add do a self.add_widget(self.dropdown)... I followed the example in the docs.
Message has been deleted

Henrik R.

unread,
Jun 28, 2024, 7:39:14 AM6/28/24
to Kivy users support
OK. :-) Your code works fine as a standalone. But when I insert it in my app, and call it from a BoxLayout like this:

class ChooseTarget(BoxLayout):

(...)

def no_no(self):
# User will choose a target from a list
self.clear_widgets()
# I try Elliot's example that DID work as a standalone:
self.mydropdown = ButtonDropDown()

I get a strange error on the 'dropdown.open':

   File "/mnt/4AF15A0435E762B4/mypython/GeoESP/main.py", line 1612, in delayed_open

     self.dropdown.open(self)  # open the dropdown
   File "/mnt/4AF15A0435E762B4/mypython/GeoESP-android/newvenv/lib/python3.8/site-packages/kivy/uix/dropdown.py", line 246, in open
     raise DropDownException(
 kivy.uix.dropdown.DropDownException: Cannot open a dropdown list on a hidden widget

I even tried delaying 'open' using Clock (similar to your code), but that didn't change anything:

self.dropdown.bind(on_select=lambda instance, x: setattr(self, 'text', x))
Clock.schedule_once(self.delayed_open)

def delayed_open(self, _):

self.dropdown.open(self) # open the dropdown

Here is your DropDown code. I only changed height=dp(48) to height=48 in Button:

class ButtonDropDown(Button): # ScalableButton
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.dropdown = DropDown()
Clock.schedule_once(self.add_buttons)

def add_buttons(self, _):
for i in range(10):
button = Button(text=f'Item {i}', size_hint_y=None, height=48)

button.bind(on_release=lambda btn: self.dropdown.select(btn.text))
self.dropdown.add_widget(button)
self.bind(on_release=self.dropdown.open)
self.dropdown.bind(on_select=lambda instance, x: setattr(self, 'text', x))
# Clock.schedule_once(self.delayed_open)

# def delayed_open(self, _):
self.dropdown.open(self) # open the dropdown

Do you have any idea about what goes wrong?

Love,
Henrik

PS: I am away on holiday for a week, starting tomorrow (Saturday).

ElliotG

unread,
Jun 28, 2024, 11:17:18 AM6/28/24
to Kivy users support
Enjoy your holiday!
When you have a chance, send an executable program that demonstrates the issue.  I've extended the previous example:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from kivy.metrics import dp
from kivy.properties import ListProperty

from kivy.clock import Clock


kv = """
AnchorLayout:
    anchor_y: 'top'
    BoxLayout:
        size_hint_y: None
        height: self.minimum_height
        ButtonDropDown:
            text: 'Big Drop Down 1'
            size_hint: None, None
            size: dp(150), dp(100)
            values: ['A', 'B', 'C']
        ButtonDropDown:
            text: 'Big Drop Down 2'
            size_hint: None, None
            size: dp(200), dp(100)
            values: [str(x) for x in range(10)]
        ButtonDropDown:
            text: 'Big Drop Down 3'
            size_hint: None, None
            size: dp(250), dp(100)
            values: [str(x) for x in range(10, 20)]
        ButtonDropDown:
            text: 'Big Drop Down 4'
            size_hint: None, None
            size: dp(150), dp(100)
            values: ['Red', 'Yellow', 'Orange', 'Green']
       
"""

class ButtonDropDown(Button):
    values = ListProperty()

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.dropdown = DropDown(auto_dismiss=False)
        Clock.schedule_once(self.add_buttons)


    def add_buttons(self, _):
        for i in self.values:
            button = Button(text=f'Item {i}', size_hint_y=None, height=dp(48))

            button.bind(on_release=lambda btn: self.dropdown.select(btn.text))
            self.dropdown.add_widget(button)
        self.bind(on_release=self.dropdown.open)
        self.dropdown.bind(on_select=lambda instance, x: setattr(self, 'text', x))
        self.dropdown.open(self)  # open the dropdown


class DDApp(App):
    def build(self):
        return Builder.load_string(kv)


DDApp().run()
drop_down.py

Henrik R.

unread,
Jul 5, 2024, 7:00:03 AM7/5/24
to Kivy users support
Thank you!
I have returned from my holidays 2 days earlier, because the weather forecast was terrible...
So now I have reduced my app to the part that generates the following error:

   File "/mnt/4AF15A0435E762B4/mypython/GeoESP/my ButtonDropDown test.py", line 64, in add_buttons

     self.dropdown.open(self)  # open the dropdown
   File "/mnt/4AF15A0435E762B4/mypython/GeoESP-android/newvenv/lib/python3.8/site-packages/kivy/uix/dropdown.py", line 246, in open
     raise DropDownException(
 kivy.uix.dropdown.DropDownException: Cannot open a dropdown list on a hidden widget

I have attached the Python file and the kv-file.

I look forward to hear from you. :-)
ButtonDropDown.kv
my ButtonDropDown test.py

Henrik R.

unread,
Jul 5, 2024, 7:10:06 AM7/5/24
to Kivy users support
PS: Remember to press the button:
"I will choose a target from the list."

ELLIOT GARBUS

unread,
Jul 5, 2024, 2:59:09 PM7/5/24
to kivy-...@googlegroups.com
You never add the button to the layout.

In the no_no method, you instance the ButtonDropDown, but it is never added to the widget tree,
def no_no(self):
   # User will choose a target from a list
   self.clear_widgets()
   # There is: https://kivy.org/doc/stable/api-kivy.uix.dropdown.html
   # It didn't really work. Now I try Elliot's example that DID work as a standalone:
   # https://groups.google.com/g/kivy-users/c/en4B5QgVOx8
   self.mydropdown = ButtonDropDown()


From: kivy-...@googlegroups.com <kivy-...@googlegroups.com> on behalf of Henrik R. <henrik.r...@gmail.com>
Sent: Friday, July 5, 2024 4:10 AM
To: Kivy users support <kivy-...@googlegroups.com>
Subject: [kivy-users] Re: DropDown list: How to open by default?
 
--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/d22a2c75-137b-4cbb-8cc2-ae501311b9acn%40googlegroups.com.

Henrik R.

unread,
Jul 5, 2024, 5:53:48 PM7/5/24
to Kivy users support
OK. I will try that... It's VERY logical...! 
But why does neither your code nor the example in the documentation add the DropDown widget to the current widget? That puzzled me, when I studied them.
Message has been deleted

Henrik R.

unread,
Jul 5, 2024, 6:04:36 PM7/5/24
to Kivy users support
I added the add_widget() function:

self.mydropdown = ButtonDropDown()
self.add_widget(self.mydropdown)

Now there is no error message, but it still does not work. There is only an empty window...

ElliotG

unread,
Jul 5, 2024, 8:56:28 PM7/5/24
to Kivy users support
You have not sized the button, so the button is filling the Window.  Try this:
        self.mydropdown = ButtonDropDown(size_hint_y=None, height=48, text='Choose')
        self.add_widget(self.mydropdown)

Henrik R.

unread,
Jul 6, 2024, 5:57:35 PM7/6/24
to Kivy users support
Oops... That is also quite logical... :-) So I insert the ScalableButton that you have helped me create:

class ButtonDropDown(ScalableButton): # ScalableButton

As you can see, I had even written the comment that I should probably use ScalableButton...

Now it - kind of - works. But how do I send the item the user selects to the calling widget?:

class ChooseTarget(BoxLayout):

ElliotG

unread,
Jul 6, 2024, 6:36:39 PM7/6/24
to Kivy users support
You can bind to the change in text, and then take the desired action with the new text.  In the code below, I print out what the user has selected:


    def no_no(self):

        # User will choose a target from a list
        self.clear_widgets()
        # There is: https://kivy.org/doc/stable/api-kivy.uix.dropdown.html
        # It didn't really work. Now I try Elliot's example that DID work as a standalone:
        # https://groups.google.com/g/kivy-users/c/en4B5QgVOx8
        self.mydropdown = ButtonDropDown(size_hint_y=None, height=48, text='Choose')
        self.add_widget(self.mydropdown)
        self.mydropdown.bind(text=self.text_selected)

    def text_selected(self, obj, value):
        print(value)  # value is the text the user selected... do something here

Henrik R.

unread,
Jul 7, 2024, 7:57:44 AM7/7/24
to Kivy users support
Fantastic! That works. But what is 'obj' in:

def text_selected(self, obj, value):

And: In your original example the DropDown (inside an AnchorLayout) DID drop down, but when I put it inside:

class ChooseTarget(BoxLayout):

It 'drops up' instead of down... Can I change that?

ELLIOT GARBUS

unread,
Jul 7, 2024, 11:04:56 AM7/7/24
to Kivy users support
When you have questions like what is obj?  The simplest thing to do is print it out and see what it is.
In this case you will see that obj is the ButtonDropDown object.

When we bind,  property callbacks are generally called with 2 arguments (the object and the property’s new value) 


"It 'drops up' instead of down... Can I change that?"
If you position the ButtonDropDown at the top of the WIndow it will drop down.  As I recall you are placing the Button in a BoxLayout.  If you add another widget (as a placeholder)  with a size hint of 1, 1 (the default), it will "push" the button to the top of the window.




Sent: Sunday, July 7, 2024 4:57 AM

To: Kivy users support <kivy-...@googlegroups.com>
Subject: Re: [kivy-users] Re: DropDown list: How to open by default?
 

Henrik R.

unread,
Jul 8, 2024, 6:50:34 AM7/8/24
to Kivy users support
Sorry for bothering you with the 'obj' question!

Yes. Using a placeholder to force the DropDown-button to the top works. But I don't really see the logic in the need for this placeholder. I would think that all the buttons that gets added to the DropDown would push the DropDown-Button itself to the top. I still find Kivy a bit strange. :-)

def no_no(self):
# User will choose a target from a list
self.clear_widgets()
# There is: https://kivy.org/doc/stable/api-kivy.uix.dropdown.html
# It didn't really work. Now I try Elliot's example that DID work as a standalone:
# https://groups.google.com/g/kivy-users/c/en4B5QgVOx8
self.mydropdown = ButtonDropDown(text="Choose a target on the list:", background_color=[0.9, 0.9, 0.9, 1])
self.add_widget(self.mydropdown)
self.mydropdown.bind(text=self.text_selected)
self.placeholder = Label()
self.add_widget(self.placeholder)
Reply all
Reply to author
Forward
Message has been deleted
0 new messages