I'm using Kivy RecycleView with a Row class to create an editable table of values. The problem is that when I type an entry in one line, this value also appears in other lines further down in the table. In the debugger, editing a row shows correctly the RecycleView.data updated for only that row which I typed in and not the other rows where they appeared so there seems to be a disconnect between the UI and the underlying data. Can't understand what I might be missing or misunderstanding with this approach. Please help. Thank you!
I've included a link to a video of the behavior of the below code so hopefully it is self explanatory. This doesn't happen when the initial rows are populated with unique values.
In the video, the application has three buttons. The left button populates random unique values for the rows. With these rows populated with unique values, editing them doesn't cause before mentioned problem and behaves expectedly. The middle and right buttons populate the rows with a repeated "None" text and blanks, respectively. When populating the rows with this type of data, editing any of these rows will cause the value to appear repeatedly further down when scrolling. I'm using Kivy 1.10.0 and Python 3.6.2 on OSX.
#https://github.com/kivy/kivy/blob/master/examples/widgets/recycleview/basic_data.py
from random import sample
from string import ascii_lowercase
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.uix.recycleview.views import RecycleDataViewBehavior
kv = """
<Row@BoxLayout>:
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 1
Rectangle:
size: self.size
pos: self.pos
value: ''
TextInput:
multiline: False
text: root.value
on_text_validate: root.update(args[0].text)
<Test>:
canvas:
Color:
rgba: 0.3, 0.3, 0.3, 1
Rectangle:
size: self.size
pos: self.pos
rv: rv
orientation: 'vertical'
GridLayout:
cols: 3
rows: 1
size_hint_y: None
height: dp(108)
padding: dp(8)
spacing: dp(16)
Button:
text: 'Populate with text'
on_press: root.populate_with_text()
Button:
text: 'Populate with same text'
on_press: root.populate_with_same_text()
Button:
text: 'Populate with blanks'
on_press: root.populate_with_blanks()
RecycleView:
id: rv
scroll_type: ['bars', 'content']
scroll_wheel_distance: dp(114)
bar_width: dp(10)
viewclass: 'Row'
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(2)
"""
class Row(RecycleDataViewBehavior, BoxLayout):
def update(self, text):
self.parent.parent.data[self.index]['value'] = text
self.parent.parent.refresh_from_data()
def refresh_view_attrs(self, rv, index, data):
self.index = index
return super(Row, self).refresh_view_attrs(rv, index, data)
class Test(BoxLayout):
def populate_with_text(self):
self.rv.data = [{'value': ''.join(sample(ascii_lowercase, 6)), 'index': x}
for x in range(50)]
self.rv.refresh_from_data()
def populate_with_same_text(self):
self.rv.data = [{'value': 'None', 'index': x}
for x in range(50)]
self.rv.refresh_from_data()
def populate_with_blanks(self):
self.rv.data = [{'value': '', 'index': x}
for x in range(50)]
self.rv.refresh_from_data()
Builder.load_string(kv)
class TestApp(App):
def build(self):
return Test()
if __name__ == '__main__':
TestApp().run()
Every time a widget is visible in the view, the visible widget will apply the list of attributes from the items in the data list, to that widget. Of course, the binding applies, so keeping state in the widget doesn't work.
You want the (recycled) widget to be set/reset when the widget is used for another data item so you have to save that selected state outside of the widget.
One possible solution is to edit the items in data(the RecycleView data attribute), but that could trigger new dispatches and so reset which widgets displays which items, and cause trouble.
The preferred solution is to save the widget state to a different list property, and just make the widget lookup that property when the widget’s key is updated.
I simplified things in your example, and created a new list to store the text fields.
from random import sample
from string import ascii_lowercase
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ListProperty, NumericProperty
kv = """
<Row@BoxLayout>:
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 1
Rectangle:
size: self.size
pos: self.pos
value: ''
TextInput:
multiline: False
text: app.root.text_list[root.index]
on_text_validate: app.root.text_list[root.index] = self.text
data: root.rv_data_list
RecycleBoxLayout:
default_size: None, dp(56)
default_size_hint: 1, None
size_hint_y: None
height: self.minimum_height
orientation: 'vertical'
spacing: dp(2)
"""
class Row(BoxLayout):
index = NumericProperty()
class Test(BoxLayout):
rv_data_list = ListProperty([{'index': x} for x in range(50)])
text_list = ListProperty([''] * 50)
def populate_with_text(self):
self.text_list = [''.join(sample(ascii_lowercase, 6)) for x in range(50)]
def populate_with_same_text(self):
self.text_list = ['None' for x in range(50)]
def populate_with_blanks(self):
self.text_list = ['' for x in range(50)]
Builder.load_string(kv)
--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/fb0780d1-187d-4541-8bc3-d74047a82260o%40googlegroups.com.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-...@googlegroups.com.
Post your code. The code I posted does not have this behavior.
From: Malcolm Nooning
Sent: Monday, July 27, 2020 10:29 AM
To: Kivy users support
Subject: [kivy-users] Re: Rows in Kivy RecycleView repeating
I have the same problem. I, too, am just now learning Kivy and RecycleView.
--
You received this message because you are subscribed to the Google Groups "Kivy users support" group.
To unsubscribe from this group and stop receiving emails from it, send an email to kivy-users+...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/d6d1fc14-fecb-473c-a43d-6b0f8c64b09ao%40googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/5f1f113c.1c69fb81.165a1.3974SMTPIN_ADDED_MISSING%40gmr-mx.google.com.
Your missing some of the kv code.
from random import sample
from string import ascii_lowercase
from kivy.app import App
from kivy.lang import Builder
from kivy.uix.boxlayout import BoxLayout
from kivy.properties import ListProperty, NumericProperty
kv =
"""
<Row@BoxLayout>:
canvas.before:
Color:
rgba: 0.5, 0.5, 0.5, 1
Rectangle:
size: self.size
pos: self.pos
value: ''
TextInput:
multiline: False
text: app.root.text_list[root.index]
on_text_validate: app.root.text_list[root.index] = self.text
<Test>:
canvas:
Color:
rgba: 0.3, 0.3, 0.3, 1
Rectangle:
size: self.size
pos: self.pos
rv: rv
orientation: 'vertical'
GridLayout:
cols: 3
rows: 1
size_hint_y: None
height: dp(108)
padding: dp(8)
spacing: dp(16)
Button:
text: 'Populate with text'
on_press: root.populate_with_text()
Button:
text: 'Populate with same text'
on_press: root.populate_with_same_text()
Button:
text: 'Populate with blanks'
on_press: root.populate_with_blanks()
RecycleView:
id: rv
scroll_type: ['bars', 'content']
scroll_wheel_distance: dp(114)
bar_width: dp(10)
viewclass: 'Row'
To view this discussion on the web visit https://groups.google.com/d/msgid/kivy-users/CALYw93N%3DRv4QxxKckVOjnjJ-QNhMa58K3vf0YB27yUOTfaLaJg%40mail.gmail.com.