Question

So, I think there should be at least two ways that I could have a drop down menu on this page, but I can get neither to work. I am new to kivy and programming in general, but I have read the documentation and it seems that I simply do not understand.

I have created the following example:

import kivy
kivy.require('1.7.2') # replace with your current kivy version !

from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
from kivy.uix.button import Button
from kivy.uix.dropdown import DropDown

class CustomDropDown(DropDown):
    pass

class HomeScreen(Screen):
    translateInput = ObjectProperty(None)
    translateButton = ObjectProperty(None)
    translateLabel = ObjectProperty(None)
    top_layout = ObjectProperty(None)
    dd_btn = ObjectProperty(None)
    drop_down = CustomDropDown()
    #notes_dropdown = ObjectProperty(None)


    dropdown = DropDown()
    notes = ['Features', 'Suggestions', 'Abreviations', 'Miscellaneous']
    for note in notes:
        # when adding widgets, we need to specify the height manually (disabling
        # the size_hint_y) so the dropdown can calculate the area it needs.
        btn = Button(text='%r' % note, size_hint_y=None, height=30)

        # for each button, attach a callback that will call the select() method
        # on the dropdown. We'll pass the text of the button as the data of the
        # selection.
        btn.bind(on_release=lambda btn: dropdown.select(btn.text))

        # then add the button inside the dropdown
        dropdown.add_widget(btn)

    # create a big main button
    mainbutton = Button(text='Usage Notes 2', size_hint=(1, 1))

    # show the dropdown menu when the main button is released
    # note: all the bind() calls pass the instance of the caller (here, the
    # mainbutton instance) as the first argument of the callback (here,
    # dropdown.open.).
    mainbutton.bind(on_release=dropdown.open)
    #dd_btn.bind(on_release=dropdown.open)

    # one last thing, listen for the selection in the dropdown list and
    # assign the data to the button text.
    dropdown.bind(on_select=lambda instance, x: setattr(mainbutton, 'text', x))
    #dropdown.bind(on_select=lambda instance, x: setattr(dd_btn, 'text', x))

    #top_layout.add_widget(mainbutton)


class dropdApp(App):

    def build(self):

        return HomeScreen()



if __name__ == '__main__':
    dropdApp().run()

And this is the corresponding .kv file:

#:kivy 1.7.2

<CustomDropDown>:
    Button:
        text: 'My first Item'
        size_hint_y: None
        height: 44
        on_release: root.select('item1')
    Label:
        text: 'Unselectable item'
        size_hint_y: None
        height: 44
    Button:
        text: 'My second Item'
        size_hint_y: None
        height: 44
        on_release: root.select('item2')

<HomeScreen>:
    id: home_screen
    translateInput: translateInputID
    translateButton: translateButtonID
    translateLabel: labelID
    top_layout: topLayoutID
    #notes_dropdown: notesDropDownID
    dd_btn: btn_ddID

    orientation: 'vertical'
    FloatLayout:
        size_hint: 1, .95
        TextInput:
            id: translateInputID
            text: 'cove'
            #text: 'ﻰﺸَﻣ'
            font_name: "data/fonts/DejaVuSans.ttf"
            background_color: 1, 1, 1, 1
            size_hint: .75, .1
            multiline: False
            pos_hint: {'x': .125, 'y': .45}
            text_size: self.size
            valign: 'middle'
            halign: 'center'
            padding: 5

        Button:
            id: translateButtonID
            text: 'Translate'
            pos_hint: {'x': .35, 'y': .35}
            size_hint: .3, .08
            valign: 'middle'
            halign: 'center'
            text_size: self.size
            on_release: root.translateButtonPressed()
            #on_press: root.manager.current = 'resultsscreen'

        Label:
            id: labelID
            text: 'Translator'
            text_size: self.size
            valign: 'middle'
            halign: 'center'
            pos_hint: {'x': .3, 'y': .75}
            size_hint: .4, .2
            #font_name: "simpo.ttf"
            #font_name: "5thgradecursive.ttf"
            #font_name: "AGA-Rasheeq-Regular.ttf"
            font_name: "data/fonts/DejaVuSans.ttf"
            font_size: 50

    BoxLayout:
        id: topLayoutID
        #cols: 2
        size_hint: 1, .05
        pos_hint: {'x': 0, 'y': .95}
        Button:
            #id: notesDropDownID 
            id: btn_ddID
            text: 'Usage Notes'
            on_release: root.drop_down.open
        Button:
            text: 'About'
  1. The first drop-down menu should be attached to the already created button, "Usage Notes", at the bottom of the kv file. It is attached to the class in python, "CustomDropDown", and the corresponding in the kv file. You might notice this example is straight out of the kivy documentation. I thought by creating the line:

    drop_down = CustomDropDown()
    

    it gave me a handle in both python and the kivy side to manipulate it, but as you might notice when you run it and click "Usage Notes", nothing happens.

  2. The second drop-down menu is created in python also following the kivy documentation. I would think it would create a third button at the top of the app, titled, "Usage Notes 2". I just get an error when I try to add it. Right now the line (53 on my file):

    top_layout.add_widget(mainbutton)
    

    is commented out or the app wouldn't even run, raising the error:

    AttributeError: 'kivy.properties.ObjectProperty' object has no attribute 'add_widget'
    

    Which I realize that I did set

    top_layout = ObjectProperty(None)
    

    but that is what kivy suggests doing for many widgets, and I have done with many others without this error.

