Kivy 使用 RecycleView 的 CheckBox 问题

Posted

技术标签:

【中文标题】Kivy 使用 RecycleView 的 CheckBox 问题【英文标题】:CheckBox problems with Kivy using RecycleView 【发布时间】:2021-09-17 11:45:27 【问题描述】:

最少的代码是用于一个图像网格,每个网格都有一个使用 Kivy、RecycleView 和 RecycleGridLayout 的复选框。问题包括: i) 复选框的选择/取消选择不显示; 2) 它正在重置复选框,因此取消选择似乎是一个选择 - 请参阅 on_checkbox_active 和 on_checkbox_press 中 print() 语句的输出(包括两者的代码)。

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

【问题讨论】:

【参考方案1】:

GridTilecb_state 属性被RecycleView 更改时,您只是缺少设置CheckBox state 的代码:

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

【讨论】:

啊,做到了。效果很好。谢谢。 如果在 GridTile 中使用了“on_active: app.on_checkbox_active(self, ...)”,则在选中复选框时,RecycleView 进入无限循环。连续输出中的消息是“[CRITICAL] [Clock] 警告,在下一帧之前完成了太多迭代。检查您的代码,或增加 Clock.max_iteration 属性”。某个地方需要一个 Clock() 函数吗?查看运行新的最小代码。 另外,在“on_cb_state()”中,“grid_tile”是什么?一个 print(grid_tile) 给出“ 如果使用on_active,那么当on_checkbox_active()改变CheckBox状态时,active属性发生改变,触发on_active(),导致循环。 grid_tile 变量是GridTile 实例(与self 相同)。【参考方案2】:

在 GridTile 中设置了“on_active: app.on_checkbox_active(self, ...)”的最小代码。 The RecycleView enters an infinite loop when any checkbox is selected.连续输出中的信息是:

[CRITICAL] [Clock] 警告,在下一次之前完成了太多迭代 框架。检查你的代码,或者增加 Clock.max_iteration 属性

某个地方需要一个 Clock() 函数吗?

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

【讨论】:

以上是关于Kivy 使用 RecycleView 的 CheckBox 问题的主要内容,如果未能解决你的问题,请参考以下文章

在 Kivy 中使用 RecycleView 的自定义小部件的对齐问题

RecycleView 导致 Kivy 可执行文件崩溃

轮播滑动方向的 Kivy RecycleView 不起作用

Kivy:制作表格小部件

KivyMD RecycleView 的 FPS 和延迟较低

如何在 Kivy python 中最大化滚动条?