如何在带有python类的kivy中使用下拉小部件

Posted

技术标签:

【中文标题】如何在带有python类的kivy中使用下拉小部件【英文标题】:how to use drop down widget in kivy with a python class 【发布时间】:2014-02-13 04:09:43 【问题描述】:

所以,我认为至少应该有两种方法可以在此页面上设置下拉菜单,但我不能让任何一种工作。我是 kivy 和一般编程的新手,但我已经阅读了文档,似乎我根本不明白。

我创建了以下示例:

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()

这是对应的.kv文件:

#: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'

    第一个下拉菜单应附加到已创建的按钮“使用说明”上,位于 kv 文件的底部。它附加在python中的类“CustomDropDown”中,并在kv文件中对应。您可能会注意到这个示例直接来自 kivy 文档。我想通过创建这条线:

    drop_down = CustomDropDown()
    

    它在 python 和 kivy 端都给了我一个句柄来操作它,但是当你运行它并单击“使用说明”时你可能会注意到,没有任何反应。

    第二个下拉菜单也是按照 kivy 文档在 python 中创建的。我认为它会在应用程序顶部创建第三个按钮,标题为“使用说明 2”。当我尝试添加它时,我只是得到一个错误。现在这一行(我的文件中的第 53 行):

    top_layout.add_widget(mainbutton)
    

    被注释掉或应用程序甚至无法运行,从而引发错误:

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

    我意识到我确实设置了

    top_layout = ObjectProperty(None)
    

    但这是 kivy 建议对许多小部件执行的操作,我已经对许多其他小部件执行此操作而没有出现此错误。

我确信这些问题中的一个或两个对于那里的人来说都很简单。我错过了什么?

【问题讨论】:

【参考方案1】:

我在这里发布此内容是希望它可以帮助其他只想要一个可以放置在 .kv 文件中的下拉按钮的 kivy 初学者。

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))

【讨论】:

【参考方案2】:

第一个下拉菜单应附加到已创建的按钮“使用说明”,位于 kv 文件的底部。附在python中的类“CustomDropDown”中,在kv文件中对应。

对于这一部分,你有正确的基本想法,但犯了两个主要错误。

首先是您的类定义错误,您通常几乎不想将代码放在类级别,而是应该放在类方法中。如果你想让它在类被实例化时运行,你应该把它放在__init__ 方法中。我在下面粘贴的代码显示了执行此操作所需的简单更改。您还需要将drop_down 更改为self.drop_down 以设置类属性,而不仅仅是制作局部变量。

您有时确实希望在类变量中创建变量,这些变量可用于类的所有实例。 Kivy 属性就是一个例子,它具有特殊的行为来控制它们在实际类实例中的行为。不过,这是常规的例外情况,而不是您想对大部分代码执行的操作。

实际上,我不完全确定您的代码如何失败的细节(不确定执行顺序/时间),但最终代码无法正常运行并且小部件未正确初始化。如果您遵循正常的__init__ 程序,这一切都很好。

第二个错误是你的kv文件有on_release: root.drop_down.open。问题是 kv 冒号右侧的所有内容都是纯 python,在这种情况下,您不调用该函数,您只需声明其名称,因此什么也没有发生。您需要将其替换为 root.drop_down.open(self) 以获得正确的行为,因为 open 方法需要一个小部件作为参数。

第二个下拉菜单也是按照 kivy 文档在 python 中创建的。我认为它会在应用程序顶部创建第三个按钮,标题为“使用说明 2”。当我尝试添加它时,我只是得到一个错误。现在这一行(我的文件中的第 53 行):

这是您的代码在类级别而不是在__init__ 方法中错误的另一个症状。问题是 top_layout 一个 ObjectProperty,并且由于 Property 在类实例中管理其接口的特殊方式,它只在您的 kv 类中显示为普通对象。当您的代码处于类级别时,它看不到那个特殊接口,因为它不是真正从类实例运行(导致所有其他问题的同一件事),所以它会引发错误,因为它看到 ObjectProperty 而不是它的内容。

将代码更改如下,添加小部件可以正常工作,因为__init__ 从类实例运行并且可以像普通属性一样与属性交互。

我进行了以下更改,下拉菜单似乎都可以正常工作:

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'

【讨论】:

以上是关于如何在带有python类的kivy中使用下拉小部件的主要内容,如果未能解决你的问题,请参考以下文章

带有 kivy 语言和自定义小部件的 Kivy 滚动视图

Python 中 Kivy 小部件之间的交互

如何在kivy中正确导入自定义小部件

如何在 kivy python 中的标签、文本输入和其他小部件中添加标题

在 kivy (python) 中更新小部件

Kivy:从另一个类的小部件中检索文本?