I'm sure one or both of these issues is simple to someone out there. What am I missing?

Était-ce utile?

La solution 2

The first drop-down menu should be attached to the already created button, "Usage Notes", at the bottom of the kv file. It is attached to the class in python, "CustomDropDown", and the corresponding in the kv file.

For this part, you have the right basic idea but made two main mistakes.

The first is that your class is defined wrong, you generally almost never want to place code at class level, it should go in class methods instead. If you want it to run when the class is instantiated, you should put it in the __init__ method. My pasted code below shows the simple change needed to do this. You also need to change drop_down to self.drop_down in order to set the class attribute rather than just making a local variable.

You do sometimes want to make variables at class variable, that are available to all instances of a class. Kivy properties are an example of this, and have special behaviour governing how they behave in actual class instances. Nevertheless, this is an exception to the norm, not something you want to do with most of your code.

Actually, I'm not totally sure on the details of how your code fails (not sure about execution order/time), but ultimately the code doesn't get run properly and the widgets are not properly initialised. This is all fine if you follow the normal __init__ procedure.

The second mistake is that your kv file has on_release: root.drop_down.open. The problem is that everything on the right of a kv colon is pure python, and in this case you don't call the function, you just state its name and so nothing happens. You need to replace that with root.drop_down.open(self) to get the right behaviour, since the open method needs a widget as an argument.

The second drop-down menu is created in python also following the kivy documentation. I would think it would create a third button at the top of the app, titled, "Usage Notes 2". I just get an error when I try to add it. Right now the line (53 on my file):

This is another symptom of the way your code is incorrectly at class level rather than in the __init__ method. The problem is that top_layout is an ObjectProperty, and only appears like a normal object in your kv class because of the special way the Property manages its interface in class instances. When your code is at class level it doesn't see that special interface though because it's not really running from a class instance (the same thing that leads to all the other problems), so it throws an error because it sees the ObjectProperty and not its contents.

With the code changed as below, adding the widget works fine, because __init__ runs from the class instance and can interact with the property like a normal attribute.

I made the following changes, and the dropdowns both seem to work correctly:

import kivy
kivy.require('1.7.2') # replace with your current kivy version !

from kivy.app import App
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.properties import ObjectProperty
from kivy.uix.button import Button
from kivy.uix.dropdown import DropDown

class CustomDropDown(DropDown):
    for i in range(5):
        print i 


