Kivy: AttributeError: 'NoneType' object has no attribute 'parent' when scroll down and scroll up again in recycle view

Kivy: AttributeError: 'NoneType' object has no attribute 'parent' when scroll down and scroll up again in recycle view

My Kivy 应用说明:

我在 MyFirstScreen 中有 3 种类型的小部件:

问题:

这两种情况都会出错:

情况一:

  1. 我 select 一个项目(例如:"User 1")。
  2. 我向下滚动直到 selected 项目完全隐藏 RecycleView
  3. 我向上滚动以再次显示 selected 项目。

情况B:

  1. 我 select 一个项目(例如:"User 1")。
  2. 我更改了在 TextInputss 中加载的新文本值。
  3. 我按了"Add New User"Button.

在这两种情况下,当我要执行第 3 步时,都会出现此错误:

my_text_input= self.parent.parent.parent.parent.parent.system_name_text_input_id

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

有谁知道我在这里做错了什么或如何让它工作?提前谢谢你...

我的KivyTest.py文件:

from kivy.app import App
from kivy.lang import Builder
from kivy.uix.label import Label
from kivy.uix.screenmanager import ScreenManager, Screen
from kivy.uix.recycleview import RecycleView
from kivy.uix.recycleview.views import RecycleDataViewBehavior
from kivy.properties import BooleanProperty
from kivy.uix.recycleboxlayout import RecycleBoxLayout
from kivy.uix.behaviors import FocusBehavior
from kivy.uix.recycleview.layout import LayoutSelectionBehavior


class Manager(ScreenManager):

    def __init__(self, **kwargs):
        super().__init__(**kwargs)


class MyFirstScreen(Screen):
    def __init__(self, **kwarg):
        super().__init__(**kwarg)
        print("__init__ of MyFirstScreen is Called")

    def update_recycle_view(self):
        global is_repetitive
        system_name_ti = self.ids.user_name_text_input_id.text
        system_id_ti = self.ids.user_id_text_input_id.text
        current_items_in_recycle_view = self.ids.recycle_view_widget_id.items_of_rv
        new_item = {"color": (0, 0, 0, 1), "font_size": "20", "text": "", "user_id": ""}

        for item_index in range(len(current_items_in_recycle_view)):
            current_item_name = current_items_in_recycle_view[item_index].get("text")
            current_item_id = current_items_in_recycle_view[item_index].get("user_id")
            print(f"current_item_name: {current_item_name}_______current_item_id: {current_item_id}")

            if system_name_ti == current_item_name:
                print("Error: Repetitive User Name")
                is_repetitive = True
                break
            elif system_id_ti == current_item_id:
                print("Error: Repetitive User ID")
                is_repetitive = True
                break
            else:
                is_repetitive = False

        if not is_repetitive:
            print("else situation")
            new_item.update({"text": system_name_ti})
            new_item.update({"user_id": system_id_ti})
            self.ids.recycle_view_widget_id.add_new_item_to_data(new_item)


class RecycleViewWidget(RecycleView):
    def __init__(self, **kwargs):
        super(RecycleViewWidget, self).__init__(**kwargs)
        self.items_of_rv = []
        self.update_my_items()
        self.update_my_data()

    def update_my_items(self):
        for i in range(1, 21):
            self.items_of_rv.append(
                {"color": (0, 0, 0, 1), "font_size": "20", "text": f"Use {i}",
                 "user_id": f"{100 * i}"})

    def update_my_data(self):
        self.data = [item for item in self.items_of_rv]

    def add_new_item_to_data(self, new_item):
        self.data.append(new_item)
        self.refresh_from_data()
        print("add_new_item_to_data called")


class SelectableRecycleBoxLayout(FocusBehavior, LayoutSelectionBehavior, RecycleBoxLayout):
    """ Adds selection and focus behaviour to the view. """


class SelectableLabel(RecycleDataViewBehavior, Label):
    """ Add selection support to the Label """
    index = None
    selected = BooleanProperty(False)
    selectable = BooleanProperty(True)

    def refresh_view_attrs(self, rv, index, data):
        """ Catch and handle the view changes """
        self.index = index
        return super(SelectableLabel, self).refresh_view_attrs(rv, index, data)

    def on_touch_down(self, touch):
        """ Add selection on touch down """
        if super(SelectableLabel, self).on_touch_down(touch):
            return True
        if self.collide_point(*touch.pos) and self.selectable:
            return self.parent.select_with_touch(self.index, touch)

    def apply_selection(self, rv, index, is_selected):
        """ Respond to the selection of items in the view. """

        self.selected = not is_selected
        if is_selected:
            rv.data[index].update({'color': (1, 1, 1, 1)})
            self.refresh_view_attrs(RecycleViewWidget(), index, rv.data[index])
            print("selection changed to {0}".format(rv.data[index]))
            self.update_text_inputs(rv.data[index])
        else:
            print("selection removed from {0}".format(rv.data[index]))
            if rv.data[index].get("color") == (1, 1, 1, 1):
                rv.data[index].update({'color': (0, 0, 0, 1)})
                self.refresh_view_attrs(RecycleViewWidget(), index, rv.data[index])
        self.selected = not self.selected

    def update_text_inputs(self, selected_system, *kwarg):
        user_name_text_input = self.parent.parent.parent.parent.parent.user_name_text_input_id
        user_id_text_input = self.parent.parent.parent.parent.parent.user_id_text_input_id

        user_name_text_input.text = selected_system.get("text")
        user_id_text_input.text = selected_system.get("user_id")


