Kivy:AttributeError:在回收视图中向下滚动并再次向上滚动时,'NoneType'对象没有属性'parent' [英] Kivy: AttributeError: 'NoneType' object has no attribute 'parent' when scroll down and scroll up again in recycle view

查看:95
本文介绍了Kivy:AttributeError:在回收视图中向下滚动并再次向上滚动时,'NoneType'对象没有属性'parent'的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have 3 types of widgets in MyFirstScreen:

  • 具有多个用户"作为其项的RecycleView. (每个项目都是dictionary)
  • 与每个回收视图项目的值相关的三个TextInput. (如果您选择RecycleView的任何项目,这些TextInput将会加载相应的dictionary值)
  • 添加新用户" Button. (如果您在TextInputs s中输入 NEW 值,然后按此按钮,则RecycleView将更新为:以前的项目+您的新项目)
  • 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)

在以下两种情况下会发生错误:

An error occurs in these two situations:

情况A:

  1. 我选择一个项目(例如:用户1").
  2. 我向下滚动,直到所选项目完全隐藏在RecycleView中.
  3. 我向上滚动以再次显示选中的项目.
  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.

情况B:

  1. 我选择一个项目(例如:用户1").
  2. 我更改了TextInputs s中加载的新文本值.
  3. 我按下添加新用户" Button.
  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.

在两种情况下,当我都想执行步骤3时,都会发生此错误:

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...

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

推荐答案

当它不可见时,它似乎将LabelRecycleViewWidget中删除了-然后Label没有父级.

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

Label可见时,则将Label放入RecycleViewWidget中,并再次具有parent.但是首先它执行apply_selection-因此它在Label再次具有父级之前运行它.

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.

类似,当它创建新的Label时,它会先执行apply_selection,然后再将Labal添加到RecycleViewWidget中-因此它会在Label具有父级之前运行它.

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.

但是在这种情况下,您可以使用rv(它是RecycleViewWidget的实例)来访问FirstScreen和访问TextInput

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

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

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


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


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


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


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

这篇关于Kivy:AttributeError:在回收视图中向下滚动并再次向上滚动时,'NoneType'对象没有属性'parent'的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