class HomeScreen(Screen):
    translateInput = ObjectProperty(None)
    translateButton = ObjectProperty(None)
    translateLabel = ObjectProperty(None)
    top_layout = ObjectProperty(None)
    dd_btn = ObjectProperty(None)

    def __init__(self, *args, **kwargs):
        super(HomeScreen, self).__init__(*args, **kwargs)
        self.drop_down = CustomDropDown()
        #notes_dropdown = ObjectProperty(None)


        dropdown = DropDown()
        notes = ['Features', 'Suggestions', 'Abreviations', 'Miscellaneous']
        for note in notes:
            # when adding widgets, we need to specify the height manually (disabling
            # the size_hint_y) so the dropdown can calculate the area it needs.
            btn = Button(text='%r' % note, size_hint_y=None, height=30)

            # for each button, attach a callback that will call the select() method
            # on the dropdown. We'll pass the text of the button as the data of the
            # selection.
            btn.bind(on_release=lambda btn: dropdown.select(btn.text))

            # then add the button inside the dropdown
            dropdown.add_widget(btn)

        # create a big main button
        mainbutton = Button(text='Usage Notes 2', size_hint=(1, 1))
        print 'yay' 

        # show the dropdown menu when the main button is released
        # note: all the bind() calls pass the instance of the caller (here, the
        # mainbutton instance) as the first argument of the callback (here,
        # dropdown.open.).
        mainbutton.bind(on_release=dropdown.open)
        #dd_btn.bind(on_release=dropdown.open)

        # one last thing, listen for the selection in the dropdown list and
        # assign the data to the button text.
        dropdown.bind(on_select=lambda instance, x: setattr(mainbutton, 'text', x))
        #dropdown.bind(on_select=lambda instance, x: setattr(dd_btn, 'text', x))

        self.top_layout.add_widget(mainbutton)

class dropdApp(App):

    def build(self):

        return HomeScreen()



if __name__ == '__main__':
    dropdApp().run()

kv:

<CustomDropDown>:
    Button:
        text: 'My first Item'
        size_hint_y: None
        height: 44
        on_release: root.select('item1')
    Label:
        text: 'Unselectable item'
        size_hint_y: None
        height: 44
    Button:
        text: 'My second Item'
        size_hint_y: None
        height: 44
        on_release: root.select('item2')

<HomeScreen>:
    id: home_screen
    translateInput: translateInputID
    translateButton: translateButtonID
    translateLabel: labelID
    top_layout: topLayoutID
    #notes_dropdown: notesDropDownID
    dd_btn: btn_ddID 

    orientation: 'vertical'
    FloatLayout: 
        size_hint: 1, .95
        TextInput:
            id: translateInputID
            text: 'cove'
            #text: 'ﻰﺸَﻣ' 
            font_name: "data/fonts/DejaVuSans.ttf"
            background_color: 1, 1, 1, 1
            size_hint: .75, .1
            multiline: False
            pos_hint: {'x': .125, 'y': .45} 
            text_size: self.size
            valign: 'middle'
            halign: 'center'
            padding: 5

        Button:
            id: translateButtonID
            text: 'Translate'
            pos_hint: {'x': .35, 'y': .35}
            size_hint: .3, .08
            valign: 'middle'
            halign: 'center'
            text_size: self.size
            on_release: root.translateButtonPressed()
            #on_press: root.manager.current = 'resultsscreen'

        Label:
            id: labelID
            text: 'Translator'
            text_size: self.size
            valign: 'middle'
            halign: 'center'
            pos_hint: {'x': .3, 'y': .75}
            size_hint: .4, .2
            #font_name: "simpo.ttf"
            #font_name: "5thgradecursive.ttf"
            #font_name: "AGA-Rasheeq-Regular.ttf"
            font_name: "data/fonts/DejaVuSans.ttf"
            font_size: 50

    BoxLayout:
        id: topLayoutID
        #cols: 2
        size_hint: 1, .05
        pos_hint: {'x': 0, 'y': .95}
        Button:
            #id: notesDropDownID
            id: btn_ddID
            text: 'Usage Notes'
            on_release: root.drop_down.open(self)
        Button:
            text: 'About'

Autres conseils

I'm posting this here in the hope that it helps other kivy beginners who just want a dropdown button that they can place in a .kv file.

class DropBut(Button):
    def __init__(self, **kwargs):
        super(DropBut, self).__init__(**kwargs)
        self.drop_list = None
        self.drop_list = DropDown()

        types = ['Item1', 'Item2', 'Item3', 'Item4', 'Item5', 'Item6']

        for i in types:
            btn = Button(text=i, size_hint_y=None, height=50)
            btn.bind(on_release=lambda btn: self.drop_list.select(btn.text))
           
            self.drop_list.add_widget(btn)

        self.bind(on_release=self.drop_list.open)
        self.drop_list.bind(on_select=lambda instance, x: setattr(self, 'text', x))
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top