1

The minimum code is for a grid of images each with a checkbox using Kivy, RecycleView and RecycleGridLayout. The problems include: i) The selection/deselection of a checkbox doesn't display; 2) It is resetting the checkbox so a deselect appears to be a select - see output of the print() statements in on_checkbox_active and on_checkbox_press (code for both is included).

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.recycleview import RecycleView
from kivy.properties import StringProperty, ListProperty, NumericProperty

Builder.load_string("""
<GridTile>:
    AsyncImage:
        source: root.tile
        size_hint_y: None
        height: '150dp'
        CheckBox:
            id: ck
            root_ref: root.index  # create reference to containing GridTile
            # on_active: app.on_checkbox_active(self, self.active, self.state, self.root_ref)
            on_press: app.on_checkbox_press(self, self.active, self.state, self.root_ref)

<GridScreen>:
    name: 'grid_screen'
    RV:
        id: rv
        viewclass: 'GridTile'
        RecycleGridLayout:
            cols: 2
            size_hint_y: None
            default_size: 1, dp(150)
            default_size_hint: 1, None
            height: self.minimum_height
""")

class GridTile(Screen):
    # properties to be set in the rv.data
    tile = StringProperty('')
    index = NumericProperty(-1)
    cb_state = StringProperty('normal')

class GridScreen(Screen):
    pass

class RV(RecycleView):
    data = ListProperty('[]')

    def __init__(self, **kwargs):
        super(RV, self).__init__(**kwargs)
        self.cell_data()

    def cell_data(self):
        self.data = [{"tile": 'The Rolling Stones', "index": i, "cb_state": 'normal'} for i in range(41)]

class ThisApp(App):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def build(self):
        self.sm = ScreenManager()
        self.sm.add_widget(GridScreen(name='grid_screen'))
        return self.sm

    def on_checkbox_active(self, checkbox, active, state, root_ref):
        if active:
            print(active, state, root_ref)
        else:
            print(active, state, root_ref)

        new_state = checkbox.state

        # set checkbox state back to the default
        checkbox.state = 'normal'  # avoids setting checkbox state without data

        rv = self.root.get_screen('grid_screen').ids.rv
        rv.data[root_ref]['cb_state'] = new_state
        rv.refresh_from_data()  # set the state from data

    def on_checkbox_press(self, checkbox, active, state, root_ref):
        if active:
            print(active, state, root_ref)
        else:
            print(active, state, root_ref)

        new_state = checkbox.state

        # set checkbox state back to the default
        checkbox.state = 'normal'  # avoids setting checkbox state without data

        rv = self.root.get_screen('grid_screen').ids.rv
        rv.data[root_ref]['cb_state'] = new_state
        rv.refresh_from_data()  # set the state from data

if __name__ == "__main__":
    ThisApp().run()
Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Henry Thornton
  • 4,381
  • 9
  • 36
  • 43

2 Answers2

1

You are just missing the code to set the CheckBox state when the cb_state property of GridTile gets changed by the RecycleView:

class GridTile(Screen):
    # properties to be set in the rv.data
    tile = StringProperty('')
    index = NumericProperty(-1)
    cb_state = StringProperty('normal')

    # code to set CheckBox state based on cb_state property
    # this could also be accomplished with a `bind`
    def on_cb_state(self, grid_tile, new_state):
        self.ids.ck.state = new_state
John Anderson
  • 35,991
  • 4
  • 13
  • 36
  • Ah, that did it. Works nicely. Thank-you. – Henry Thornton Jul 07 '21 at 13:52
  • If "on_active: app.on_checkbox_active(self, ...)" is used in GridTile, the RecycleView enters an infinite loop when a checkbox is selected. The message in the continuous output is "[CRITICAL] [Clock] Warning, too much iteration done before the next frame. Check your code, or increase the Clock.max_iteration attribute". Is a Clock() function needed someplace? To see run new min code. – Henry Thornton Jul 07 '21 at 13:53
  • Also, in "on_cb_state()", what is 'grid_tile'? A print(grid_tile) gives "" – Henry Thornton Jul 07 '21 at 14:55
  • If you use `on_active`, then when `on_checkbox_active()` changes the CheckBox state, the `active` property is changed, triggering the `on_active()`, which results in a loop. The `grid_tile` variable is the `GridTile` instance (its the same as `self`). – John Anderson Jul 07 '21 at 15:20
1

Min code with "on_active: app.on_checkbox_active(self, ...)" set in GridTile. The RecycleView enters an infinite loop when any checkbox is selected. The message in the continuous output is:

[CRITICAL] [Clock] Warning, too much iteration done before the next frame. Check your code, or increase the Clock.max_iteration attribute

Is a Clock() function needed someplace?

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.recycleview import RecycleView
from kivy.properties import StringProperty, ListProperty, NumericProperty

Builder.load_string("""
<GridTile>:
    AsyncImage:
        source: root.tile
        size_hint_y: None
        height: '150dp'
        CheckBox:
            id: ck
            root_ref: root.index  # create reference to containing GridTile
            on_active: app.on_checkbox_active(self, self.active, self.state, self.root_ref)
            # on_press: app.on_checkbox_press(self, self.active, self.state, self.root_ref)

<GridScreen>:
    name: 'grid_screen'
    RV:
        id: rv
        viewclass: 'GridTile'
        RecycleGridLayout:
            cols: 2
            size_hint_y: None
            default_size: 1, dp(150)
            default_size_hint: 1, None
            height: self.minimum_height
""")

class GridTile(Screen):
    # properties to be set in the rv.data
    tile = StringProperty('')
    index = NumericProperty(-1)
    cb_state = StringProperty('normal')

    def __init__(self, **kwargs):
        super(GridTile, self).__init__(**kwargs)
        self.bind(cb_state=self.set_cb_state)  # bind the cb_state property to set the state of the CheckBox

    def set_cb_state(self, gridtile, cb_state_value):
        self.ids.ck.state = cb_state_value  # actually set the state of the CheckBox

class GridScreen(Screen):
    pass

class RV(RecycleView):
    data = ListProperty('[]')

    def __init__(self, **kwargs):
        super(RV, self).__init__(**kwargs)
        self.cell_data()

    def cell_data(self):
        self.data = [{"tile": 'The Beatles', "index": i, "cb_state": 'normal'} for i in range(41)]

class ThisApp(App):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def build(self):
        self.sm = ScreenManager()
        self.sm.add_widget(GridScreen(name='grid_screen'))
        return self.sm

    def on_checkbox_active(self, checkbox, active, state, root_ref):
        if active:
            print(active, state, root_ref)
        else:
            print(active, state, root_ref)

        new_state = checkbox.state

        # set checkbox state back to the default
        checkbox.state = 'normal'  # avoids setting checkbox state without data

        rv = self.root.get_screen('grid_screen').ids.rv
        rv.data[root_ref]['cb_state'] = new_state
        rv.refresh_from_data()  # set the state from data

    def on_checkbox_press(self, checkbox, active, state, root_ref):
        if active:
            print(active, state, root_ref)
        else:
            print(active, state, root_ref)

        new_state = checkbox.state

        # set checkbox state back to the default
        checkbox.state = 'normal'  # avoids setting checkbox state without data

        rv = self.root.get_screen('grid_screen').ids.rv
        rv.data[root_ref]['cb_state'] = new_state
        rv.refresh_from_data()  # set the state from data

if __name__ == "__main__":
    ThisApp().run()
Henry Thornton
  • 4,381
  • 9
  • 36
  • 43