复选框操作在 KivyMD 回收视图网格中重复



当在回收视图网格中单击/取消单击某个项的复选框时,单击/取消单击也会自动对网格中的其他数据项重复。为什么会这样?下面的代码是一个最小的工作示例。谢谢。

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

这是您原始发布代码的修改版本。此版本有效,但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()

修改的要点是将indexcb_state属性添加到GridTile类和data中。index属性只是在调整data时用作data的索引。cb_stateMDCheckboxstate.由于MDCheckbox没有出现在kv中,如果cb_state属性到MDChckbox的实际state,则没有自动绑定,因此在GridTile类中显式创建绑定。此外,更新dataMDCheckbox的绑定被更改为绑定到on_press,而不是on_active,因为active属性将由RecycleView根据data更改,并可能导致循环效果。

RecycleView通过回收最少数量的viewclass实例来工作,这在您的情况下GridTileRecycleView根据data中的条目将属性分配给这些GridTile实例。如果您更改了未在data中处理的GridTile或其子项的任何属性,则RecycleView不知道这些更改,并且这些更改保留在GridTile的回收实例中。因此,如果您希望正确处理MDCheckBox状态,则必须将其作为GridTile的另一个属性包含在data中。事实上,你的MDCheckBox不在你的kv,这使得这更难实现。这回答了why问题。

最新更新