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 种类型的小部件:
- 一个
RecycleView
有多个 "User" 作为其项目。 (每一项都是dictionary
)
- 三个
TextInput
与每个回收视图项的值相关。 (如果您 select RecycleView
的任何项目,这些 TextInput
将加载相应的 dictionary
值)
- 一个"Add New User"
Button
。 (如果您在 TextInputs
s 中输入 NEW 值并按下此按钮,RecycleView
将更新为:以前的项目 + 您的新项目)
问题:
这两种情况都会出错:
情况一:
- 我 select 一个项目(例如:"User 1")。
- 我向下滚动直到 selected 项目完全隐藏
RecycleView
。
- 我向上滚动以再次显示 selected 项目。
情况B:
- 我 select 一个项目(例如:"User 1")。
- 我更改了在
TextInputs
s 中加载的新文本值。
- 我按了"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.
但在这种情况下,您可以使用 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
编辑: 我发现你也可以在不使用 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
的原始第一个实例
My Kivy 应用说明:
我在 MyFirstScreen
中有 3 种类型的小部件:
- 一个
RecycleView
有多个 "User" 作为其项目。 (每一项都是dictionary
) - 三个
TextInput
与每个回收视图项的值相关。 (如果您 selectRecycleView
的任何项目,这些TextInput
将加载相应的dictionary
值) - 一个"Add New User"
Button
。 (如果您在TextInputs
s 中输入 NEW 值并按下此按钮,RecycleView
将更新为:以前的项目 + 您的新项目)
问题:
这两种情况都会出错:
情况一:
- 我 select 一个项目(例如:"User 1")。
- 我向下滚动直到 selected 项目完全隐藏
RecycleView
。 - 我向上滚动以再次显示 selected 项目。
情况B:
- 我 select 一个项目(例如:"User 1")。
- 我更改了在
TextInputs
s 中加载的新文本值。 - 我按了"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.
但在这种情况下,您可以使用 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
编辑: 我发现你也可以在不使用 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