从 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()
我想实现的是:
- kv 文件应默认加载 2 个按钮,如 kv 文件中定义的那样
- 我在文本输入字段中指定了一个数字。例如:4
- 下拉列表总共应该有 2 + 4 = 6 个按钮
- 如果我定义一个新按钮来删除 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 数组)
我创建了一个动态下拉列表,它创建了与我在文本输入字段中指定的按钮一样多的按钮。这些按钮按预期动态填充,但我想有选择地从下拉列表中删除某些按钮(子小部件),而不是同时清除所有子小部件。 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()
我想实现的是:
- kv 文件应默认加载 2 个按钮,如 kv 文件中定义的那样
- 我在文本输入字段中指定了一个数字。例如:4
- 下拉列表总共应该有 2 + 4 = 6 个按钮
- 如果我定义一个新按钮来删除 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 数组)