Frankster
Frankster

Reputation: 689

How to select entire row in RecycleView using Kivy

I’m fairly new in python but a complete beginner with Kivy. I find the documentation around Kivy to be hard to interpret at least for me. So far I have been able to create a RecycleView where I am able to add, remove and change data. Ideally, I want to be able to have as many columns as I want but all the data on each row belongs together. Hence if I select one I want to highlight all items on that row. I have tried with togglebutton but without any success. I simply don’t know how I can access each individual togglebutton in the RecycleView through ids or some other method. If I could access each togglebutton individually I could simply change its state to be equal to ‘Down’. So far I have only been able to find which row the user selected (however not so elegantly). I managed this by calculating the number of buttons per row and comparing this against the index of the selected button.

Here is an example where I'm experimenting, usually I prefer using the .kv file:

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):
        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)


KV = '''

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

TestRecycleView:
    data: [{'text': str(x)} for x in range(21)]
    viewclass: 'MyButton'
    id: rv_controller
    target_id: None
    RecycleGridLayout:
        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()

Since I have been able to calculate what row got selected by the user I can also calculate what data in the RecycleView that is being selected. However, I would like a neat visual representation for this kind of row selection.

Would greatly appreciate some help.

Upvotes: 0

Views: 816

Answers (1)

John Anderson
John Anderson

Reputation: 38992

You can add a hack to your find_row() method to set the state of all the buttons in a row. This method sets them to agree with the state of the Button instance selected, so you can also unselect.

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

This also requires setting the id of the RecycleGridLayout as:

id: gl

The Buttons are children of the RecycleGridLayout, so that can be used to get the total number of Buttons. And the index into the children list is the opposite of what you would expect. That is that the Button labelled 0 is index 20 and the Button labelled 20 is index 0. So the calculation of index1 uses that knowledge to determine the index of the left-most Button in the selected row. Then the right-most Button in the selected row will be index - 2. The range(inde1, index2, -1) steps through the Buttons in the selected row. Since find_row() is called on_release, the state of the instance is already toggled, so its state can be used to set the state of the other Buttons in the row.

Upvotes: 1

Related Questions