When the checkbox for an item is clicked/unclicked in a recycleview grid, the click/unclick also automatically repeats for other data items in the grid. Why is this happening? The code below is a minimum working example. Thanks.
from kivy.lang import Builder
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.recycleview import RecycleView
from kivy.properties import StringProperty, ListProperty
from kivy.clock import Clock
from kivymd.app import MDApp
from kivymd.uix.imagelist import SmartTile
from kivymd.uix.selectioncontrol import MDCheckbox
Builder.load_string("""
<Check>:
<GridTile>:
SmartTile:
source: root.tile
size_hint_y: None
height: '150dp'
Check:
<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):
tile = StringProperty('')
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'} for i in range(41)]
class Check(SmartTile):
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self.add_checkbox)
def add_checkbox(self, interval):
app = MDApp.get_running_app()
self.check = MDCheckbox(size_hint=(None, None), size=(48, 48))
self.check.bind(active=app.on_checkbox_active)
self._box_overlay.add_widget(self.check)
class ThisApp(MDApp):
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, value):
if value:
print('The checkbox', checkbox, 'is active', 'and', checkbox.state, 'state')
else:
print('The checkbox', checkbox, 'is inactive', 'and', checkbox.state, 'state')
if __name__ == "__main__":
ThisApp().run()
Here is a modified version of your original posted code. This version works, but there is some interaction between GridTile
instances (when you click on one check box, another GridTile
appears to refresh itself). I have only seen this interaction with KivyMd. Writing a similar app without KivyMD does not display that odd interaction.
from functools import partial
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, ObjectProperty
from kivy.clock import Clock
from kivymd.app import MDApp
from kivymd.uix.imagelist import SmartTile
from kivymd.uix.selectioncontrol import MDCheckbox
Builder.load_string("""
<GridTile>:
SmartTile:
source: root.tile
size_hint_y: None
height: '150dp'
Check:
id: ck
root_ref: root # creat reference to containing GridTile
<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 MDCheckBox
def set_cb_state(self, gridtile, cb_state_value):
self.ids.ck.check.state = cb_state_value # actually set the state of the MDCheckBox
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 Check(SmartTile):
root_ref = ObjectProperty(None) # reference to the containing GridTile (set by kv)
def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self.add_checkbox)
def add_checkbox(self, interval):
app = MDApp.get_running_app()
self.check = MDCheckbox(size_hint=(None, None), size=(48, 48))
self.check.bind(on_press=partial(app.on_checkbox_press, self)) # bind to on_press to avoid possible looping when active is changed
self._box_overlay.add_widget(self.check)
class ThisApp(MDApp):
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_press(self, check, checkbox):
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[check.root_ref.index]['cb_state'] = new_state
rv.refresh_from_data() # set the state from data
if __name__ == "__main__":
ThisApp().run()
Th gist of the modifications is the adding of the index
and cb_state
properties to the GridTile
class and to the data
. The index
property is just used as the index into the data
when adjusting the data
. And the cb_state
is the state
of the MDCheckbox
. Since the MDCheckbox
does not appear in the kv
, there is no automatic binding if the cb_state
property to the actual state
of the MDChckbox
, so that binding is explicitly created in the GridTile
class. Also, the binding of the MDCheckbox
to update the data
is changed to bind to on_press
, rather than on_active
, since the active
property will be changed by the RecycleView
based on the data
and could result in a looping effecet.