使用自定义小部件的 Kivy 复选框问题

Kivy checkbox issue using custom widget

我是 Kivy 的新手(一般来说是编程新手),所以忙着做一些小项目来尝试学习。特别是我试图了解在 Kivy 中创建自定义小部件以减少代码并在小部件组总是一起使用时更容易(即带有 CheckBox 的标签)。

我创建了一个自定义小部件来组合标签和复选框。在此示例中,我想根据选中的复选框更改图像。

在代码中我制作了两个版本,一个使用自定义小部件,另一个拆分出 Label 和 Checkbox。第二种方法有效,但自定义小部件没有错误:

 AttributeError: 'NoneType' object has no attribute 'ids'

在我提取此片段的程序中,我制作了另一个带有两个标签的小部件,其中一个标签是标题,另一个标签通过时钟更新。它工作正常,所以我不明白为什么不行。

这是我的 kv 和 py 文件:

#:kivy 2.0.0

<SoundCheckBox@GridLayout>:
    cols:2
    label_text: ''
    check_state: 'normal'
    Label:
        text: self.parent.label_text
    CheckBox:
        state: self.parent.check_state
        allow_no_selection: False
        group: 'sound_option'
        on_active: app.sound_stat(self, self.active, self.parent.label_text)

<MyCheckBox@CheckBox>:
    state: 'normal'
    allow_no_selection: False
    group: 'sound_option'
    on_active: app.sound_stat_alt(self, self.active, self.label_text)

GridLayout:
    rows: 4
    Label: 
        text: 'Change Image Test'

    BoxLayout:
        orientation:'horizontal'

        SoundCheckBox:
            id: sound_on
            label_text: 'On'
            check_state: 'normal'
        SoundCheckBox:
            id: sound_dings
            label_text: 'Ding'
            check_state: 'down'
        SoundCheckBox:
            id: sound_off
            label_text: 'Off'
            check_state: 'normal'

    BoxLayout:
        orientation:'horizontal'
        Label:
            text: 'On (a)'
        MyCheckBox:
            id: sound_on
            label_text: 'On'
        Label:
            text: 'Ding (a)'
        MyCheckBox:
            id: sound_dings
            label_text: 'Ding'
        Label:
            text: 'Off (a)'
        MyCheckBox:
            id: sound_off
            label_text: 'Off'

    Image:
        id: im
        source: 'sound-On.png'

和 Python 文件

from kivy.app import App
    
    
    class TestApp(App):
    
        def on_start(self):
            print(self.root.ids)    
    
        def sound_stat(self, instance, value, option):
            image_source = f'sound-{option}.png'
            # depending on check box is checked I want to set an image
            # print(self.root.ids) # Fails if this is not commented out
            
            self.root.ids.im.source = image_source # Fails if this is not commented out
    
            print(f'value is {value} and option is {option} so image source will be {image_source}')
    
        def sound_stat_alt(self, instance, value, option):
            image_source = f'sound-{option}.png'
            # depending on check box is checked I want to set an image
            print(self.root.ids) 
            self.root.ids.im.source = image_source
            print(f'value is {value} and option is {option} so image source will be {image_source}')
    
    if __name__ == "__main__":
        TestApp().run()

它不会 运行 除非我按照代码中的注释注释掉 sound_stat 方法中的行。非常感谢任何见解。

编辑:我刚刚注意到如果我不将复选框状态设置为 'down',上面的代码将起作用。我尝试将另一个 CheckBox 的状态之一也设置为“向下”,但我得到了同样的错误。所以它有效,但我无法在 kv 文件中设置默认选择。为什么会这样? 因此对于 kv 文件中的这段代码,如果我将所有状态设为 'normal',则代码将 运行。一旦我将它们中的任何一个设置为 'down',它就会因属性错误而失败。然后,我在 App class 的 start_up 方法中将默认按钮设置为 'down'。这是一个错误吗?当然这应该可以在我尝试的 kv 文件中设置。

 SoundCheckBox:
        id: sound_on
        label_text: 'On'
        check_state: 'normal'
    SoundCheckBox:
        id: sound_dings
        label_text: 'Ding'
        check_state: 'down' # This 'down' causes the crash.
    SoundCheckBox:
        id: sound_off
        label_text: 'Off'
        check_state: 'normal'

问题是,当您在 kv 文件中设置 SoundCheckBoxcheck_state 时,会触发 on_active 操作。但这甚至发生在 Approot 小部件被分配之前。解决方法是使用 Clock.schedule_once() 之类的方法延迟 check_state 的设置,或者忽略在分配 root 小部件之前发生的对 check_state 的任何更改,如下所示:

def sound_stat(self, instance, value, option):
    # ignore calls to this method that happen before the root widget is assigned
    if self.root is None:
        return
    image_source = f'sound-{option}.png'
    # depending on check box is checked I want to set an image
    # print(self.root.ids) # Fails if this is not commented out

    self.root.ids.im.source = image_source  # Fails if this is not commented out

    print(f'value is {value} and option is {option} so image source will be {image_source}')