问题
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 adictionary
) - Three
TextInput
s that are related to values of each recycle view item. (if you select any items ofRecycleView
theseTextInput
s will loaded with correspondingdictionary
values) - An "Add New User"
Button
. (if you enter NEW values inTextInputs
s and press this button,RecycleView
will be updated with: previous items + your new item)
Issue:
An error occurs in these two situations:
Situation A:
- I select an item (for example : "User 1").
- I scroll DOWN until the selected item hides completely from
RecycleView
. - I scroll UP to show selected item again.
Situation B:
- I select an item (for example : "User 1").
- I change the new text values which is loaded in
TextInputs
s. - 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()
回答1:
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
来源:https://stackoverflow.com/questions/61924388/kivy-attributeerror-nonetype-object-has-no-attribute-parent-when-scroll-do