main_style = Builder.load_file("test.kv")


class MyApp(App):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)

    def build(self):
        return main_style


if __name__ == '__main__':
    MyApp().run()

我的test.kv文件:

Manager:
    MyFirstScreen:

<SelectableLabel>:
    canvas.before:
        Color:
            rgba: (0, 0, 1, 1) if self.selected else (1, 1, 1, 1)
        Rectangle:
            pos: self.pos
            size: self.size
<RecycleViewWidget>:
    viewclass: 'SelectableLabel'
    SelectableRecycleBoxLayout:
        default_size: None, dp(56)
        default_size_hint: 1, None
        size_hint_y: None
        height: self.minimum_height
        orientation: 'vertical'

<MyFirstScreen>:
    name: 'system_setup_page'
    user_name_text_input_id:user_name_text_input_id
    user_id_text_input_id:user_id_text_input_id
    recycle_view_widget_id:recycle_view_widget_id
    GridLayout:
        cols: 2
        BoxLayout:
            cols: 1
            padding: 20
            RecycleViewWidget:
                id:recycle_view_widget_id
        FloatLayout:
            Label:
                size_hint: None, None
                text: "User Name:"
                font_size: 22
                pos_hint: {'center': (20/100, 90/100)}
            TextInput:
                id: user_name_text_input_id
                size_hint: None, None
                hint_text: "Type Your Name..."
                size: 200, 30
                multiline: False
                pos_hint: {'center': (65/100, 90/100)}
            Label:
                size_hint: None, None
                text: "User ID:"
                font_size: 20
                pos_hint: {'center': (20/100, 70/100)}
            TextInput:
                id: user_id_text_input_id
                size_hint: None, None
                size: 200, 30
                hint_text: "Type Your ID..."
                pos_hint: {'center': (65/100, 70/100)}
            Button:
                text: "Add New User"
                size_hint: None, None
                font_size: 20
                size: 300, 50
                pos_hint: {'center': (50/100, 30/100)}
                on_release: root.update_recycle_view()

它似乎在 RecycleViewWidget 不可见时从 Label 中删除了它 - 然后 Label 就没有 parent。

Label 可见时,它会将 Label 放在 RecycleViewWidget 中,然后它再次出现 parent。但首先它执行 apply_selection - 所以它在 Label 再次具有 parent 之前运行它。

类似的,当它创建新的 Label 然后它首先执行 apply_selection 然后将 Labal 添加到 RecycleViewWidget - 所以它在 Label 之前运行它parent.


但在这种情况下,您可以使用 rvRecycleViewWidget 的实例)访问 FirstScreen 并访问 TextInput

现在我发送 rvindex 而不是 rv.data[index]update_text_inputs 所以我可以用它来获得 rv.parent.parent.parentrv.data[index]

screen = rv.parent.parent.parent

user_name_text_input = screen.user_name_text_input_id
user_id_text_input   = screen.user_id_text_input_id

编辑: 我发现你也可以在不使用 parent 和不使用 rv

的情况下获得它
screen = main_style.screens[0]
# or
#screen = main_style.screens[0].ids

user_name_text_input = screen.user_name_text_input_id
user_id_text_input   = screen.user_id_text_input_id

def apply_selection(self, rv, index, is_selected):
    """ Respond to the selection of items in the view. """

    self.selected = is_selected

    if is_selected:
        rv.data[index].update({'color': (1, 1, 1, 1)})

        self.refresh_view_attrs(rv, index, rv.data[index])

        print("selection changed to {0}".format(rv.data[index]))

        self.update_text_inputs(rv, index)
    else:
        rv.data[index].update({'color': (0, 0, 0, 1)})

        self.refresh_view_attrs(rv, index, rv.data[index])

        print("selection removed from {0}".format(rv.data[index]))


def update_text_inputs(self, rv, index, *kwarg):
    screen = rv.parent.parent.parent
    #screen = main_style.screens[0]
    #screen = main_style.screens[0].ids
    print('[DEBUG] screen:', screen)

    user_name_text_input = screen.user_name_text_input_id
    user_id_text_input   = screen.user_id_text_input_id

    user_name_text_input.text = rv.data[index].get("text")
    user_id_text_input.text   = rv.data[index].get("user_id")

顺便说一句: 我也在 refresh_view_attrs() 中使用 rv 而不是 RecycleViewWidget() 因为它会产生与上一个问题相同的问题 - RecycleViewWidget() 可以创建 RecycleViewWidget 的新实例,您应该使用 RecycleViewWidget

的原始第一个实例