简体   繁体   English

使用 RecycleView (Python/Kivy) 正确设置默认选择

[英]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 实例,它可以:

  • Accept pre-selected defaults接受预先选择的默认值
  • Functionalities to "select all items" and "clear selections" “选择所有项目”和“清除选择”的功能
  • A method to retrieve a list of selections一种检索选择列表的方法

I'm still looking for improvements, though.不过,我仍在寻求改进。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM