从 Kivy 中动态填充的下拉列表中有选择地删除子小部件

Selectively delete child widgets from dynamically populated drop down list in Kivy

我创建了一个动态下拉列表,它创建了与我在文本输入字段中指定的按钮一样多的按钮。这些按钮按预期动态填充,但我想有选择地从下拉列表中删除某些按钮(子小部件),而不是同时清除所有子小部件。 kv 文件默认定义了两个按钮。当我使用 clear_widgets() 函数时,它会删除所有子窗口小部件。以下是代码。

Python

from kivy.app import App
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.button import Button
from kivy.uix.behaviors import ButtonBehavior
from kivy.uix.image import Image
from kivy.uix.label import Label
from kivy.uix.textinput import TextInput
from kivy.properties import NumericProperty, StringProperty
from kivy.uix.dropdown import DropDown

class CustomTextInput(TextInput):
    # Numeric property defined for use in both Python and Kivy (binding)
    max_characters = NumericProperty(0)
    # Override the default behaviour of the insert_text method
    def insert_text(self, substring, from_undo=False):
        if len(self.text)==self.max_characters and self.max_characters>0:
            substring=""
        TextInput.insert_text(self, substring, from_undo)


class ImageLabel(BoxLayout):
    source = StringProperty('atlas://data/images/defaulttheme/audio-volume-high')
    text = StringProperty('default text')


class ImageLabelButton(ButtonBehavior, ImageLabel):
    pass


class ImageLabelButtonTop(ButtonBehavior, ImageLabel):
    pass

class DropDownScreen(Screen):
    def add_dd_values(self):
        dd_input = App.get_running_app().root.get_screen('dropdown_screen').ids.textinput_num.text
        print("TextInputBox: ", dd_input, "\n")
        print("Length: ",len(dd_input))
        print("Data Type: ", type(dd_input))
        # Check if the text input box is empty and assign a default string of '0' so that int conversion and numerical operations do not fail with an error.
        if dd_input == '':
            dd_input="0"
        print(int(dd_input)+1)
        # Reset dropdown list. Clear all existing child widgets of dropdown including the original 2 buttons defined inside the kv file.
        App.get_running_app().root.get_screen('dropdown_screen').ids.dropdown.clear_widgets()
        for x in range(int(dd_input)):
            if x%2==0:
                print(x,"is an even number")
                App.get_running_app().root.get_screen('dropdown_screen').ids.dropdown.add_widget(ImageLabelButton(source="twitter-48.png",text="Twitter"))
            else:
                print(x,"is an odd number")
                App.get_running_app().root.get_screen('dropdown_screen').ids.dropdown.add_widget(ImageLabelButton(source="linkedin-2-48.png",text="LinkedIn"))


class MyScreenManager(ScreenManager):
    pass


class DropApp(App):
    def build(self):
        # Initialize root widget
        # Screen Manager instance
        sm = MyScreenManager()
        # DropDown Screen Instance
        dds = DropDownScreen()
        sm.add_widget(dds)
        return sm


if __name__ == '__main__':
    # Run application
    DropApp().run()

基维

<DropDownScreen>:
    name: 'dropdown_screen'
    canvas.before:
        Color:
            rgba: 255/255, 255/255, 255/255, 1
        Rectangle:
            pos: self.pos
            size: self.size
    BoxLayout:
        orientation: 'vertical'
        BoxLayout:
            orientation: 'horizontal'
            Label:
                text: 'No. of drop-down buttons:'
                markup: True
                color: 0, 0, 0, 1
            CustomTextInput:
                id: textinput_num
                max_characters: 2
                multiline: False
                input_filter: 'int'
        BoxLayout:
            ImageLabelButtonTop: # conflict in the on_release defined for ImageLabelButton and this instance.
                id: parent_button
                source: 'expand-arrow-48.png'
                text: 'Expand Drop-Down'
                on_release:
                    root.add_dd_values()
                    dropdown.open(self)
                    print("self in ImageLabelButtonTop points to ", self, "\n")
                on_parent: dropdown.dismiss()
                size_hint_y: None
                height: '48dp'
            DropDown:
                id: dropdown
                on_select:
                    # parent_button.text = str(args[1]) # args is a reserved keyword which returns only two values: object alias (0) and data (1).
                    parent_button.text = args[1][0]
                    parent_button.source = args[1][1]
                    print("Invoked inside dropdown")
                    print(args, app.root, root, self, "\n") # root - Screen, self - DropDown object
                ImageLabelButton:
                    source: 'twitter-48.png'
                    text: 'Twitter'
                ImageLabelButton:
                    source: 'linkedin-2-48.png'
                    text: 'LinkedIn'
        BoxLayout:
            Label:
                text: 'Test dynamic drop-down'
                markup: True
                color: 0, 0, 0, 1


<ImageLabel>:
    orientation: 'horizontal'
    size_hint_y: None
    height: '48dp'
    spacing: 1
    Image:
        keep_ratio: True
        source: root.source  # root - ImageLabel
    Label:
        markup: True
        text: root.text  # root - ImageLabel
        color: 0, 0, 0, 1


<ImageLabelButton>:
    on_release:
        # Tuple returned in dropdown.select() method to pass two values bundled as one.
        app.root.get_screen('dropdown_screen').ids.dropdown.select((self.text, self.source))
        print("Invoked inside ImageLabelButton rule")
        print(app.root, root, self, "\n") # app.root - Screen, root - ImageLabelButton object, self - ImageLabelButton object

<CustomTextInput>:
    use_bubble: True
    use_handles: True

如果我注释掉以下行,下拉列表会在每次点击时累积添加按钮,这是可以理解的。

App.get_running_app().root.get_screen('dropdown_screen').ids.dropdown.clear_widgets()

我想实现的是:

  1. kv 文件应默认加载 2 个按钮,如 kv 文件中定义的那样
  2. 我在文本输入字段中指定了一个数字。例如:4
  3. 下拉列表总共应该有 2 + 4 = 6 个按钮
  4. 如果我定义一个新按钮来删除 4 个新按钮,则只应删除最后 4 个子控件。原来的 2 个按钮应该在下拉列表中保持完整。

我想,如果我将动态创建的子窗口小部件的引用存储在列表或字典中,然后通过单独的删除按钮删除它们,它可能会起作用。但我不确定如何在 Python 函数中对其进行编码。

有人可以帮忙吗?

谢谢

您是否尝试过使用 remove_widget 而不是 clear_widgets?例如:

dropdown = App.get_running_app().root.get_screen('dropdown_screen').ids.dropdown
children_to_remove = []
for child in dropdown.children:
    if child.some_indicator == True:
        children_to_remove.append(child)

for child in children_to_remove:
    dropdown.remove(child)

(您不能在第一个 for 循环中执行 dropdown.remove(child) 调用,因为它会在迭代时更改 dropdown.children 数组)