Kivy: AttributeError: объект «NoneType» не имеет «родительского» атрибута при прокрутке вниз и снова прокрутке вверх в режиме просмотра корзины

Описание моего приложения Kivy:

У меня есть 3 типа виджетов в MyFirstScreen:

  • RecycleView, который имеет несколько "Пользователей" в качестве элементов. (каждый элемент dictionary)
  • Три TextInput, которые связаны со значениями каждого элемента представления корзины. (если вы выберете какие-либо элементы RecycleView, эти TextInput будут загружены с соответствующими значениями dictionary)
  • «Добавить нового пользователя» Button. (если вы введете значения NEW в TextInputss и нажмете эту кнопку, RecycleView будет обновлено: предыдущие элементы + ваш новый элемент)

Проблема:

Ошибка возникает в следующих двух случаях:

Ситуация А:

  1. Я выбираю элемент (например: «Пользователь 1»).
  2. Я прокручиваю ВНИЗ, пока выбранный элемент полностью не скроется от RecycleView.
  3. Я прокручиваю ВВЕРХ, чтобы снова отобразить выбранный элемент.

Ситуация Б:

  1. Я выбираю элемент (например: «Пользователь 1»).
  2. Я меняю новые текстовые значения, которые загружаются в TextInputss.
  3. Я нажимаю "Добавить нового пользователя" 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()

person sirAmin    schedule 20.05.2020    source источник
comment
Когда вы пишете такие вещи, как self.parent.parent.parent.parent.parent.system_name_text_input_id, как вы подтверждаете себе, что у вас есть правильное количество parent?   -  person Karl Knechtel    schedule 21.05.2020
comment
Привет, Карл! Спасибо за ваш комментарий. Используя print(self.paret) и print(self.paren.parent) и т. д., я нашел правильного родителя этого идентификатора. Из моего предыдущего вопроса (можно не обновлять значение ввода текста из другого класса) этот метод был предложен мне. У вас есть идея получше?   -  person sirAmin    schedule 21.05.2020
comment
возможно, снова используйте print(self.paret) и print(self.paren.parent) и т. д., чтобы увидеть, нужно ли по-прежнему такое же количество parent.   -  person furas    schedule 21.05.2020


Ответы (1)


Кажется, он удаляет Label из RecycleViewWidget, когда он не виден, и тогда Label не имеет родителя.

Когда Label виден напротив, он помещает Label в RecycleViewWidget и снова имеет parent. Но сначала он выполняет apply_selection, поэтому он запускает его до того, как Label снова получит родителя.

Аналогично, когда он создает новый Label, затем он сначала выполняет apply_selection, прежде чем добавлять Labal к RecycleViewWidget, поэтому он запускает его до того, как Label получит родителя.


Но в этой ситуации вы можете использовать rv (экземпляр RecycleViewWidget), чтобы получить доступ к FirstScreen и получить доступ к TextInput.

Теперь я отправляю rv и index вместо rv.data[index] на update_text_inputs, чтобы использовать его для получения rv.parent.parent.parent и rv.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

EDIT: я обнаружил, что вы можете получить его и без использования 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")

Кстати: я также использую rv вместо RecycleViewWidget() в refresh_view_attrs(), потому что это может создать ту же проблему, что и в предыдущем вопросе — RecycleViewWidget() может создать новый экземпляр RecycleViewWidget, и вы должны работать с исходным первым экземпляром RecycleViewWidget

person furas    schedule 21.05.2020
comment
Да! Это правильно. furas вы помогли мне понять важную концепцию ООП в этом и предыдущем вопросе. Большое спасибо еще раз... - person sirAmin; 21.05.2020