[英]Properly set default selections with RecycleView (Python/Kivy)
I want to use Kivy's RecycleView
to make a multiline scrollable selection list, but I need to set as selected some of the items by default.我想使用 Kivy 的
RecycleView
来制作一个多行可滚动的选择列表,但我需要默认设置为选中的一些项目。 The user must still be able to unselect them, if they wish (I want to implement some sort of proposed choices).如果他们愿意,用户仍然必须能够取消选择它们(我想实现某种建议的选择)。
Based on the example on Kivy documentation , here follows a minimal working code that presents my problem:基于Kivy 文档上的示例,下面是一个最小的工作代码,它提出了我的问题:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.uix.label import Label
from kivy.properties import BooleanProperty
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior
Builder.load_string('''
<SelectableLabel>:
# Draw a background to indicate selection
canvas.before:
Color:
rgba: (.0, 0.9, .1, .3) if self.selected else (0, 0, 0, 1)
Rectangle:
pos: self.pos
size: self.size
<RV>:
viewclass: 'SelectableLabel'
SelectableRecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
multiselect: True
touch_multiselect: True
''')
class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior,
RecycleBoxLayout):
''' Adds selection and focus behaviour to the view. '''
class SelectableLabel(RecycleDataViewBehavior, Label):
''' Add selection support to the Label '''
index = None
selected = BooleanProperty(False)
selectable = BooleanProperty(True)
def refresh_view_attrs(self, rv, index, data):
''' Catch and handle the view changes '''
self.index = index
self.selected = rv.data[self.index]['selected']
return super(SelectableLabel, self).refresh_view_attrs(
rv, index, data)
def on_touch_down(self, touch):
''' Add selection on touch down '''
if super(SelectableLabel, self).on_touch_down(touch):
return True
if self.collide_point(*touch.pos) and self.selectable:
return self.parent.select_with_touch(self.index, touch)
def apply_selection(self, rv, index, is_selected):
''' Respond to the selection of items in the view. '''
self.selected = is_selected # line X, the gaming change
if is_selected:
print("selection changed to {0}".format(rv.data[index]))
else:
print("selection removed for {0}".format(rv.data[index]))
class RV(RecycleView):
def __init__(self, items, **kwargs):
if 'selection' in kwargs:
selection = kwargs['selection']
del kwargs['selection']
else:
selection = [False]*len(items)
super().__init__(**kwargs)
self.data = [{'text': x, 'selected': selection[i]} \
for i, x in enumerate(items)]
items = [
"apple", "dog", "banana", "cat", "pear", "rat",
"pineapple", "bat", "pizza", "ostrich",
"apple", "dog", "banana", "cat", "pear", "rat",
"pineapple", "bat", "pizza", "ostrich",
]
class TestApp(App):
def build(self):
return RV(items,
selection=[x[0] in ['p','a','r'] \
for x in items]
)
if __name__ == '__main__':
test_app = TestApp()
test_app.run()
This code doesn't show the default items actually selected.此代码不显示实际选择的默认项目。 While I was conducting a proper investigation, I notice that if I comment a single line in the
apply_selection
method ( line X comment in code above), ie, if I change it to # self.selected = is_selected
, I finally can see all my items with default selections.当我进行适当的调查时,我注意到如果我在
apply_selection
方法中注释一行(上面代码中的第 X 行注释),即如果我将其更改为# self.selected = is_selected
,我终于可以看到我的所有具有默认选择的项目。
Problem is, as you should probably know, that's the instruction that allows the selection feature to happen (!), ie, this line while commented wins me my desired default items, but I lose the ability to actually select/unselect items.问题是,正如您可能应该知道的那样,这是允许选择功能发生的指令(!),即,这一行在评论时为我赢得了我想要的默认项目,但我失去了实际选择/取消选择项目的能力。 I think that the
is_selected
parameter is some sort of event which somehow detects an actual click selection and, while instantiating the RV class, some other method unselect all items of the list after apply_selection
comes to play.我认为
is_selected
参数是某种事件,它以某种方式检测到实际的单击选择,并且在实例化 RV 类时,其他一些方法在apply_selection
开始播放后取消选择列表的所有项目。
I tried to look up into documentation, but I don't even know what to search for.我试图查找文档,但我什至不知道要搜索什么。 I'm missing which method I should overwrite in order to make this default trick finally work together with selection.
我错过了应该覆盖哪个方法以使这个默认技巧最终与选择一起工作。 Any thoughts?
有什么想法吗?
After some diggings, I drop the SelectableLabel
approach and adopted Button
to do the trick.经过一番挖掘,我放弃了
SelectableLabel
方法并采用了Button
来解决问题。 I'm registering my working custom RecycleView here to others:我在这里向其他人注册我的工作自定义 RecycleView:
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.button import Button
from kivy.uix.recycleview import RecycleView
from kivy.properties import BooleanProperty
from kivy.uix.gridlayout import GridLayout
Builder.load_string('''
<Item>:
index: None
on_release: root.parent.parent.apply_selection(self.index)
<RV>:
viewclass: 'Item'
RecycleBoxLayout:
default_size_hint: 1, None
default_size: 0, dp(40)
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
''')
class Item(Button): # root
selected = BooleanProperty(False)
class RV(RecycleView):
DEF_COLOR = [0, 0, 0, 1]
SEL_COLOR = [.0, .4, .8, .4]
def __init__(self, **kwargs):
super(RV, self).__init__(**kwargs)
self.data = []
def apply_selection(self, index, sel=None):
''' Respond to the selection of items in the view. '''
sel = not self.data[index]['selected'] if sel is None else sel
self.data[index]['selected'] = sel
self.data[index]['background_color'] = RV.SEL_COLOR \
if sel else RV.DEF_COLOR
self.refresh_from_data()
print("DEBUG:", self.get_selected_indices())
def update(self, data_list, sel=False, default=None):
if default is None:
default = [sel]*len(data_list)
self.data = [{
'index': i,
'text': str(d),
'selected': default[i],
'background_normal': '',
'background_color': RV.SEL_COLOR \
if default[i] else RV.DEF_COLOR,
} for i, d in enumerate(data_list)]
self.refresh_from_data()
def none_selected(self, instance):
self.update(list(d['text'] for d in self.data))
def all_selected(self, instance):
self.update(list(d['text'] for d in self.data), sel=True)
def get_selected_indices(self):
return list(d['index'] for d in self.data if d['selected'])
class TestPage(GridLayout):
def __init__(self, **kwargs):
super().__init__(**kwargs)
self.padding = [5]*4
self.cols = 1
# Usage example
rv = RV(size_hint=(1, 0.8))
items = [i for i in range(0, 100)]
my_default_selections = [3, 8, 42]
default = [i in my_default_selections for i \
in range(len(items))]
rv.update([f"item {str(i)}" for i in items],
default=default)
self.add_widget(rv)
# Access to funcionalities (example)
btn_all = Button(text="Select All", size_hint=(1, 0.05))
btn_all.bind(on_release=rv.all_selected)
self.add_widget(btn_all)
btn_none = Button(text="Select None", size_hint=(1, 0.05))
btn_none.bind(on_release=rv.none_selected)
self.add_widget(btn_none)
self.btn_get = Button(size_hint=(1, 0.1))
self.update()
self.btn_get.bind(on_release=self.update)
self.add_widget(self.btn_get)
def update(self, *largs, **kwargs):
self.btn_get.text='Click to update:\n' + \
str(self.rv.get_selected_indices())
if __name__ == '__main__':
class TestApp(App):
def build(self):
return TestPage()
TestApp().run()
Now I can generate a RV instance with multiline selection that can:现在我可以生成一个带有多行选择的 RV 实例,它可以:
I'm still looking for improvements, though.不过,我仍在寻求改进。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.