Kivy:AttributeError:“NoneType"对象在向下滚动并在回收视图中再次向上滚动时没有属性“父"

2022-01-15 00:00:00 python kivy object nonetype attributeerror

问题描述

我的 Kivy 应用说明:

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

  • 一个 RecycleView 有多个用户"作为其项目.(每个项目都是一个字典)
  • 三个 TextInput 与每个回收视图项的值相关.(如果您选择 RecycleView 的任何项目,这些 TextInput 将加载相应的 dictionary 值)
  • 添加新用户"按钮.(如果您在 TextInputs 中输入 NEW 值并按下此按钮,RecycleView 将更新为:以前的项目 + 您的新项目)

问题:

这两种情况会报错:

情况 A:

  1. 我选择了一个项目(例如:用户 1").
  2. 我向下滚动,直到所选项目从 RecycleView 完全隐藏.
  3. 我向上滚动以再次显示所选项目.

情况 B:

  1. 我选择了一个项目(例如:用户 1").
  2. 我更改了 TextInputss 中加载的新文本值.
  3. 我按下添加新用户"按钮.

在两种情况下,当我想做第 3 步时,都会出现此错误:

my_text_input= self.parent.parent.parent.parent.parent.system_name_text_input_idAttributeError:NoneType"对象没有属性父"

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

我的 KivyTest.py 文件:

从 kivy.app 导入 App从 kivy.lang 导入生成器从 kivy.uix.label 导入标签从 kivy.uix.screenmanager 导入 ScreenManager,Screen从 kivy.uix.recycleview 导入 RecycleView从 kivy.uix.recycleview.views 导入 RecycleDataViewBehavior从 kivy.properties 导入 BooleanProperty从 kivy.uix.recycleboxlayout 导入 RecycleBoxLayout从 kivy.uix.behaviors 导入 FocusBehavior从 kivy.uix.recycleview.layout 导入 LayoutSelectionBehavior类管理器(屏幕管理器):def __init__(self, **kwargs):super().__init__(**kwargs)MyFirstScreen 类(屏幕):def __init__(self, **kwarg):super().__init__(**kwarg)print("调用了 MyFirstScreen 的 __init__")def update_recycle_view(self):全局 is_repetitivesystem_name_ti = self.ids.user_name_text_input_id.textsystem_id_ti = self.ids.user_id_text_input_id.textcurrent_items_in_recycle_view = self.ids.recycle_view_widget_id.items_of_rvnew_item = {"color": (0, 0, 0, 1), "font_size": "20", "text": "", "user_id": ""}对于范围内的 item_index(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")打印(fcurrent_item_name:{current_item_name}_______current_item_id:{current_item_id}")如果 system_name_ti == current_item_name:print("错误:重复的用户名")is_repetitive = 真休息elif system_id_ti == current_item_id:print("错误:重复的用户 ID")is_repetitive = 真休息别的:is_repetitive = 假如果不是 is_repetitive:print("其他情况")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)类 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):对于范围内的 i (1, 21):self.items_of_rv.append({"color": (0, 0, 0, 1), "font_size": "20", "text": f"使用 {i}","user_id": f"{100 * i}"})def update_my_data(self):self.data = [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 调用")类 SelectableRecycleBoxLayout(FocusBehavior,LayoutSelectionBehavior,RecycleBoxLayout):""" 向视图添加选择和焦点行为."""类 SelectableLabel(RecycleDataViewBehavior,标签):""" 为标签添加选择支持 """索引 = 无选择 = BooleanProperty(False)可选 = BooleanProperty(True)def refresh_view_attrs(self, rv, index, data):""" 捕捉并处理视图变化 """self.index = 索引return super(SelectableLabel, self).refresh_view_attrs(rv, index, data)def on_touch_down(自我,触摸):""" 按下时添加选择 """if super(SelectableLabel, self).on_touch_down(touch):返回真如果 self.collide_point(*touch.pos) 和 self.selectable:返回 self.parent.select_with_touch(self.index, touch)def apply_selection(self, rv, index, is_selected):""" 响应视图中项目的选择."""self.selected = 不是 is_selected如果 is_selected:rv.data[index].update({'color': (1, 1, 1, 1)})self.refresh_view_attrs(RecycleViewWidget(), index, rv.data[index])print("选择更改为 {0}".format(rv.data[index]))self.update_text_inputs(rv.data[index])别的:print("选择从 {0} 中删除".format(rv.data[index]))如果 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 = 不是 self.selecteddef update_text_inputs(self, selected_system, *kwarg):user_name_text_input = self.parent.parent.parent.parent.parent.user_name_text_input_iduser_id_text_input = self.parent.parent.parent.parent.parent.user_id_text_input_iduser_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")我的应用程序(应用程序)类:def __init__(self, **kwargs):super().__init__(**kwargs)定义构建(自我):返回 main_style如果 __name__ == '__main__':MyApp().run()

我的 test.kv 文件:

经理:我的第一屏:<可选标签>:画布之前:颜色:rgba: (0, 0, 1, 1) if self.selected else (1, 1, 1, 1)长方形:pos: self.pos尺寸:self.size<RecycleViewWidget>:视图类:可选标签"可选回收框布局:default_size:无,dp(56)default_size_hint:1,无size_hint_y:无高度:self.minimum_height方向:垂直"<我的第一屏>:名称:'system_setup_page'user_name_text_input_id:user_name_text_input_iduser_id_text_input_id:user_id_text_input_idrecycle_view_widget_id:recycle_view_widget_id网格布局:列数:2盒子布局:列数:1填充:20回收视图小部件:id:recycle_view_widget_id浮动布局:标签:size_hint:无,无文本:用户名:"字体大小:22pos_hint: {'center': (20/100, 90/100)}文本输入:id: user_name_text_input_idsize_hint:无,无hint_text: "输入你的名字..."尺寸:200、30多行:假pos_hint: {'center': (65/100, 90/100)}标签:size_hint:无,无文本:用户 ID:"字体大小:20pos_hint: {'center': (20/100, 70/100)}文本输入:id: user_id_text_input_idsize_hint:无,无尺寸:200、30hint_text: "输入你的 ID..."pos_hint: {'center': (65/100, 70/100)}按钮:文本:添加新用户"size_hint:无,无字体大小:20尺寸:300、50pos_hint: {'center': (50/100, 30/100)}on_release: root.update_recycle_view()

解决方案

它似乎在 RecycleViewWidget 不可见时从 Label 中删除 - 然后 Label 没有父级.

Label 可见时,它会将 Label 放入 RecycleViewWidget 并再次具有 parent.但首先它会执行 apply_selection - 所以它会在 Label 再次拥有父级之前运行它.

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

<小时>

但在这种情况下,您可以使用 rv(它是 RecycleViewWidget 的实例)来访问 FirstScreen 并访问 <代码>文本输入

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

screen = rv.parent.parent.parentuser_name_text_input = screen.user_name_text_input_iduser_id_text_input = screen.user_id_text_input_id

<小时>

我发现你也可以不使用 parent 和不使用 rv

screen = main_style.screens[0]# 或者#screen = main_style.screens[0].idsuser_name_text_input = screen.user_name_text_input_iduser_id_text_input = screen.user_id_text_input_id

<小时>

def apply_selection(self, rv, index, is_selected):""" 响应视图中项目的选择."""self.selected = is_selected如果 is_selected:rv.data[index].update({'color': (1, 1, 1, 1)})self.refresh_view_attrs(rv, index, rv.data[index])print("选择更改为 {0}".format(rv.data[index]))self.update_text_inputs(rv, index)别的:rv.data[index].update({'color': (0, 0, 0, 1)})self.refresh_view_attrs(rv, index, rv.data[index])print("选择从 {0} 中删除".format(rv.data[index]))def update_text_inputs(self, rv, index, *kwarg):屏幕 = rv.parent.parent.parent#screen = main_style.screens[0]#screen = main_style.screens[0].ids打印('[调试]屏幕:',屏幕)user_name_text_input = screen.user_name_text_input_iduser_id_text_input = screen.user_id_text_input_iduser_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 的原始第一个实例p>

My Kivy App Description:

I have 3 types of widgets in MyFirstScreen:

  • A RecycleView that has multiple "User"s as its items. (each item is a dictionary)
  • Three TextInputs that are related to values of each recycle view item. (if you select any items of RecycleView these TextInputs will loaded with corresponding dictionary values)
  • An "Add New User" Button. (if you enter NEW values in TextInputss and press this button, RecycleView will be updated with: previous items + your new item)

Issue:

An error occurs in these two situations:

Situation A:

  1. I select an item (for example : "User 1").
  2. I scroll DOWN until the selected item hides completely from RecycleView.
  3. I scroll UP to show selected item again.

Situation B:

  1. I select an item (for example : "User 1").
  2. I change the new text values which is loaded in TextInputss.
  3. I press the "Add New User" Button.

In both situations when I want to do step 3, this error occurs:

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

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

Does anyone know what am I doing wrong here or how to make this work? Thank you in advance...

My KivyTest.py file:

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

My test.kv file:

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

解决方案

It seems it removes Label from RecycleViewWidget when it is not visible - and then Label has no parent.

When Label is visible agant then it puts Label in RecycleViewWidget and it has again parent. But first it executes apply_selection - so it runs it before Label has parent again.

Similar when it crates new Label then it first executes apply_selection before it adds Labal to RecycleViewWidget - so it runs it before Label has parent.


But in this situation you can use rv (which is instance of RecycleViewWidget) to get access to FirstScreen and to get access to TextInput

Now I sends rv and index instead of rv.data[index] to update_text_inputs so I can use it to get rv.parent.parent.parent and to get 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: I found you can get it also without using parent and without 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")


BTW: I use also rv instead of RecycleViewWidget() in refresh_view_attrs() because it can make the same problem as in previous question - RecycleViewWidget() can creates new instance of RecycleViewWidget and you should work with original first instance of RecycleViewWidget

相关文章