KivyMD RecycleView 网格中的复选框操作重复
CheckBox Action Repeats in a KivyMD RecycleView Grid
当项目的复选框在回收视图网格中为 clicked/unclicked 时,click/unclick 也会自动重复网格中的其他数据项。为什么会这样?下面的代码是一个最小的工作示例。谢谢
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()
RecycleView
通过回收最少数量的 viewclass
实例来工作,在您的情况下是 GridTile
。 RecycleView
根据 data
中的条目将属性分配给 GridTile
的那些实例。如果您更改 GridTile
或其子项的任何属性,而这些属性未在 data
中处理,则 RecycleView
不会意识到这些更改,并且这些更改将保留在 [= 的回收实例中=12=]。因此,如果您希望 MDCheckBox
状态得到正确处理,您必须将其作为 GridTile
的另一个 属性 包含在您的 data
中。事实上,您的 MDCheckBox
不在您的 kv
中,这使得这更难实现。这回答了 why
问题。
这是您原始发布代码的修改版本。此版本有效,但 GridTile
个实例之间存在一些交互(当您单击一个复选框时,另一个 GridTile
似乎会自行刷新)。我只见过这种与 KivyMd 的互动。在没有 KivyMD 的情况下编写类似的应用程序不会显示奇怪的交互。
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()
修改的要点是将 index
和 cb_state
属性添加到 GridTile
class 和 data
。 index
属性 只是在调整 data
时用作 data
的索引。 cb_state
是 MDCheckbox
的 state
。由于 MDCheckbox
没有出现在 kv
中,如果 cb_state
属性 到 MDChckbox
的实际 state
则不会自动绑定,以便在 GridTile
class 中显式创建绑定。此外,MDCheckbox
更新 data
的绑定更改为绑定到 on_press
,而不是 on_active
,因为 active
属性将由 RecycleView
基于 data
更改,并可能导致循环效果。
当项目的复选框在回收视图网格中为 clicked/unclicked 时,click/unclick 也会自动重复网格中的其他数据项。为什么会这样?下面的代码是一个最小的工作示例。谢谢
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()
RecycleView
通过回收最少数量的 viewclass
实例来工作,在您的情况下是 GridTile
。 RecycleView
根据 data
中的条目将属性分配给 GridTile
的那些实例。如果您更改 GridTile
或其子项的任何属性,而这些属性未在 data
中处理,则 RecycleView
不会意识到这些更改,并且这些更改将保留在 [= 的回收实例中=12=]。因此,如果您希望 MDCheckBox
状态得到正确处理,您必须将其作为 GridTile
的另一个 属性 包含在您的 data
中。事实上,您的 MDCheckBox
不在您的 kv
中,这使得这更难实现。这回答了 why
问题。
这是您原始发布代码的修改版本。此版本有效,但 GridTile
个实例之间存在一些交互(当您单击一个复选框时,另一个 GridTile
似乎会自行刷新)。我只见过这种与 KivyMd 的互动。在没有 KivyMD 的情况下编写类似的应用程序不会显示奇怪的交互。
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()
修改的要点是将 index
和 cb_state
属性添加到 GridTile
class 和 data
。 index
属性 只是在调整 data
时用作 data
的索引。 cb_state
是 MDCheckbox
的 state
。由于 MDCheckbox
没有出现在 kv
中,如果 cb_state
属性 到 MDChckbox
的实际 state
则不会自动绑定,以便在 GridTile
class 中显式创建绑定。此外,MDCheckbox
更新 data
的绑定更改为绑定到 on_press
,而不是 on_active
,因为 active
属性将由 RecycleView
基于 data
更改,并可能导致循环效果。