1

I'm trying to create a simple table where selecting one item in the RecycleView selects the entire row. This has been solved by changing the state of each togglebutton on the clicked row, through its index. However, if I want to manually add some data to my recycleview and afterwards update the table using the refresh_from_data() method, this leads to some strange behavior. The refresh_from_data() method changes the indices of the buttons in the recycleview.

By changing the state of the togglebutton with the index = 0, one can observe how the index[0] toggles between being located in top left corner of the RecycleView and the bottom right corner of the RecycleView. My code below illustrates this. I have set index[0] to be equal to 'down' as soon as any button is pressed.

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.recycleview import RecycleView
from kivy.uix.togglebutton import ToggleButton
from kivy.uix.recycleview.views import RecycleDataViewBehavior


class MyButton(RecycleDataViewBehavior, ToggleButton):
    index = None

    def refresh_view_attrs(self, rv, index, data):
        """ Catch and handle the view changes """
        self.index = index
        return super(MyButton, self).refresh_view_attrs(
            rv, index, data)


class TestRecycleView(RecycleView):
    items_per_row = 3
    selected_data = None
    selected_row = None

    def find_row(self, instance):
        rgl = self.ids.gl
        num_buttons = len(rgl.children)
        num_rows = num_buttons // self.items_per_row
        self.selected_row = instance.index // self.items_per_row + 1
        print('Row: ', self.selected_row)
        self.selected_data = self.data[(self.selected_row - 1) * self.items_per_row: self.items_per_row
                                                                                     * self.selected_row]
        print('Data: ', self.selected_data)
        index1 = (num_rows - self.selected_row + 1) * self.items_per_row - 1
        index2 = index1 - self.items_per_row
        state = instance.state
        for index in range(index1, index2, -1):
            rgl.children[index].state = state

        print('======================')
        print('    Add some data     ')
        print('======================')

        # See how index[0] changes between the refresh()
        rgl.children[0].state = "down"
        root = App.get_running_app().root
        root.refresh_from_data()


KV = '''

<MyButton>:
    on_release:
        app.root.find_row(self)

TestRecycleView:
    data: [{'text': str(x)} if x not in range(9,12) else {'text': 'Press here'} for x in range(21)]
    viewclass: 'MyButton'
    id: rv_controller
    target_id: None
    RecycleGridLayout:
        id: gl
        cols: 3
        default_size_hint: 1, None
        orientation: 'vertical'
        key_selection: 'selectable'
        default_size: None, dp(26)
        size_hint_y: None
        height: self.minimum_height
        multiselect: True
        touch_multiselect: True

'''


class Test(App):
    def build(self):
        root = Builder.load_string(KV)
        # root.data = items
        return root


Test().run()

I have concluded that the indices toggle between being 0,1,2,3.... and end, end-1, end-2,...Meaning that a selected row gets "mirrored" over the middle row between each button press.

But why is the RecycleView behaving this way and what do I do about it? All I want to do is to be able to select an entire row with one click, manually add/change data and I also only want one row to be selected at any given time.

But the refresh_from_data() method is making it very hard for me to do.

Frankster
  • 653
  • 7
  • 26

1 Answers1

1

I think slightly different approach will work better. Rather than setting the state of specific buttons, add the state property to the data, and change the state in the data. That way the RecycleView will set the state according to the data, and its swapping of the buttons will not matter. So in the kv, change the data to:

TestRecycleView:
    data: [{'text': str(x), 'state': 'normal'} for x in range(21)]

and change find_row() to:

def find_row(self, instance):
    self.selected_row = instance.index // self.items_per_row + 1
    start_index = (self.selected_row - 1) * self.items_per_row
    for index in range(start_index, start_index + self.items_per_row):
        self.data[index]['state'] = 'down' # change data, not button state
    self.refresh_from_data()
John Anderson
  • 35,991
  • 4
  • 13
  • 36