Rows in Kivy RecycleView repeating

702 views
Skip to first unread message

shiroo...@gmail.com

unread,
Jul 25, 2018, 7:48:18 AM7/25/18
to Kivy users support

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



cristian garmin

unread,
Jul 15, 2020, 2:45:02 AM7/15/20
to Kivy users support
Hello...have you found a solution to solve this issue ? thanks

Elliot Garbus

unread,
Jul 15, 2020, 12:21:57 PM7/15/20
to kivy-...@googlegroups.com

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.

 

cristian garmin

unread,
Jul 16, 2020, 7:40:37 AM7/16/20
to Kivy users support
Thank you for reply...i will let you know if this solution is working for me.

To unsubscribe from this group and stop receiving emails from it, send an email to kivy-...@googlegroups.com.

Malcolm Nooning

unread,
Jul 27, 2020, 1:29:31 PM7/27/20
to Kivy users support
I have the same problem.  I, too, am just now learning Kivy and RecycleView.
I have tried the sets of code presented so far in this thread, to no avail.
For each set, I would run the code, then shrink the window with my mouse so that only a few rows showed.  That makes it easier to test it.
To test it I simply pressed "Populate with same text", then added 1, 2, ... etc., to each succeeding text input row.  After maybe 12 rows I scrolled back up.  Besides some numbers repeating, in the last code set, some rows had the added number disappear altogether! 

Has anyone made any progress with this?
Thanks


Elliot Garbus

unread,
Jul 27, 2020, 1:39:17 PM7/27/20
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.

 

Malcolm Nooning

unread,
Jul 27, 2020, 6:02:15 PM7/27/20
to kivy-...@googlegroups.com
First, the code I thought I ran may have been prior to your correction.  In the meanwhile I will post the code I think you posted, but the window disappears almost immediately.   The entire log is only 12 lines long, so I will paste that in, too.  Thanks
# Source: https://groups.google.com/forum/#!topic/kivy-users/lKJJdSI5nbI

# Elliot Garbus
# RE: [kivy-users] Re: Rows in Kivy RecycleView repeating


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)

Now for the log file:
[INFO   ] Logger: Record log in C:\Users\Malcolm\.kivy\logs\kivy_20-07-27_106.txt
[INFO   ] deps: Successfully imported "kivy_deps.gstreamer" 0.2.0
[INFO   ] deps: Successfully imported "kivy_deps.angle" 0.2.0
[INFO   ] deps: Successfully imported "kivy_deps.glew" 0.2.0
[INFO   ] deps: Successfully imported "kivy_deps.sdl2" 0.2.0
[INFO   ] Kivy: v2.0.0rc2, git-78fb93b, 20200429
[INFO   ] Kivy: Installed at "C:\Python38\lib\site-packages\kivy\__init__.py"
[INFO   ] Python: v3.8.2 (tags/v3.8.2:7b3ab59, Feb 25 2020, 22:45:29) [MSC v.1916 32 bit (Intel)]
[INFO   ] Python: Interpreter at "C:\Python38\python.exe"
[INFO   ] Factory: 184 symbols loaded
[INFO   ] Image: Providers: img_tex, img_dds, img_sdl2, img_pil (img_ffpyplayer, img_gif ignored)
[WARNING] Factory: Ignored class "Row" re-declaration. Current -  module: None, cls: <class '__main__.Row'>, baseclass: None, filename: None. Ignored -  module: None, cls: None, baseclass: BoxLayout, filename: None.

Malcolm Nooning

unread,
Jul 27, 2020, 6:10:35 PM7/27/20
to kivy-...@googlegroups.com
I missed the last part of your code.    The window comes up and stays up now, but nowI get a blank, black  screen.  Let me repost the code.

class TestApp(App):

    def build(self):
        return Test()


if __name__ == '__main__':
    TestApp().run()

Elliot Garbus

unread,
Jul 28, 2020, 10:20:02 AM7/28/20
to kivy-...@googlegroups.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'
Reply all
Reply to author
Forward
0 new messages